mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-29 07:20:43 +00:00
135 lines
6.4 KiB
TypeScript
135 lines
6.4 KiB
TypeScript
import { expect, test, type Page } from '@playwright/test'
|
||
import { serveExport } from './export'
|
||
import { bare, config, person } from './heard'
|
||
import releases from '../../data/releases.json' with { type: 'json' }
|
||
|
||
/**
|
||
* HANZO.APP IS WHERE THE APP IS GOT.
|
||
*
|
||
* Its root is this export's /app, served by the edge with the export's assets
|
||
* (universe static-sites `hanzo-app`); every other address there is hanzo.ai's.
|
||
* The page offers three things and each must work: Download is a build the
|
||
* release carries, for the reader's own platform first; Install CLI puts the
|
||
* one install line on the clipboard; Open in web is the chat at hanzo.ai. It
|
||
* reports to hanzo.app's own GA4 stream, and hanzo.ai keeps its own.
|
||
*
|
||
* hanzo.app is HSTS-preloaded, so it is routed rather than resolved: `/` is
|
||
* answered with the export's app page and everything else with the export's own
|
||
* file, which is what the edge does. Nothing leaves this machine.
|
||
*/
|
||
|
||
const HANZO_APP = 'G-P3G0R2PX3D'
|
||
const HANZO_AI = 'G-RBVVVLFZDR'
|
||
const MAC = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36'
|
||
|
||
let base: string
|
||
let stop: () => Promise<void>
|
||
|
||
test.beforeAll(async () => {
|
||
const served = await serveExport()
|
||
base = served.url
|
||
stop = served.close
|
||
})
|
||
test.afterAll(async () => stop())
|
||
|
||
/** hanzo.app as the edge answers it; hanzo.ai stubbed; returns the GA4 ids asked for. */
|
||
async function edge(page: Page): Promise<{ ga: string[]; ai: string[] }> {
|
||
await person(page)
|
||
await page.route('**/*', (route) => (new URL(route.request().url()).origin === base ? route.continue() : route.abort()))
|
||
const ga: string[] = []
|
||
const ai: string[] = []
|
||
await config(page)
|
||
await page.route('https://www.googletagmanager.com/**', (route) => {
|
||
ga.push(new URL(route.request().url()).searchParams.get('id') ?? '')
|
||
return route.fulfill({ status: 200, contentType: 'text/javascript', body: '' })
|
||
})
|
||
await page.route('https://hanzo.app/**', async (route) => {
|
||
const u = new URL(route.request().url())
|
||
const path = u.pathname === '/' ? '/app' : u.pathname
|
||
return route.fulfill({ response: await route.fetch({ url: `${base}${path}${u.search}` }) })
|
||
})
|
||
await page.route('https://hanzo.ai/**', (route) => {
|
||
ai.push(route.request().url())
|
||
return route.fulfill({ status: 200, contentType: 'text/html', body: '<!doctype html><title>Hanzo</title>' })
|
||
})
|
||
return { ga, ai }
|
||
}
|
||
|
||
test.describe('hanzo.app', () => {
|
||
test.use({ userAgent: MAC, permissions: ['clipboard-read', 'clipboard-write'] })
|
||
|
||
test('is the page that gets the app: Download, Install CLI, Open in web', async ({ page }) => {
|
||
await edge(page)
|
||
await page.goto('https://hanzo.app/')
|
||
await expect(page.getByRole('heading', { level: 1, name: 'Get the Hanzo App' })).toBeVisible()
|
||
for (const choice of ['Download', 'Install CLI', 'Open in web']) await expect(page.getByRole('heading', { level: 2, name: choice })).toBeVisible()
|
||
expect(await page.locator('link[rel=canonical]').getAttribute('href')).toBe('https://hanzo.app')
|
||
})
|
||
|
||
test('Download leads with the reader’s own build, one the release carries', async ({ page }) => {
|
||
await edge(page)
|
||
await page.goto('https://hanzo.app/')
|
||
const own = page.getByRole('link', { name: /^Download for macOS/ })
|
||
await expect(own).toBeVisible()
|
||
// The desktop app once its release carries builds, and the CLI until then (data/downloads `NATIVE`).
|
||
const shelf = releases as Record<string, { platforms: Record<string, { url: string }> }>
|
||
const native = Object.keys(shelf.desktop.platforms).length ? shelf.desktop : shelf.cli
|
||
const built = Object.values(native.platforms).map((p) => p.url)
|
||
expect(built).toContain(await own.getAttribute('href'))
|
||
// Every other platform is a row of its own.
|
||
await expect(page.getByRole('link', { name: /^Download .* for Windows$/ })).toBeVisible()
|
||
await expect(page.getByRole('link', { name: 'All downloads and checksums →' })).toHaveAttribute('href', 'https://hanzo.ai/download')
|
||
})
|
||
|
||
test('Install CLI puts the install line on the clipboard', async ({ page }) => {
|
||
await edge(page)
|
||
await page.goto('https://hanzo.app/')
|
||
await expect(page.getByText('curl -fsSL hanzo.sh | sh', { exact: true })).toBeVisible()
|
||
// The line is in the export's HTML; the button copies only once React has hydrated it.
|
||
await page.locator('html[data-hydrated]').waitFor({ state: 'attached' })
|
||
await page.getByRole('button', { name: 'Copy the install command' }).click()
|
||
await expect(page.getByRole('button', { name: 'Copied' })).toBeVisible()
|
||
expect(await page.evaluate(() => navigator.clipboard.readText())).toBe('curl -fsSL hanzo.sh | sh')
|
||
})
|
||
|
||
test('Open in web opens the chat at hanzo.ai, in this tab', async ({ page }) => {
|
||
const { ai } = await edge(page)
|
||
await page.goto('https://hanzo.app/')
|
||
await page.getByRole('link', { name: 'Open Hanzo' }).click()
|
||
await page.waitForURL((u) => bare(u.href) === 'https://hanzo.ai/chat')
|
||
expect(ai.map(bare)).toEqual(['https://hanzo.ai/chat'])
|
||
expect(page.context().pages()).toHaveLength(1)
|
||
})
|
||
|
||
test('reports to hanzo.app’s own GA4 stream', async ({ page }) => {
|
||
const { ga } = await edge(page)
|
||
await page.goto('https://hanzo.app/')
|
||
await expect.poll(() => ga).toEqual([HANZO_APP])
|
||
const config = await page.evaluate(() =>
|
||
((window as unknown as { dataLayer: IArguments[] }).dataLayer ?? []).map((a) => Array.from(a)).find((a) => a[0] === 'config'),
|
||
)
|
||
expect(config?.[1]).toBe(HANZO_APP)
|
||
})
|
||
})
|
||
|
||
test('the same page on hanzo.ai reports to hanzo.ai’s stream', async ({ page }) => {
|
||
const { ga } = await edge(page)
|
||
await page.goto(`${base}/app`)
|
||
await expect.poll(() => ga).toEqual([HANZO_AI])
|
||
})
|
||
|
||
test('a phone is not offered a build it cannot open', async ({ browser }) => {
|
||
const phone = await browser.newContext({
|
||
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1',
|
||
viewport: { width: 390, height: 844 },
|
||
hasTouch: true,
|
||
})
|
||
const page = await phone.newPage()
|
||
await edge(page)
|
||
await page.goto('https://hanzo.app/')
|
||
await expect(page.getByText('There is no build for a phone. Open the app in this browser instead.')).toBeVisible()
|
||
await expect(page.getByRole('link', { name: /^Download for / })).toHaveCount(0)
|
||
await expect(page.getByRole('link', { name: 'Open Hanzo' })).toBeVisible()
|
||
await phone.close()
|
||
})
|