/* * gate-phase2.mjs * Zyquo Cloud Web * * Author: Simon-Pierre Boucher * Mail: contact@spboucher.ai * * Phase 2 gate check: drives a real Chromium browser against the dev server, * seeds a provider key from .keys.local.json into localStorage, runs the bare * stream test view, and asserts the completion arrived TOKEN-BY-TOKEN * (multiple incremental output snapshots) with working Stop cancellation. * Usage: node scripts/gate-phase2.mjs [provider] [baseURL] */ import { chromium } from 'playwright' import { readFileSync } from 'node:fs' import { fileURLToPath } from 'node:url' import { dirname, join } from 'node:path' const root = dirname(dirname(fileURLToPath(import.meta.url))) const keys = JSON.parse(readFileSync(join(root, '.keys.local.json'), 'utf8')) const provider = process.argv[2] ?? 'openai' const baseURL = process.argv[3] ?? 'http://localhost:5173' const browser = await chromium.launch() const page = await browser.newPage() await page.goto(baseURL) await page.evaluate( ([p, k]) => localStorage.setItem('zyquo.cloud.web.keys', JSON.stringify({ [p]: k })), [provider, keys[provider]] ) await page.goto(`${baseURL}/#stream-test`) await page.reload() await page.selectOption('[data-testid="provider"]', provider) await page.fill('[data-testid="prompt"]', 'Write a 150-word paragraph about clouds.') await page.click('[data-testid="run"]') // Sample the output as it streams to prove token-by-token rendering. const snapshots = [] for (let i = 0; i < 600; i++) { const status = (await page.textContent('[data-testid="status"]'))?.replace('status: ', '') ?? '' const output = await page.textContent('[data-testid="output"]') if (output && (snapshots.length === 0 || output !== snapshots[snapshots.length - 1])) { snapshots.push(output) } if ( status.startsWith('finished') || /invalidAPIKey|badRequest|serverError|networkError|rateLimited|invalidResponse/.test(status) ) { break } await page.waitForTimeout(50) } const status = await page.textContent('[data-testid="status"]') const tokens = await page.textContent('[data-testid="tokens"]') const finalOutput = snapshots[snapshots.length - 1] ?? '' console.log(`provider: ${provider}`) console.log(`status: ${status}`) console.log(`usage: ${tokens}`) console.log(`snapshots: ${snapshots.length} incremental output states`) console.log(`final out: ${finalOutput.slice(0, 120).replace(/\n/g, ' / ')}`) // Cancellation check: start a second stream and stop it immediately. await page.click('[data-testid="run"]') await page.waitForTimeout(700) await page.click('[data-testid="stop"]') await page.waitForTimeout(700) const stopStatus = await page.textContent('[data-testid="status"]') console.log(`stop test: ${stopStatus}`) await browser.close() const streamedIncrementally = snapshots.length >= 3 const finished = status?.includes('finished') const stopped = stopStatus?.includes('cancelled') || stopStatus?.includes('stopped') if (streamedIncrementally && finished && stopped) { console.log('GATE PASS: real token-by-token streaming + cancellation in a real browser') } else { console.error('GATE FAIL') process.exit(1) }