mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-30 14:58:08 +00:00
101 lines
5.4 KiB
TypeScript
101 lines
5.4 KiB
TypeScript
import { expect, test, type Page } from '@playwright/test'
|
||
import { serveExport, read } from './export'
|
||
import { AGENTS } from '../../components/gen/roster'
|
||
import { sideways } from './wide'
|
||
|
||
/**
|
||
* /gen, on the bytes that ship: the harness's page.
|
||
*
|
||
* It leads with the install (`curl -fsSL https://hanzo.sh | sh -s gen`), then
|
||
* `gen` and `gen --<agent>`, keeps `hanzo dev --<agent>` as the same session
|
||
* inside the Hanzo CLI, draws every agent the harness's list names
|
||
* (components/gen/roster) with its flag, and names gen's latest release only as
|
||
* GitHub answers it when the page opens.
|
||
*/
|
||
|
||
const RELEASE = 'https://api.github.com/repos/hanzoai/gen/releases/latest'
|
||
/** GitHub's API answers any origin. */
|
||
const CORS = { 'access-control-allow-origin': '*' }
|
||
|
||
/** Waits for gui to mount the page, which is when its effects (the release read) have run. */
|
||
const hydrated = (page: Page) =>
|
||
page.waitForFunction(() => !document.documentElement.className.includes('t_unmounted'), null, { timeout: 60_000 })
|
||
|
||
let base: string
|
||
let stop: () => Promise<void>
|
||
test.beforeAll(async () => {
|
||
const served = await serveExport()
|
||
base = served.url
|
||
stop = served.close
|
||
})
|
||
test.afterAll(async () => stop())
|
||
|
||
test('the export holds /gen: its own canonical, offered to crawlers', () => {
|
||
const html = read('gen.html')
|
||
expect(html).toContain('<title>Hanzo Gen')
|
||
expect(html).toContain('<link rel="canonical" href="https://hanzo.ai/gen"/>')
|
||
expect(html).not.toContain('noindex')
|
||
expect(read('sitemap.xml'), '/gen is offered').toContain('<loc>https://hanzo.ai/gen</loc>')
|
||
})
|
||
|
||
for (const width of [1440, 390]) {
|
||
test(`/gen is the harness’s page at ${width}px: the install first, every agent and its flag, and the release GitHub names`, async ({ page }, info) => {
|
||
await page.route(RELEASE, (route) =>
|
||
route.fulfill({ headers: CORS, json: { tag_name: 'v9.8.7', html_url: 'https://github.com/hanzoai/gen/releases/tag/v9.8.7' } }),
|
||
)
|
||
await page.setViewportSize({ width, height: 900 })
|
||
await page.goto(`${base}/gen`)
|
||
await expect(page.getByRole('heading', { level: 1 })).toHaveText(/Any agent\.\s*Our rails\./, { timeout: 20_000 })
|
||
await expect(page.locator('header[data-slot="masthead"]')).toBeVisible()
|
||
await expect(page.locator('[data-agent]')).toHaveCount(AGENTS.length)
|
||
for (const a of AGENTS) await expect(page.locator(`[data-agent="${a.flag}"]`)).toContainText(a.name)
|
||
// The install leads, gen runs the agents, and hanzo dev is the same session in the CLI.
|
||
const text = await page.locator('main').innerText()
|
||
expect(text).toContain('curl -fsSL https://hanzo.sh | sh -s gen')
|
||
for (const command of ['gen --claude', 'gen --codex', 'hanzo dev --claude']) {
|
||
expect(text).toContain(command)
|
||
expect(text.indexOf('curl -fsSL https://hanzo.sh | sh -s gen'), `the install comes before ${command}`).toBeLessThan(text.indexOf(command))
|
||
}
|
||
expect(text, 'gen is released').not.toMatch(/\bcoming\b|not (yet )?released/i)
|
||
// The hero's calls are every family page's (9920b118); the install is one press away under Read.
|
||
await expect(page.getByRole('link', { name: 'Get API key', exact: true }).first()).toHaveAttribute('href', 'https://platform.hanzo.ai/keys')
|
||
await expect(page.getByRole('link', { name: 'Install curl -fsSL https://hanzo.sh | sh -s gen', exact: true })).toHaveAttribute('href', '#install')
|
||
await hydrated(page)
|
||
await expect(page.getByRole('link', { name: 'Latest release v9.8.7 →' })).toHaveAttribute('href', 'https://github.com/hanzoai/gen/releases/tag/v9.8.7', {
|
||
timeout: 30_000,
|
||
})
|
||
expect(await sideways(page), 'nothing wider than the window').toBeLessThanOrEqual(0)
|
||
await page.evaluate(() => window.scrollTo(0, 0))
|
||
await page.screenshot({ path: info.outputPath(`gen-${width}.png`) })
|
||
await page.screenshot({ path: info.outputPath(`gen-${width}-full.png`), fullPage: true })
|
||
})
|
||
}
|
||
|
||
test('/gen says nothing about a release GitHub does not answer for', async ({ page }) => {
|
||
await page.route(RELEASE, (route) => route.fulfill({ status: 403, headers: CORS, json: { message: 'rate limited' } }))
|
||
const refused = page.waitForResponse(RELEASE, { timeout: 60_000 })
|
||
await page.goto(`${base}/gen`)
|
||
await expect(page.getByRole('heading', { level: 1 })).toBeVisible({ timeout: 20_000 })
|
||
await refused
|
||
await page.waitForTimeout(1000)
|
||
await expect(page.getByText(/Latest release/)).toHaveCount(0)
|
||
})
|
||
|
||
test('the products menu names Dev and Gen, and Gen opens its page', async ({ page }) => {
|
||
await page.setViewportSize({ width: 1440, height: 900 })
|
||
// From Dev's page, which wears the site's header: Gen is the next door over.
|
||
await page.goto(`${base}/dev`, { waitUntil: 'load' })
|
||
const products = page.getByRole('banner').getByRole('button', { name: 'Products', exact: true })
|
||
const plane = page.locator('#menu-products')
|
||
await expect(async () => {
|
||
if (!(await plane.isVisible())) await products.click()
|
||
await expect(plane).toBeVisible({ timeout: 1000 })
|
||
}).toPass({ timeout: 15_000 })
|
||
await expect(plane.getByRole('link', { name: 'Hanzo Dev', exact: true })).toHaveAttribute('href', '/dev')
|
||
const gen = plane.getByRole('link', { name: 'Hanzo Gen', exact: true })
|
||
await expect(gen).toHaveAttribute('href', '/gen')
|
||
await gen.click()
|
||
await page.waitForURL((u) => u.pathname === '/gen', { timeout: 20_000 })
|
||
await expect(page.getByRole('heading', { level: 1 })).toHaveText(/Any agent\.\s*Our rails\./, { timeout: 20_000 })
|
||
})
|