From 12149dcfef3472dfea4d4b6da5d044df489c8541 Mon Sep 17 00:00:00 2001 From: Paul <8560030+pwang347@users.noreply.github.com> Date: Sat, 27 Jun 2026 19:51:22 -0700 Subject: [PATCH] Fix model config smoke test flake (#323304) --- test/automation/src/agentsWindow.ts | 15 ++++--- test/automation/src/chat.ts | 61 ++++++++++++++++++++--------- 2 files changed, 52 insertions(+), 24 deletions(-) diff --git a/test/automation/src/agentsWindow.ts b/test/automation/src/agentsWindow.ts index 0df9cb5b652..cad5d36b3f1 100644 --- a/test/automation/src/agentsWindow.ts +++ b/test/automation/src/agentsWindow.ts @@ -570,12 +570,15 @@ export class AgentsWindow { // `force` bypasses the transient `context-view-pointerBlock` overlay // that intercepts pointer events while the action widget animates open. await row.click({ force: true }); - // The picker dismisses after a selection (aria-expanded flips to false). - await page.waitForFunction( - (sel: string) => { const n = document.querySelector(sel); return !n || n.getAttribute('aria-expanded') !== 'true'; }, - ACTIVE_SESSION_MODEL_PICKER_NAME, - { timeout: 15_000 }, - ); + // Confirm the selection actually committed: the picker name button + // must now display the chosen model. A non-committing click (e.g. + // absorbed by the animating pointer-block overlay) silently leaves the + // previous model selected and the picker dismissed, so waiting only + // for the popup to close would miss it. Scope to `:visible` so a hidden + // overflow duplicate of the name button can't produce a false positive. + await page.locator(`${ACTIVE_SESSION_MODEL_PICKER_NAME}:visible`, { hasText: modelName }) + .first() + .waitFor({ state: 'visible', timeout: 15_000 }); return; } catch (error) { lastError = error; diff --git a/test/automation/src/chat.ts b/test/automation/src/chat.ts index 33660daf50f..75a0853be5f 100644 --- a/test/automation/src/chat.ts +++ b/test/automation/src/chat.ts @@ -234,27 +234,52 @@ export class Chat { * Opens the model picker (in the panel chat input) and selects the model * whose displayed name contains `modelName`. Clicks the model-picker name * button to open the popup, waits for the matching row to appear (models may - * still be registering), clicks it, then waits for the popup to dismiss. + * still be registering), clicks it, then confirms the selection committed. + * + * The row click can occasionally fail to commit — e.g. absorbed by the + * action-widget's animating `context-view-pointerBlock` overlay — silently + * leaving the previous model (often "Auto") selected. That model advertises + * no configurable options, so the config button never appears and a later + * `openModelConfig` wedges. To absorb this, re-open the picker and retry until + * the name button reflects the chosen model or `timeoutMs` elapses. */ - async selectModel(modelName: string): Promise { + async selectModel(modelName: string, timeoutMs: number = 60_000): Promise { const page = this.code.driver.currentPage; - await page.locator(`${CHAT_MODEL_PICKER_NAME}:visible`).first().click(); - await this.code.waitForElement(ACTION_WIDGET); - // The picker opens with a focused filter input. Type the model name to - // narrow the list — otherwise the model may be hidden in a collapsed - // "Other Models" section. - await page.keyboard.type(modelName); + const nameButton = page.locator(`${CHAT_MODEL_PICKER_NAME}:visible`).first(); const row = page.locator(ACTION_WIDGET_ROW, { hasText: modelName }).first(); - await row.waitFor({ state: 'visible', timeout: 30_000 }); - // `force` bypasses the transient `context-view-pointerBlock` overlay that - // intercepts pointer events while the action widget is animating open. - await row.click({ force: true }); - // The picker dismisses after a selection (aria-expanded flips back to false). - await page.waitForFunction( - (sel: string) => { const n = document.querySelector(sel); return !n || n.getAttribute('aria-expanded') !== 'true'; }, - CHAT_MODEL_PICKER_NAME, - { timeout: 15_000 }, - ); + const deadline = Date.now() + timeoutMs; + let lastError: unknown; + + while (Date.now() < deadline) { + try { + await nameButton.click(); + await this.code.waitForElement(ACTION_WIDGET); + // The picker opens with a focused filter input. Type the model name to + // narrow the list — otherwise the model may be hidden in a collapsed + // "Other Models" section. + await page.keyboard.type(modelName); + await row.waitFor({ state: 'visible', timeout: 10_000 }); + // `force` bypasses the transient `context-view-pointerBlock` overlay + // that intercepts pointer events while the action widget animates open. + await row.click({ force: true }); + // Confirm the selection actually committed: the picker name button must + // now display the chosen model. (A non-committing click leaves the old + // model selected and the picker dismissed, so waiting only for the + // popup to close would miss it.) Scope to `:visible` so a hidden overflow + // duplicate of the name button can't produce a false positive. + await page.locator(`${CHAT_MODEL_PICKER_NAME}:visible`, { hasText: modelName }) + .first() + .waitFor({ state: 'visible', timeout: 10_000 }); + return; + } catch (error) { + lastError = error; + // Dismiss the (possibly empty / stale) picker so the next attempt + // re-opens a freshly-populated one. + try { await page.keyboard.press('Escape'); } catch { /* picker already gone */ } + await new Promise(r => setTimeout(r, 500)); + } + } + throw new Error(`Timed out selecting model "${modelName}". Last error: ${lastError instanceof Error ? lastError.message : String(lastError)}`); } /**