From 0f406063f0e814064a519d5ed676937d52cb3f5f Mon Sep 17 00:00:00 2001 From: Codex Agent Date: Fri, 5 Jun 2026 23:35:19 +0800 Subject: [PATCH] test: wait for web probe submit readiness --- scripts/web-live-dom-probe.mjs | 103 ++++++++++++++++++++++++++++++++- 1 file changed, 101 insertions(+), 2 deletions(-) diff --git a/scripts/web-live-dom-probe.mjs b/scripts/web-live-dom-probe.mjs index 9c2fb590..53406c0f 100644 --- a/scripts/web-live-dom-probe.mjs +++ b/scripts/web-live-dom-probe.mjs @@ -103,7 +103,7 @@ export async function runProbe(args) { await page.locator("#workspace").waitFor({ state: "visible", timeout: args.timeoutMs }); await page.locator("#command-input").waitFor({ state: "visible", timeout: args.timeoutMs }); if (args.conversationId) await selectConversationInBrowserSession(page, args, actions); - if (args.freshSession) await maybeClick(page, "#session-create", actions, "fresh-session"); + if (args.freshSession) await createFreshSession(page, args, actions); if (args.message) await submitPrompt(page, args, actions); await waitForMessageCards(page, args, actions); await page.screenshot({ path: screenshotPath, fullPage: true }); @@ -273,14 +273,113 @@ async function maybeClick(page, selector, actions, action) { return false; } +async function createFreshSession(page, args, actions) { + const selector = "#session-create"; + const locator = page.locator(selector).first(); + if (!(await locator.isVisible({ timeout: 3000 }).catch(() => false))) { + actions.push({ action: "fresh-session", selector, skipped: "not-visible" }); + return false; + } + const shellReady = await waitForSessionShellReady(page, args, actions); + const before = await collectSessionState(page); + await locator.click(); + const timeoutMs = Math.min(args.timeoutMs, 30000); + const settled = await page.waitForFunction((initial) => { + const statusText = document.querySelector("#session-status")?.textContent?.trim() ?? null; + const changed = Boolean(statusText && statusText !== initial.statusText && !/加载中|等待 workspace/u.test(statusText)); + const messageCount = document.querySelectorAll(".message-card").length; + const emptyVisible = Boolean(document.querySelector("[data-conversation-empty]")); + const input = document.querySelector("#command-input"); + const send = document.querySelector("#command-send"); + return changed && messageCount === 0 && emptyVisible && input && send && !input.disabled && !send.disabled; + }, before, { timeout: timeoutMs }).catch(() => null); + const after = await collectSessionState(page); + actions.push({ action: "fresh-session", selector, settled: Boolean(settled), timeoutMs, shellReady, before, after }); + if (!settled) throw new Error(`fresh session did not settle: ${JSON.stringify({ before, after, timeoutMs })}`); + return true; +} + +async function waitForSessionShellReady(page, args, actions) { + const timeoutMs = Math.min(args.timeoutMs, 30000); + const before = await collectSessionState(page); + const ready = await page.waitForFunction(() => { + const statusText = document.querySelector("#session-status")?.textContent?.trim() ?? ""; + const workspace = document.querySelector("#workspace"); + const create = document.querySelector("#session-create"); + return Boolean(workspace && create && !create.disabled && statusText && !/加载中/u.test(statusText)); + }, null, { timeout: timeoutMs }).catch(() => null); + const after = await collectSessionState(page); + const result = { action: "session-shell-ready", ready: Boolean(ready), timeoutMs, before, after }; + actions.push(result); + if (!ready) throw new Error(`session shell did not finish initial loading: ${JSON.stringify(result)}`); + return after; +} + +async function collectSessionState(page) { + return page.evaluate(() => { + const activeTab = document.querySelector(".session-tab[aria-selected='true']"); + return { + statusText: document.querySelector("#session-status")?.textContent?.trim() ?? null, + activeTabTitle: activeTab?.getAttribute("title") ?? null, + tabCount: document.querySelectorAll(".session-tab").length, + messageCount: document.querySelectorAll(".message-card").length, + emptyVisible: Boolean(document.querySelector("[data-conversation-empty]")), + command: { + inputDisabled: Boolean(document.querySelector("#command-input")?.disabled), + sendDisabled: Boolean(document.querySelector("#command-send")?.disabled), + sendText: document.querySelector("#command-send")?.textContent?.trim() ?? null, + formTitle: document.querySelector("#command-form")?.getAttribute("title") ?? null + } + }; + }); +} + async function submitPrompt(page, args, actions) { + const readyBefore = await waitForCommandReady(page, args, actions); await page.locator("#command-input").fill(args.message); + await page.waitForFunction((expected) => document.querySelector("#command-input")?.value === expected, args.message, { timeout: Math.min(args.timeoutMs, 5000) }); + const afterFill = await collectCommandState(page); await page.locator("#command-send").click(); - actions.push({ action: "submit-prompt", chars: args.message.length }); + const afterClick = await collectCommandState(page); + actions.push({ action: "submit-prompt", chars: args.message.length, readyBefore, afterFill, afterClick }); await page.waitForTimeout(args.waitAfterSubmitMs); if (args.cancelRunning) await maybeClick(page, ".message-action-cancel", actions, "cancel-running-message"); } +async function waitForCommandReady(page, args, actions) { + const timeoutMs = Math.min(args.timeoutMs, 30000); + const before = await collectCommandState(page); + const ready = await page.waitForFunction(() => { + const input = document.querySelector("#command-input"); + const send = document.querySelector("#command-send"); + return Boolean(input && send && !input.disabled && !send.disabled); + }, null, { timeout: timeoutMs }).catch(() => null); + const after = await collectCommandState(page); + const result = { action: "command-ready", ready: Boolean(ready), timeoutMs, before, after }; + actions.push(result); + if (!ready) throw new Error(`command bar is not ready for submit: ${JSON.stringify(result)}`); + return after; +} + +async function collectCommandState(page) { + return page.evaluate(() => { + const input = document.querySelector("#command-input"); + const send = document.querySelector("#command-send"); + const form = document.querySelector("#command-form"); + return { + inputVisible: Boolean(input), + inputDisabled: input ? Boolean(input.disabled) : null, + inputLength: input && typeof input.value === "string" ? input.value.length : null, + sendVisible: Boolean(send), + sendDisabled: send ? Boolean(send.disabled) : null, + sendText: send?.textContent?.trim() ?? null, + formTitle: form?.getAttribute("title") ?? null, + messageCount: document.querySelectorAll(".message-card").length, + authState: document.body.getAttribute("data-auth-state") + }; + }); +} + async function selectConversationInBrowserSession(page, args, actions) { const result = await page.evaluate(async ({ projectId, conversationId }) => { const summarizeAuthSession = (body) => ({