Files
hanzo.ai/e2e/nav.spec.ts
zeekay b02f6926dc The landing leads its models with Enso 1, Kai 1 and Zen 6, shows six research results, a platform of parts and its open-source core, and is backed by Techstars and trusted by the companies it launched; the footer is the header's menus
- 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.
2026-10-18 21:06:02 -07:00

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)
}
})