Files
hanzo.ai/e2e/gates/local-cloud-e2e.spec.ts
zeekay 6c65e3e941 @hanzo/app 0.2.0: the app SDK, renamed from @hanzo/rooms
Every import, the catalog pin and the docs name @hanzo/app and
github.com/hanzoai/app. lib/rooms.ts is lib/app.ts and
components/rooms.tsx is components/app.tsx.
2026-10-22 19:07:20 -07:00

135 lines
6.3 KiB
TypeScript

import { test, expect } from '@playwright/test'
import { serveExport } from './export'
import { serve } from './api'
import { answer, signIn } from '../session'
test.describe('E2E Local Cloud & Swarm System Verification', () => {
test('1. Local Sovereign Cloud Daemon answers native exec in <50ms', async ({ request }) => {
// THIS GATE NEVER FAILS THE BUILD ON A SERVICE THE BUILDER DOES NOT RUN.
// It asks a daemon at 127.0.0.1:8080, which exists on a developer's machine
// after `hanzo up` and nowhere in CI — so as a step inside the publishing
// job it froze the site for a reason that has nothing to do with the commit.
// `platform.spec.ts` settles the same shape the same way: reachable or the
// test skips and says so.
const up = await request.post('http://127.0.0.1:8080/v1/exec', { data: { cmd: 'true' } }).catch(() => null)
test.skip(!up || !up.ok(), 'no local cloud daemon on 127.0.0.1:8080 — run `hanzo up` to exercise this')
// 1. Bash execution
const bashRes = await request.post('http://127.0.0.1:8080/v1/exec', {
data: {
cmd: 'echo "hanzo-swarm-verified"',
},
})
expect(bashRes.status()).toBe(200)
const bashData = await bashRes.json()
expect(bashData.stdout).toContain('hanzo-swarm-verified')
expect(bashData.exit_code).toBe(0)
// 2. Python execution
const pyRes = await request.post('http://127.0.0.1:8080/v1/exec', {
data: {
code: 'print("py-swarm-" + str(2**8))',
lang: 'python',
},
})
expect(pyRes.status()).toBe(200)
const pyData = await pyRes.json()
expect(pyData.stdout).toContain('py-swarm-256')
// 3. Node.js / JavaScript execution
const jsRes = await request.post('http://127.0.0.1:8080/v1/exec', {
data: {
code: 'console.log("js-swarm-" + (40 + 2))',
lang: 'javascript',
},
})
expect(jsRes.status()).toBe(200)
const jsData = await jsRes.json()
expect(jsData.stdout).toContain('js-swarm-42')
})
test('2. Swarm Board (/board) renders 5 columns, pre-seeded cards, and handles interaction', async ({ page }) => {
await signIn(page)
const site = await serveExport()
try {
await page.setViewportSize({ width: 1440, height: 900 })
await page.goto(`${site.url}/board`)
// Verify Board header
await expect(page.locator('h1')).toContainText('Board')
// Verify all 5 Kanban columns
await expect(page.getByText('Backlog', { exact: true })).toBeVisible()
await expect(page.getByText('To do', { exact: true })).toBeVisible()
await expect(page.getByText('Doing', { exact: true })).toBeVisible()
await expect(page.getByText('Done', { exact: true })).toBeVisible()
await expect(page.getByText('Canceled', { exact: true })).toBeVisible()
// Verify Pre-seeded swarm tasks
await expect(page.getByText('Deploy 3D Gaussian Splat Whale')).toBeVisible()
await expect(page.getByText('Sovereign Cloud Exec Runner')).toBeVisible()
await expect(page.getByText('Pay & Billing Architecture Unification')).toBeVisible()
await expect(page.getByText('Autonomous Swarm Collaboration in Channels')).toBeVisible()
// Switch to Planner and back
const plannerBtn = page.locator('button[aria-pressed]').filter({ hasText: 'Planner' })
const boardBtn = page.locator('button[aria-pressed]').filter({ hasText: 'Board' })
await plannerBtn.click()
await expect(plannerBtn).toHaveAttribute('aria-pressed', 'true')
await boardBtn.click()
await expect(boardBtn).toHaveAttribute('aria-pressed', 'true')
} finally {
await site.close()
}
})
test('3. Flagship Home (/) renders the hero with its sign-up, then the plans', async ({ page }) => {
const site = await serveExport()
// The plans are the plan authority's, read when the page opens (e2e/gates/api).
await serve(page)
try {
await page.setViewportSize({ width: 1440, height: 900 })
await page.goto(`${site.url}/`)
await expect(page).toHaveTitle(/Hanzo/)
// STRUCTURE, NOT COPY. This asserted two exact marketing sentences — "The
// Autonomous App Builder." and "Build Shift Board with Hanzo" — and
// neither is on the page. A gate that names a sentence goes red on every rewrite and says
// "the hero is missing" when a line was edited, which is the failure
// e2e/gates/workspace.spec.ts already records and corrected.
const hero = page.getByRole('heading', { level: 1 })
await expect(hero).toBeVisible()
await expect(hero, 'the fold owes a heading that says something').not.toHaveText('')
// The apex is the way in, then the proof and the plans (components/home/Home.tsx).
await expect(page.getByRole('region', { name: /^(Log in|Sign up)$/ }), 'the hero carries the sign-up').toBeVisible()
await expect(page.locator('#plans').getByRole('region', { name: 'Free', exact: true }), 'the plans follow it').toBeVisible({ timeout: 15_000 })
} finally {
await site.close()
}
})
test('5. Swarm Chat (/chat) renders starter prompts, voice button, and composer', async ({ page }) => {
await signIn(page)
// Nothing kept and nothing done yet: the suggestions are Hanzo's three
// starters (@hanzo/build Suggestions; the four fixed openings went with
// @hanzo/app 0.1.83). starters.spec.ts holds what a press does.
await answer(page, '/v1/pref', { prefs: { suggest: { at: new Date().toISOString(), sig: '', rows: [], dismissed: [] } } })
const site = await serveExport()
try {
await page.setViewportSize({ width: 1440, height: 900 })
await page.goto(`${site.url}/chat`)
const main = page.locator('main')
await expect(main.getByRole('textbox', { name: 'Ask anything' }), 'the composer').toBeVisible({ timeout: 20_000 })
await expect(main.getByRole('button', { name: 'Voice mode' }), 'voice, while the draft is empty').toBeVisible()
const starters = main.getByRole('list', { name: 'Suggestions' })
await expect(starters).toBeVisible({ timeout: 20_000 })
for (const title of ['Ask what Hanzo can do for you', 'Build a landing page with Dev', 'Get a cited brief on AI agents'])
await expect(starters.getByRole('button', { name: title, exact: true })).toBeVisible()
} finally {
await site.close()
}
})
})