mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-30 14:58:08 +00:00
87 lines
4.1 KiB
TypeScript
87 lines
4.1 KiB
TypeScript
import { test, expect } from '@playwright/test'
|
|
import { serveExport } from './export'
|
|
import { SETUP } from '../../components/home/prompts'
|
|
|
|
/**
|
|
* The fold's second action hands the platform to the reader's own agent.
|
|
*
|
|
* It copies `SETUP` (components/home/prompts.ts), the one sentence every copy
|
|
* control on the page carries: read /skill.md, sign in, pick a starter, boot
|
|
* it. `/skill.md` is written by `scripts/skill.mjs` on every build out of the
|
|
* same `GET /v1/openapi.json` snapshot the mega-menu reads, so the manual the
|
|
* sentence points at cannot describe an API that has moved on.
|
|
*
|
|
* `preventDefault` has to run during the click's own dispatch, or the browser
|
|
* has already begun following the link and the page walks off to the raw
|
|
* markdown. Measured once, with the two lines the other way round: the click
|
|
* navigated to /skill.md every time and the clipboard stayed empty. A
|
|
* screenshot cannot see that — the button looks identical either way — so it
|
|
* is asserted here.
|
|
*
|
|
* And the element is a LINK before it is a button, because content on this site
|
|
* must not depend on scripting. With JS off the manual still opens.
|
|
*/
|
|
test('the templates page copies the setup prompt without leaving the page', async ({ page, context }) => {
|
|
await context.grantPermissions(['clipboard-read', 'clipboard-write'])
|
|
const server = await serveExport()
|
|
try {
|
|
await page.goto(`${server.url}/templates`, { waitUntil: 'load' })
|
|
// A press before hydration follows the link to the raw manual (providers `Hydrated`).
|
|
await page.locator('html[data-hydrated]').waitFor({ state: 'attached' })
|
|
// `$=`: a press writes the link's address out absolute (providers `carry`),
|
|
// so the attribute reads /skill.md before it and https://…/skill.md after.
|
|
const cta = page.locator('a[data-slot="action"][href$="/skill.md"]').first()
|
|
await expect(cta).toBeVisible()
|
|
await expect(cta).toHaveText(/Copy setup prompt/)
|
|
|
|
// WATCH FOR A DOWNLOAD, and nothing else. Three candidate signals, two of
|
|
// them useless here, and each one was tried:
|
|
//
|
|
// the URL — `/skill.md` is served with no content-type (production does
|
|
// the same), so the default action SAVES the file rather than
|
|
// navigating and `page.url()` is unchanged either way. Asserting on it
|
|
// is how the first version of this gate passed against the very bug it
|
|
// was written for.
|
|
// `framenavigated` — fires on its own after hydration, measured: once,
|
|
// for the page's own URL, with no click at all. Noise.
|
|
// `download` — fires only when the default action ran. That is the whole
|
|
// difference between a cancelled click and an uncancelled one here.
|
|
const saved: string[] = []
|
|
page.on('download', (d) => saved.push(d.suggestedFilename()))
|
|
|
|
await cta.click()
|
|
await expect(cta).toHaveText(/Copied/)
|
|
// The click was cancelled: nothing reached the download manager.
|
|
expect(saved).toEqual([])
|
|
|
|
const copied = await page.evaluate(() => navigator.clipboard.readText())
|
|
expect(copied).toBe(SETUP)
|
|
expect(copied.startsWith('Read https://hanzo.ai/skill.md')).toBe(true)
|
|
|
|
// …and it goes back, so a second reader of the same page is not told a
|
|
// copy happened that did not.
|
|
await expect(cta).toHaveText(/Copy setup prompt/, { timeout: 6000 })
|
|
} finally {
|
|
await server.close()
|
|
}
|
|
})
|
|
|
|
test('with no scripting it is still a working link', async ({ browser }) => {
|
|
const server = await serveExport()
|
|
const ctx = await browser.newContext({ javaScriptEnabled: false })
|
|
try {
|
|
const page = await ctx.newPage()
|
|
await page.goto(`${server.url}/templates`, { waitUntil: 'load' })
|
|
const cta = page.locator('a[data-slot="action"][href="/skill.md"]').first()
|
|
await expect(cta).toBeVisible()
|
|
await expect(cta).toHaveText(/Copy setup prompt/)
|
|
// The href is the fallback, so it has to be the real file and not a hash.
|
|
const res = await page.request.get(server.url + '/skill.md')
|
|
expect(res.status()).toBe(200)
|
|
expect((await res.text()).startsWith('# Hanzo')).toBe(true)
|
|
} finally {
|
|
await ctx.close()
|
|
await server.close()
|
|
}
|
|
})
|