mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-30 14:58:08 +00:00
The landing carries the new arc in full — the crew band among the sections — and reads in the voice the site now keeps: short lines, one idea each, nothing a reader has to decode. The console is platform.hanzo.ai on every page, and the API-key door is the page the console itself opens, /api-keys. Nothing a visitor can see calls itself deprecated or frozen. Playwright found and the sweeps closed: Zen Mono's feature settings collapsed "--" into one cell, so the terminal drew the wrong command — code type now sets its own font features; the fixed composer and the corner mark ride over content on a scrim rather than through it; the logo strip fades at its ends instead of cutting words; the phone in the product composition is a 9:19.5 device layered over the browser rather than a squashed panel; the workforce and the connected systems break the run of centred sections. The header keeps the shell's own gutter and glass. Two site rules written for a 56px workspace rail that no longer exists forced the bar's padding to a negative value the browser read as zero, and an opaque ground over the shell's glass; both are gone, so the bar sits where the mark does and shares one material with any menu that opens from it. @hanzogui/shell 8.3.13.
106 lines
5.0 KiB
TypeScript
106 lines
5.0 KiB
TypeScript
// The Skills product surface, asserted against the running dev server.
|
|
//
|
|
// `/skills` is a marketing product page (app/(marketing)/skills/page.tsx) that
|
|
// renders through the shared `ProductLanding` kit. `e2e/products-audit.spec.ts`
|
|
// already walks it as one of ~90 slugs, but only for the SHAPE every product
|
|
// page shares — a 200, some <h1>, an OSS block, a deploy CTA. Nothing asserts
|
|
// what THIS page says: its heading, the six things it calls a skill, and the
|
|
// two addresses a reader leaves by. That is the gap this file fills.
|
|
//
|
|
// It drives the running dev server rather than the export — the copy is client
|
|
// content behind `ProductLanding` — so it lives in the `chromium` project and
|
|
// cannot gate a build, the same rule the older live-surface specs follow.
|
|
//
|
|
// BASE_URL=http://localhost:3000 pnpm exec playwright test --project=chromium e2e/skills.spec.ts
|
|
//
|
|
// `BASE` already defaults to :3000 (e2e/app.ts), so the command runs without the
|
|
// variable too.
|
|
|
|
import { test, expect } from '@playwright/test'
|
|
import { mkdirSync } from 'node:fs'
|
|
import { dirname, join } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
import { BASE, quiet } from './app'
|
|
|
|
// The repo's screenshot output dir — gitignored, the same tree
|
|
// `e2e/products-audit.spec.ts` writes its shots into.
|
|
const SHOTS = join(dirname(fileURLToPath(import.meta.url)), '../tests/screenshots')
|
|
|
|
// The six things the page calls a skill — the cards a reader actually sees, read
|
|
// off the live DOM rather than invented: they are the `what.pillars` titles in
|
|
// app/(marketing)/skills/page.tsx. A card dropped from that grid fails here
|
|
// rather than shipping a page that quietly says less than it did.
|
|
const SKILLS = [
|
|
'A skill is a markdown file',
|
|
'Gateway skills do the finding',
|
|
'Install it as a plugin',
|
|
'Written small on purpose',
|
|
'Cross-referenced, not duplicated',
|
|
'Add your own',
|
|
]
|
|
|
|
// Where a reader leaves by, asserted as a real href rather than link text alone
|
|
// — that is what catches a CTA repointed at a near-miss, the class of bug this
|
|
// repo's link audits keep finding. The source is one anchor and the key is the
|
|
// other; docs publishes no Skills page, so the page offers no docs link and
|
|
// `e2e/docs-links.spec.ts` holds that address on its measured-404 list.
|
|
const GITHUB = 'https://github.com/hanzoai'
|
|
const KEY = 'https://platform.hanzo.ai/api-keys'
|
|
|
|
test.describe.configure({ timeout: 120_000 })
|
|
|
|
test.describe('the Skills page says what Skills is', () => {
|
|
test.beforeAll(() => mkdirSync(SHOTS, { recursive: true }))
|
|
|
|
test('renders the hero, the six skills, and the way out', async ({ page }) => {
|
|
const resp = await page.goto(`${BASE}/skills`, { waitUntil: 'domcontentloaded' })
|
|
expect(resp?.status(), '/skills did not answer 200').toBe(200)
|
|
await quiet(page) // take the dev-server overlay out of the shot
|
|
|
|
// One heading, and it names the page. `headings.spec.ts` holds this for the
|
|
// app rooms; a product page owes a crawler and a screen reader the same one.
|
|
const h1 = page.locator('h1')
|
|
await expect(h1, '/skills must ship exactly one <h1>').toHaveCount(1)
|
|
await expect(h1).toHaveText('Skills')
|
|
|
|
// The one-line what-it-is, and the badge that names the repo.
|
|
await expect(
|
|
page.getByText('Development knowledge your coding agent loads only when it needs it'),
|
|
).toBeVisible()
|
|
await expect(page.getByText('hanzoai/skills').first()).toBeVisible()
|
|
|
|
// Every skill the grid claims to hold — the page's actual content, and the
|
|
// reason this file exists beside the generic product audit.
|
|
for (const name of SKILLS) {
|
|
await expect(
|
|
page.getByRole('heading', { name, exact: true }),
|
|
`the "${name}" card is missing from /skills`,
|
|
).toBeVisible()
|
|
}
|
|
|
|
// The final call to action, and the two addresses a reader leaves by, by
|
|
// real href rather than link text alone.
|
|
await expect(page.getByRole('heading', { name: 'Get started with Skills' })).toBeVisible()
|
|
await expect(
|
|
page.locator(`a[href="${GITHUB}"]`).first(),
|
|
'no anchor points at the Hanzo GitHub org',
|
|
).toBeVisible()
|
|
await expect(
|
|
page.locator(`a[href="${KEY}"]`).first(),
|
|
'no anchor points at the console, where the key comes from',
|
|
).toBeVisible()
|
|
|
|
// The instrument arrived. gui compiles every style prop to an atomic class
|
|
// in an external sheet; a document that loaded none answers every layout
|
|
// question with the base rule, so the hero section would read `block` and a
|
|
// screenshot would be of an unstyled page. Check the instrument before
|
|
// trusting the picture — the house rule from e2e/gates/workspace.spec.ts.
|
|
// The hero is the first <section> (a `render="section"` XStack, so flex).
|
|
const display = await page.locator('section').first().evaluate((el) => getComputedStyle(el).display)
|
|
expect(display, 'the hero section is not flex — the gui stylesheet did not arrive').toBe('flex')
|
|
|
|
// Evidence, never the assertion.
|
|
await page.screenshot({ path: join(SHOTS, 'skills.png'), fullPage: true }).catch(() => {})
|
|
})
|
|
})
|