refactor: replace store-based keyboard action requests with direct dispatcher and simplify agent attention clearing

This commit is contained in:
Mohamed Boudra
2026-03-06 14:49:06 +07:00
parent 885b575d14
commit 5b270444d5
25 changed files with 617 additions and 1355 deletions
+120 -40
View File
@@ -1,9 +1,24 @@
import { test, expect } from './fixtures';
import { createAgent, ensureHostSelected, gotoHome, setWorkingDirectory } from './helpers/app';
import {
createAgent,
createAgentInRepo,
ensureHostSelected,
gotoHome,
openSettings,
setWorkingDirectory,
} from './helpers/app';
import { createTempGitRepo } from './helpers/workspace';
import type { Page } from '@playwright/test';
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import {
buildHostWorkspaceAgentRoute,
buildHostWorkspaceRoute,
} from '@/utils/host-routes';
import {
ensureWorkspaceAgentPaneVisible,
waitForWorkspaceTabsVisible,
} from './helpers/workspace-tabs';
function escapeRegex(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
@@ -110,6 +125,89 @@ async function addFakeMicrophone(page: Page) {
}, { base64Audio, mimeType });
}
async function expectComposerReady(page: Page) {
await expect(page.getByRole('textbox', { name: 'Message agent...' }).first()).toBeEditable();
}
async function expectDictationStarted(page: Page) {
await expect
.poll(async () => page.evaluate(() => (window as any).__mic.active as number))
.toBe(1);
}
async function expectDictationStopped(page: Page) {
await expect
.poll(async () => page.evaluate(() => (window as any).__mic.active as number))
.toBe(0);
}
test('dictation hotkey works on a workspace agent tab', async ({ page }) => {
await addFakeMicrophone(page);
const repo = await createTempGitRepo();
try {
const serverId = process.env.E2E_SERVER_ID;
if (!serverId) {
throw new Error('E2E_SERVER_ID is not set.');
}
await createAgentInRepo(page, {
directory: repo.path,
prompt: 'Respond with exactly: Hello',
});
await page.goto(buildHostWorkspaceRoute(serverId, repo.path));
await waitForWorkspaceTabsVisible(page);
await ensureWorkspaceAgentPaneVisible(page);
await expect(page).toHaveURL(/\/workspace\//);
await expectComposerReady(page);
await page.keyboard.press('Control+d');
await expectDictationStarted(page);
const calls = await page.evaluate(() => (window as any).__mic.getUserMediaCalls as number);
expect(calls).toBe(1);
await page.keyboard.press('Escape');
await expectDictationStopped(page);
} finally {
await repo.cleanup();
}
});
test('dictation hotkey works on a workspace draft tab', async ({ page }) => {
await addFakeMicrophone(page);
const repo = await createTempGitRepo();
try {
const serverId = process.env.E2E_SERVER_ID;
if (!serverId) {
throw new Error('E2E_SERVER_ID is not set.');
}
await createAgentInRepo(page, {
directory: repo.path,
prompt: 'Respond with exactly: Hello',
});
await page.goto(buildHostWorkspaceRoute(serverId, repo.path));
await waitForWorkspaceTabsVisible(page);
await ensureWorkspaceAgentPaneVisible(page);
await page.getByTestId('workspace-new-agent-tab').first().click();
await expectComposerReady(page);
await page.keyboard.press('Control+d');
await expectDictationStarted(page);
const calls = await page.evaluate(() => (window as any).__mic.getUserMediaCalls as number);
expect(calls).toBe(1);
await page.keyboard.press('Escape');
await expectDictationStopped(page);
} finally {
await repo.cleanup();
}
});
test('dictation hotkeys do not trigger on background screens', async ({ page }) => {
await addFakeMicrophone(page);
@@ -119,23 +217,18 @@ test('dictation hotkeys do not trigger on background screens', async ({ page })
await ensureHostSelected(page);
await setWorkingDirectory(page, repo.path);
await createAgent(page, 'Respond with exactly: Hello');
await expect(page).toHaveURL(/\/workspace\//);
await expectComposerReady(page);
await expect(page).toHaveURL(/\/agent\//);
await expect(page.getByRole('textbox', { name: 'Message agent...' })).toBeEditable();
await openSettings(page);
await page.keyboard.press('Control+d');
await page.waitForTimeout(200);
const calls = await page.evaluate(() => (window as any).__mic.getUserMediaCalls as number);
const active = await page.evaluate(() => (window as any).__mic.active as number);
expect(calls).toBe(1);
expect(active).toBe(1);
await page.keyboard.press('Escape');
await expect
.poll(async () => page.evaluate(() => (window as any).__mic.active as number))
.toBe(0);
expect(calls).toBe(0);
expect(active).toBe(0);
} finally {
await repo.cleanup();
}
@@ -150,23 +243,18 @@ test('dictation transcribes fixture via real STT', async ({ page }) => {
await ensureHostSelected(page);
await setWorkingDirectory(page, repo.path);
await createAgent(page, 'Respond with exactly: Hello');
await expect(page).toHaveURL(/\/agent\//);
await expect(page.getByRole('textbox', { name: 'Message agent...' })).toBeEditable();
await expect(page).toHaveURL(/\/workspace\//);
await expectComposerReady(page);
await page.keyboard.press('Control+d');
await expect
.poll(async () => page.evaluate(() => (window as any).__mic.active as number))
.toBe(1);
await expectDictationStarted(page);
const initialCopyMessageCount = await page
.getByRole('button', { name: 'Copy message' })
.count();
await page.keyboard.press('Control+d');
await expect
.poll(async () => page.evaluate(() => (window as any).__mic.active as number))
.toBe(0);
await expectDictationStopped(page);
await expect
.poll(
@@ -189,13 +277,11 @@ test('cancel stops mic even if recorder is already inactive', async ({ page }) =
await setWorkingDirectory(page, repo.path);
await createAgent(page, 'Respond with exactly: Hello');
await expect(page).toHaveURL(/\/agent\//);
await expect(page.getByRole('textbox', { name: 'Message agent...' })).toBeEditable();
await expect(page).toHaveURL(/\/workspace\//);
await expectComposerReady(page);
await page.keyboard.press('Control+d');
await expect
.poll(async () => page.evaluate(() => (window as any).__mic.active as number))
.toBe(1);
await expectDictationStarted(page);
await page.evaluate(() => {
const mic = (window as any).__mic as { lastRecorder: null | { state: string } };
@@ -205,9 +291,7 @@ test('cancel stops mic even if recorder is already inactive', async ({ page }) =
});
await page.keyboard.press('Escape');
await expect
.poll(async () => page.evaluate(() => (window as any).__mic.active as number))
.toBe(0);
await expectDictationStopped(page);
} finally {
await repo.cleanup();
}
@@ -223,35 +307,31 @@ test('dictation confirm+send does not dispatch after navigating away', async ({
await ensureHostSelected(page);
await createAgent(page, 'Respond with exactly: Hello');
await expect(page).toHaveURL(/\/agent($|\/)/);
const match = page.url().match(/\/h\/([^/]+)\/agent\/([^/?#]+)/);
await expect(page).toHaveURL(/\/workspace\//);
const match = page.url().match(/\/h\/([^/]+)\/workspace\/[^?]+(?:\?open=agent%3A|\\?open=agent:)([^/?#&]+)/);
if (!match) {
throw new Error(`Expected /h/:serverId/agent/:agentId URL, got ${page.url()}`);
throw new Error(`Expected workspace agent URL, got ${page.url()}`);
}
const serverId = decodeURIComponent(match[1]!);
const agentId = decodeURIComponent(match[2]!);
await expect(page.getByRole('textbox', { name: 'Message agent...' })).toBeEditable();
await expectComposerReady(page);
const initialCopyMessageCount = await page.getByRole('button', { name: 'Copy message' }).count();
await page.keyboard.press('Control+d');
await expect
.poll(async () => page.evaluate(() => (window as any).__mic.active as number))
.toBe(1);
await expectDictationStarted(page);
await page.keyboard.press('Control+d');
const newAgentButton = page.getByTestId('sidebar-new-agent');
await expect(newAgentButton).toBeVisible();
await newAgentButton.click();
await expect(page).toHaveURL(/\/h\/[^/]+\/agent(\?|$)/);
await expect(page).toHaveURL(/\/h\/[^/]+\/new-agent(\?|$)/);
await page.waitForTimeout(10_000);
const agentEntry = page.getByTestId(`agent-row-${serverId}-${agentId}`).first();
await expect(agentEntry).toBeVisible();
await agentEntry.click();
await page.goto(buildHostWorkspaceAgentRoute(serverId, repo.path, agentId));
await expect(page).toHaveURL(
new RegExp(`/h/${escapeRegex(serverId)}/agent/${escapeRegex(agentId)}(?:\\?|$)`)
new RegExp(`/h/${escapeRegex(serverId)}/workspace/[^?]+\\?open=agent(?:%3A|:)${escapeRegex(agentId)}(?:$|&)`)
);
await expect(page.getByRole('button', { name: 'Copy message' })).toHaveCount(initialCopyMessageCount);
+3 -2
View File
@@ -371,8 +371,9 @@ export const createAgent = async (page: Page, message: string) => {
await input.fill(message);
await input.press('Enter');
// Router updates can be same-document transitions; assert URL state instead of waiting for a commit event.
await expect(page).toHaveURL(/\/(workspace|agent)\//, { timeout: 30000 });
// The composer may remain on the draft screen briefly while the initial run starts,
// so assert the user-visible result instead of forcing one route shape here.
await expect(page).toHaveURL(/\/(workspace|agent|new-agent)(\/|$|\?)/, { timeout: 30000 });
await expect(page.getByText(message, { exact: true }).first()).toBeVisible({
timeout: 30000,
});