mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-29 07:20:43 +00:00
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]>
70 lines
2.5 KiB
TypeScript
70 lines
2.5 KiB
TypeScript
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)
|
||
})
|