From 4e9402a441b2ccfb8a3af69690f601469867fd22 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Sun, 8 Nov 2026 22:48:02 -0800 Subject: [PATCH] Use the history page's itemId in the 4a harness and name what a visible-state wait timed out on --- .../native-chat/native-chat-4a-ops-reads.ts | 2 +- .../native-chat/native-chat-4a-probes.ts | 34 ++++++++++++++--- .../native-chat/native-chat-4a-session.ts | 37 ++++++++++++------- 3 files changed, 52 insertions(+), 21 deletions(-) diff --git a/config/scripts/performance/native-chat/native-chat-4a-ops-reads.ts b/config/scripts/performance/native-chat/native-chat-4a-ops-reads.ts index 47b3bb96bb3..8846cdf7b38 100644 --- a/config/scripts/performance/native-chat/native-chat-4a-ops-reads.ts +++ b/config/scripts/performance/native-chat/native-chat-4a-ops-reads.ts @@ -94,7 +94,7 @@ export async function historyRows(ctx: RunContext): Promise { continue } await sampled(ctx, row, n, () => - read(ctx, 'agentSession.item', { sessionId, itemId: entry.item.id }) + read(ctx, 'agentSession.item', { sessionId, itemId: entry.item.itemId }) ) } } diff --git a/config/scripts/performance/native-chat/native-chat-4a-probes.ts b/config/scripts/performance/native-chat/native-chat-4a-probes.ts index cff44fe4ca9..48bdd599775 100644 --- a/config/scripts/performance/native-chat/native-chat-4a-probes.ts +++ b/config/scripts/performance/native-chat/native-chat-4a-probes.ts @@ -94,15 +94,37 @@ export async function markerSeenAt( marker: string, timeoutMs = 120_000 ): Promise { - const handle = await page.waitForFunction( - (marker) => - (window as unknown as { __4a: { seen: Map } }).__4a.seen.get(marker) ?? false, - marker, - { timeout: timeoutMs, polling: 16 } - ) + const handle = await page + .waitForFunction( + (marker) => + (window as unknown as { __4a: { seen: Map } }).__4a.seen.get(marker) ?? + false, + marker, + { timeout: timeoutMs, polling: 16 } + ) + .catch(async (error: unknown) => { + throw new Error( + `${marker} not visible in ${timeoutMs} ms (${await visibleChatState(page)})`, + { + cause: error + } + ) + }) return (await handle.jsonValue()) as number } +/** One line on the chat the user sees, for timeout messages. */ +export function visibleChatState(page: Page): Promise { + return page + .evaluate(() => { + const roots = [...document.querySelectorAll('[data-native-chat-root="true"]')] + const root = roots.find((candidate) => candidate.checkVisibility()) + const text = root?.textContent ?? '' + return `${roots.length} roots, visible=${Boolean(root)}, working=${root?.getAttribute('data-native-chat-working')}, tail=${JSON.stringify(text.slice(-160))}` + }) + .catch((error: unknown) => `state unreadable: ${String(error)}`) +} + /** Gaps between visible transcript updates inside [from, to], the live-update cadence a reader sees. */ export async function mutationGaps(page: Page, from: number, to: number): Promise { return page.evaluate( diff --git a/config/scripts/performance/native-chat/native-chat-4a-session.ts b/config/scripts/performance/native-chat/native-chat-4a-session.ts index 522a8741524..c886b2d7bb2 100644 --- a/config/scripts/performance/native-chat/native-chat-4a-session.ts +++ b/config/scripts/performance/native-chat/native-chat-4a-session.ts @@ -4,9 +4,9 @@ import type {} from '../../../../tests/e2e/helpers/runtime-types' import { randomUUID } from 'node:crypto' import type { Page } from '@stablyai/playwright-test' import { operationId, type Arm } from './native-chat-4a-arm' -import { rpc } from './native-chat-4a-probes' +import { rpc, visibleChatState } from './native-chat-4a-probes' -type HistoryItem = { id: string; body?: { kind?: string; role?: string; state?: string } } +type HistoryItem = { itemId: string; body?: { kind?: string; role?: string; state?: string } } export type HistoryPage = { epoch: string fence?: number @@ -155,7 +155,7 @@ export async function userItemIds(page: Page, sessionId: string, limit = 200): P ids.unshift( ...read.page.items .filter((item) => item.body?.kind === 'message' && item.body.role === 'user') - .map((item) => item.id) + .map((item) => item.itemId) ) if (!read.page.hasOlder || ids.length >= limit) { return ids.slice(-limit) @@ -212,17 +212,26 @@ export async function waitForWorking( working: boolean, timeoutMs = 120_000 ): Promise { - const handle = await page.waitForFunction( - (working) => { - const root = [...document.querySelectorAll('[data-native-chat-root="true"]')].find( - (candidate) => candidate.checkVisibility() + const handle = await page + .waitForFunction( + (working) => { + const root = [...document.querySelectorAll('[data-native-chat-root="true"]')].find( + (candidate) => candidate.checkVisibility() + ) + return root?.getAttribute('data-native-chat-working') === String(working) + ? performance.now() + : false + }, + working, + { timeout: timeoutMs, polling: 16 } + ) + .catch(async (error: unknown) => { + throw new Error( + `working=${working} not shown in ${timeoutMs} ms (${await visibleChatState(page)})`, + { + cause: error + } ) - return root?.getAttribute('data-native-chat-working') === String(working) - ? performance.now() - : false - }, - working, - { timeout: timeoutMs, polling: 16 } - ) + }) return (await handle.jsonValue()) as number }