Files
hanzo.ai/e2e/agent-attached.spec.ts
zeekayandHanzo Dev dff1d5f1e3 A signed-in spec starts past the organization picker
The one seeder in e2e/credential.ts now marks the browser as the bearer's
and chooses the first organization on the token, the way a returning
person's browser already has; the three specs that copied the seeder use
it. The other spec edits say what the site does now: money is at
pay.hanzo.ai, the header has no products door, ledger is the private
repo, a fresh board carries four projects, and the landing composer is
measured after it is scrolled into view.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 03:07:17 -07:00

70 lines
2.5 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { expect, test } from '@playwright/test'
import { credential, signedIn } from './credential'
/**
* Pressing a name opens a chat WITH that name.
*
* The roster used to select a run — a thing the Agents room asks about — and
* the chat pane never read it, so the press did nothing you could see. This
* asserts the three things "attached" means: the room says who is in it, the
* composer addresses them, and the turn on the wire carries their brief.
*/
test('a name in the roster opens a chat with that character', async ({ page }) => {
const TOKEN = credential()
test.skip(!TOKEN, 'no local credential: the roster needs somebody signed in')
await page.addInitScript(signedIn(TOKEN), TOKEN)
// What the turn actually sends, captured before it leaves.
const sent: any[] = []
await page.route('**/v1/agents/chat', async (route) => {
sent.push(JSON.parse(route.request().postData() || '{}'))
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
id: 'x',
choices: [{ message: { role: 'assistant', content: 'ok' } }],
}),
})
})
await page.goto('/work')
await page.waitForLoadState('networkidle')
// THE SIDEBAR IS A SHEET NOW, and it starts away: the app mark is its
// handle. What this spec is about lives inside it, so it opens it first
// rather than asserting against a column that is not on screen.
const handle = page.getByRole('button', { name: 'Open sidebar' })
if (await handle.isVisible().catch(() => false)) await handle.click()
const row = page.getByText('feynman', { exact: true }).first()
await expect(row).toBeVisible({ timeout: 30_000 })
await row.click()
// 1. The room says who is in it.
await expect(page).toHaveURL(/\/chat/)
await expect(page.getByRole('heading', { name: 'feynman' })).toBeVisible({
timeout: 20_000,
})
// 2. The composer addresses them.
const field = page.getByPlaceholder('Message feynman')
await expect(field).toBeVisible()
// 3. The turn carries their brief.
await field.fill('hello')
await field.press('Enter')
await expect.poll(() => sent.length, { timeout: 20_000 }).toBeGreaterThan(0)
const messages = sent[0].messages ?? []
const brief = messages.find((m: any) => m.role === 'system')
expect(brief, 'the persona rides as the thread’s system turn').toBeTruthy()
expect(brief.content.toLowerCase()).toContain('feynman')
// And it is sent, never drawn: no system bubble in the transcript.
await expect(page.getByText(brief.content.slice(0, 40))).toHaveCount(0)
})