SPB Git

Repository Public

codex-helpers.ts 3.4 KB 53 lines typescript

1import { expect, type Page } from '@playwright/test';2import { SEED, totp } from './helpers';34/** The Codex instance of the suite: same static export, mock started with MOCK_AGENT=codex (playwright.config.ts). */5export const CODEX_MOCK = `http://127.0.0.1:${process.env.CODEX_MOCK_PORT ?? 8622}/api/v1`;6export const CLAUDE_WEB = `http://127.0.0.1:${process.env.WEB_PORT ?? 3621}`;78export async function resetCodex(page: Page, seed = true): Promise<void> {9  await page.request.post(`${CODEX_MOCK}/__mock/reset`, { data: { seed } });10}11export async function codexLogin(page: Page): Promise<void> {12  const r = await page.request.post('/api/v1/auth/login', { data: { username: SEED.username, password: SEED.password, totp: totp(SEED.totpSecret) } });13  expect(r.ok()).toBeTruthy();14}15async function post<T>(page: Page, path: string, data: unknown): Promise<T> {16  const csrf = (await (await page.request.get('/api/v1/auth/status')).json()).csrfToken as string;17  const r = await page.request.post(`/api/v1${path}`, { data, headers: { 'X-CSRF-Token': csrf } });18  expect(r.ok(), `${path} → ${r.status()}`).toBeTruthy();19  return (await r.json()) as T;20}21/** Same working state as shot-helpers `seed`, on the Codex mock. */22export async function seedCodex(page: Page): Promise<{ shell: string; agent: string; agentTitle: string; pid: string }> {23  await resetCodex(page);24  await codexLogin(page);25  const pid = ((await (await page.request.get('/api/v1/projects')).json()) as { id: string; name: string }[]).find((p) => p.name === 'demo')!.id;26  const shell = await post<{ id: string }>(page, '/sessions', { projectId: pid, kind: 'shell', title: 'api server' });27  const agent = await post<{ id: string; title: string }>(page, '/sessions', { projectId: pid, kind: 'claude' }); // wire kind: "the instance's agent"28  await post(page, '/sessions', { projectId: pid, kind: 'shell', title: 'db migrations' });29  await page.request.post(`${CODEX_MOCK}/__mock/session-state`, { data: { id: agent.id, state: 'WAITING_FOR_INPUT', waitingReason: 'Codex finished its turn and is waiting for you' } });30  await post(page, '/tasks', { projectId: pid, title: 'Unit tests', command: 'echo "42 passed"; exit 0', idempotent: true });31  await post(page, '/tasks', { projectId: pid, title: 'Build release', command: 'for i in 1 2 3 4 5 6 7 8; do echo "compiling module $i"; sleep 20; done' });32  return { shell: shell.id, agent: agent.id, agentTitle: agent.title, pid };33}34/** Computed value of a design token on <html>. */35export const token = (page: Page, name: string) => page.evaluate((n) => getComputedStyle(document.documentElement).getPropertyValue(n).trim().toLowerCase(), name);36/** No user-visible "Claude" anywhere in the rendered page (text, titles, aria-labels, placeholders). */37export async function expectNoClaude(page: Page, where: string): Promise<void> {38  const hits = await page.evaluate(() => {39    const out: string[] = [];40    const re = /claude/i;41    if (re.test(document.title)) out.push(`title: ${document.title}`);42    const visible = document.body.innerText;43    if (re.test(visible)) out.push(`text: ${visible.match(/.{0,40}claude.{0,40}/i)?.[0] ?? ''}`);44    for (const el of document.querySelectorAll('[aria-label],[title],[placeholder]'))45      for (const a of ['aria-label', 'title', 'placeholder']) {46        const v = el.getAttribute(a);47        if (v && re.test(v)) out.push(`${a}: ${v}`);48      }49    return out;50  });51  expect(hits, `user-visible "Claude" on ${where}`).toEqual([]);52}53
Files main
Menu

Theme