mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-30 14:58:08 +00:00
- Models: Enso 1, Kai 1, Zen 6, then the creative models; Enso's panel stands open. - Research.Log: the Decision Suite and LiveCodeBench, and under them a tile each for Enso on GPQA Diamond, Kai's decision clock, Hanzo Dev's coding race (read when the page opens) and Zen 6's prefill measured by Hanzo; the section stands only while a read result holds. - Platform: eight parts, each with what it gives a builder; no apps or models row. - Open.Source (new): sixteen core repositories, each opening on GitHub. - Backed by: Techstars alone, drawn larger; the clouds Hanzo runs on are no longer shown under the hero. - The agency's case-study block leaves the landing for a Trusted by strip of every company whose launch Hanzo ran, scrolling, each opening its story. - Footer: Research, Products, Solutions, Developers, Learn and Company, the header's menus in the header's order (held by e2e/nav.spec.ts). - Learn ends with Hanzo University and each of its six courses.
61 lines
2.8 KiB
TypeScript
61 lines
2.8 KiB
TypeScript
import { test, expect } from '@playwright/test'
|
|
import { LAYERS, href } from '../lib/layers'
|
|
import { COMPANY, FOOT, FOOT_LINE, LEGAL, LOGIN, MENUS, TRY, away } from '../components/home/nav-data'
|
|
|
|
/**
|
|
* Every link the header, the phone sheet and the footer offer answers 200.
|
|
*
|
|
* The bar and the sheet are one tree (components/home/nav-data.ts). The footer
|
|
* is the short set, and it is read off the page to prove it stayed short. Run
|
|
* against `pnpm dev` or a live host with BASE_URL pointing at it. A
|
|
* redirect is not an answer: a row that lands on a forward names an address the
|
|
* site has retired. A dev server compiles each route on its first request, so
|
|
* the budget is per link, not per test.
|
|
*/
|
|
test("the footer's columns are the header's menus, in the header's order, then Company", () => {
|
|
expect(FOOT.map((m) => m.label)).toEqual([...MENUS.map((m) => m.label), COMPANY.label])
|
|
})
|
|
|
|
test('every header and footer link answers 200', async ({ page, request }) => {
|
|
test.setTimeout(900_000)
|
|
await page.goto('/', { waitUntil: 'load' })
|
|
const hrefs = new Set<string>(
|
|
[COMPANY, ...MENUS].flatMap((m) => m.groups.flatMap((g) => g.links.map((l) => l.href))),
|
|
)
|
|
for (const link of [...LOGIN, TRY, ...LEGAL]) hrefs.add(link.href)
|
|
const want = [
|
|
...FOOT.flatMap((m) => [m.href, ...m.groups.flatMap((g) => g.links.map((l) => l.href))]),
|
|
...FOOT_LINE.map((l) => l.href),
|
|
]
|
|
const foot = await page.locator('footer[data-slot="footer"] a[href]').evaluateAll((els) => els.map((e) => e.getAttribute('href') ?? ''))
|
|
expect(foot.filter((h) => h !== '/').sort()).toEqual([...new Set(want)].sort())
|
|
for (const h of want) hrefs.add(h)
|
|
|
|
const failed: string[] = []
|
|
for (const h of hrefs) {
|
|
if (h.startsWith('#')) continue // a control on this page, not a page
|
|
const res = away(h)
|
|
? await request.get(h, { timeout: 30_000 })
|
|
: await request.get(h, { maxRedirects: 0, timeout: 120_000 })
|
|
if (res.status() !== 200) failed.push(`${h} → ${res.status()}`)
|
|
}
|
|
expect(failed, 'a header or footer link that does not answer 200').toEqual([])
|
|
})
|
|
|
|
/**
|
|
* The product index is the OS: every layer in lib/layers, each linking its own
|
|
* page, and each page answering. Replaces the ten-category index this route used
|
|
* to hold.
|
|
*/
|
|
test('/products draws every layer, and every layer page answers', async ({ page, request }) => {
|
|
test.setTimeout(900_000)
|
|
const res = await page.goto('/products')
|
|
expect(res?.status()).toBe(200)
|
|
await expect(page.locator('h1')).toHaveCount(1)
|
|
for (const l of LAYERS) {
|
|
await expect(page.locator(`main a[href="${href(l)}"]`).first(), `/products links ${l.name}`).toBeVisible()
|
|
const layer = await request.get(href(l), { maxRedirects: 0, timeout: 120_000 })
|
|
expect(layer.status(), `${href(l)} answers`).toBe(200)
|
|
}
|
|
})
|