diff --git a/tools/win-update-e2e/app-driver.mjs b/tools/win-update-e2e/app-driver.mjs index 71492fa804b..d68bb4cec81 100644 --- a/tools/win-update-e2e/app-driver.mjs +++ b/tools/win-update-e2e/app-driver.mjs @@ -275,19 +275,30 @@ async function createWorkspaceFromSeededRepo(page, timeoutMs) { // timeout by another full click attempt. await newWorkspace.click({ timeout: 1 }) } - // Choose the plain-terminal mode (best-effort — if it is already the default - // or the label differs, the create below still produces a worktree). - await tryClickWithKnownOverlayRetry( - page, - page.getByRole('button', { name: 'Blank Terminal' }).first(), - 15_000 - ) + const composer = page.getByRole('dialog', { name: 'Create worktree' }).last() + await composer.waitFor({ state: 'visible', timeout: 15_000 }) + const agentPicker = composer + .locator('[data-agent-combobox-root="true"] button[role="combobox"]') + .first() + const selectedAgent = await agentPicker.innerText({ timeout: 15_000 }).catch(() => '') + if (!selectedAgent.includes('Blank Terminal')) { + if (!(await tryClickWithKnownOverlayRetry(page, agentPicker, 15_000))) { + await agentPicker.click({ timeout: 1 }) + } + const blankTerminalOption = page + .locator('[data-agent-combobox-root="true"] [data-slot="command-item"]:visible') + .filter({ hasText: 'Blank Terminal' }) + .first() + if (!(await tryClickWithKnownOverlayRetry(page, blankTerminalOption, 15_000))) { + await blankTerminalOption.click({ timeout: 1 }) + } + } // Submit. The create button's accessible name carries the shortcut hint // ("Create worktreeCtrl"), so match by prefix; fall back to the documented // Ctrl+Enter shortcut if the button is not directly clickable. const created = await tryClickWithKnownOverlayRetry( page, - page.getByRole('button', { name: /^Create worktree/ }).last(), + composer.getByRole('button', { name: /^Create worktree/ }).last(), 15_000 ) if (!created) { @@ -296,18 +307,21 @@ async function createWorkspaceFromSeededRepo(page, timeoutMs) { } const OVERLAY_DISMISS_LABELS = ['Got it', 'Dismiss setup scripts', 'Dismiss tip', 'Dismiss update'] -const DIALOG_CLOSE_BUTTON = '[data-slot="dialog-content"]:visible [data-slot="dialog-close"]' +const CLI_FEATURE_TIP_TITLE = 'Let agents drive Orca with the Orca CLI' async function dismissKnownOverlays(page) { let acted = false - // Why: a global "Close" role also matches the Windows/Linux title-bar button. - const dialogClose = page.locator(DIALOG_CLOSE_BUTTON).first() - if (await dialogClose.isVisible().catch(() => false)) { - const clicked = await dialogClose - .click({ timeout: 3_000 }) - .then(() => true) - .catch(() => false) - acted ||= clicked + const cliFeatureTip = page.getByRole('dialog', { name: CLI_FEATURE_TIP_TITLE }).first() + if (await cliFeatureTip.isVisible().catch(() => false)) { + // Why: a global "Close" role also matches the Windows/Linux title-bar button. + const dialogClose = cliFeatureTip.locator('[data-slot="dialog-close"]').first() + if (await dialogClose.isVisible().catch(() => false)) { + const clicked = await dialogClose + .click({ timeout: 3_000 }) + .then(() => true) + .catch(() => false) + acted ||= clicked + } } for (const name of OVERLAY_DISMISS_LABELS) { const btn = page.getByRole('button', { name }).first() diff --git a/tools/win-update-e2e/app-driver.test.mjs b/tools/win-update-e2e/app-driver.test.mjs index 2048537dbcb..daaf7017ae5 100644 --- a/tools/win-update-e2e/app-driver.test.mjs +++ b/tools/win-update-e2e/app-driver.test.mjs @@ -23,10 +23,20 @@ describe('dismissOverlays', () => { click: vi.fn().mockRejectedValue(new Error('window closed')) } windowClose.first.mockReturnValue(windowClose) + const featureTipDialog = { + first: vi.fn(), + isVisible: vi.fn().mockResolvedValue(true), + locator: vi.fn().mockReturnValue(dialogClose) + } + featureTipDialog.first.mockReturnValue(featureTipDialog) const page = { - locator: vi.fn().mockReturnValue(dialogClose), - getByRole: vi.fn((_role, { name }) => (name === 'Close' ? windowClose : hiddenButton())), + getByRole: vi.fn((role, { name }) => { + if (role === 'dialog') { + return featureTipDialog + } + return name === 'Close' ? windowClose : hiddenButton() + }), keyboard: { press: vi.fn().mockResolvedValue(undefined) }, waitForTimeout: vi.fn().mockResolvedValue(undefined) } @@ -35,9 +45,9 @@ describe('dismissOverlays', () => { expect(dialogClose.click).toHaveBeenCalledOnce() expect(windowClose.click).not.toHaveBeenCalled() - expect(page.locator).toHaveBeenCalledWith( - '[data-slot="dialog-content"]:visible [data-slot="dialog-close"]' - ) + expect(page.getByRole).toHaveBeenCalledWith('dialog', { + name: 'Let agents drive Orca with the Orca CLI' + }) }) it('keeps overlay retries within the caller timeout budget', async () => { @@ -53,9 +63,12 @@ describe('dismissOverlays', () => { newWorkspace.first.mockReturnValue(newWorkspace) const page = { locator: vi.fn().mockImplementation(() => hiddenButton()), - getByRole: vi.fn((_role, { name }) => - name === 'New workspace' ? newWorkspace : hiddenButton() - ), + getByRole: vi.fn((role, { name }) => { + if (role === 'dialog') { + return hiddenButton() + } + return name === 'New workspace' ? newWorkspace : hiddenButton() + }), keyboard: { press: vi.fn().mockResolvedValue(undefined) }, isClosed: vi.fn().mockReturnValue(false) } @@ -71,4 +84,71 @@ describe('dismissOverlays', () => { vi.useRealTimers() } }) + + it('submits within the composer without reopening the selected Blank Terminal picker', async () => { + const newWorkspace = { + first: vi.fn(), + click: vi.fn().mockResolvedValue(undefined) + } + newWorkspace.first.mockReturnValue(newWorkspace) + const agentPicker = { + first: vi.fn(), + innerText: vi.fn().mockResolvedValue('Blank Terminal'), + click: vi.fn() + } + agentPicker.first.mockReturnValue(agentPicker) + const createWorktree = { + last: vi.fn(), + click: vi + .fn() + .mockRejectedValueOnce(new Error('submit was briefly intercepted')) + .mockResolvedValueOnce(undefined) + } + createWorktree.last.mockReturnValue(createWorktree) + const composer = { + last: vi.fn(), + waitFor: vi.fn().mockResolvedValue(undefined), + locator: vi.fn().mockReturnValue(agentPicker), + getByRole: vi.fn().mockReturnValue(createWorktree) + } + composer.last.mockReturnValue(composer) + const xterm = { + first: vi.fn(), + waitFor: vi.fn().mockResolvedValue(undefined) + } + xterm.first.mockReturnValue(xterm) + const terminalSurface = { + first: vi.fn(), + isVisible: vi.fn().mockResolvedValue(false), + waitFor: vi.fn().mockResolvedValue(undefined), + locator: vi.fn().mockReturnValue(xterm) + } + terminalSurface.first.mockReturnValue(terminalSurface) + const unintendedDialogClose = { + first: vi.fn(), + isVisible: vi.fn().mockResolvedValue(true), + click: vi.fn().mockResolvedValue(undefined) + } + unintendedDialogClose.first.mockReturnValue(unintendedDialogClose) + const page = { + locator: vi.fn((selector) => + selector.includes('dialog-close') ? unintendedDialogClose : terminalSurface + ), + getByRole: vi.fn((role, { name }) => { + if (role === 'dialog') { + return name === 'Create worktree' ? composer : hiddenButton() + } + return name === 'New workspace' ? newWorkspace : hiddenButton() + }), + keyboard: { press: vi.fn().mockResolvedValue(undefined) }, + isClosed: vi.fn().mockReturnValue(false) + } + + await ensureTerminal(page) + + expect(agentPicker.click).not.toHaveBeenCalled() + expect(composer.getByRole).toHaveBeenCalledWith('button', { name: /^Create worktree/ }) + expect(unintendedDialogClose.click).not.toHaveBeenCalled() + expect(createWorktree.click).toHaveBeenCalledTimes(2) + }) })