fix: stabilize workspace e2e routing and cli helper export

This commit is contained in:
Mohamed Boudra
2026-04-14 17:01:38 +07:00
parent 0f0efa1707
commit d55209b81e
5 changed files with 37 additions and 22 deletions
+7 -7
View File
@@ -83,14 +83,14 @@ export async function connectTerminalClient(): Promise<TerminalPerfDaemonClient>
return client;
}
export function buildTerminalWorkspaceUrl(cwd: string, terminalId: string): string {
export function buildTerminalWorkspaceUrl(workspaceId: string, terminalId: string): string {
const serverId = getServerId();
const route = buildHostWorkspaceRoute(serverId, cwd);
const route = buildHostWorkspaceRoute(serverId, workspaceId);
return `${route}?open=${encodeURIComponent(`terminal:${terminalId}`)}`;
}
function buildWorkspaceUrl(cwd: string): string {
return buildHostWorkspaceRoute(getServerId(), cwd);
function buildWorkspaceUrl(workspaceId: string): string {
return buildHostWorkspaceRoute(getServerId(), workspaceId);
}
export async function getTerminalBufferText(page: Page): Promise<string> {
@@ -129,19 +129,19 @@ export async function waitForTerminalContent(
export async function navigateToTerminal(
page: Page,
input: { cwd: string; terminalId: string },
input: { workspaceId: string; terminalId: string },
): Promise<void> {
// Boot the app at the workspace route directly.
// The fixtures.ts beforeEach addInitScript seeds localStorage on every navigation,
// so the daemon registry is already configured when the app starts.
const workspaceRoute = buildTerminalWorkspaceUrl(input.cwd, input.terminalId);
const workspaceRoute = buildTerminalWorkspaceUrl(input.workspaceId, input.terminalId);
await page.goto(workspaceRoute);
// The workspace layout consumes `?open=...`, returns null during the effect,
// then replaces the URL with the clean workspace route after preparing the tab.
// On CI, Expo Router's rootNavigationState may take time to initialize,
// so we allow a generous timeout here.
const cleanWorkspaceRoute = buildWorkspaceUrl(input.cwd);
const cleanWorkspaceRoute = buildWorkspaceUrl(input.workspaceId);
await page.waitForURL(
(url) => url.pathname === cleanWorkspaceRoute && !url.searchParams.has("open"),
{ timeout: 30_000 },
@@ -20,6 +20,7 @@ const KEYSTROKE_P95_BUDGET_MS = 150;
test.describe("Terminal wire performance", () => {
let client: TerminalPerfDaemonClient;
let tempRepo: { path: string; cleanup: () => Promise<void> };
let workspaceId: string;
test.beforeAll(async () => {
tempRepo = await createTempGitRepo("perf-");
@@ -27,6 +28,7 @@ test.describe("Terminal wire performance", () => {
// Seed the workspace in the daemon so the app can resolve the path
const seedResult = await client.openProject(tempRepo.path);
if (!seedResult.workspace) throw new Error(seedResult.error ?? "Failed to seed workspace");
workspaceId = seedResult.workspace.id;
});
test.afterAll(async () => {
@@ -48,7 +50,7 @@ test.describe("Terminal wire performance", () => {
const terminalId = result.terminal.id;
try {
await navigateToTerminal(page, { cwd: tempRepo.path, terminalId });
await navigateToTerminal(page, { workspaceId, terminalId });
await setupDeterministicPrompt(page);
const sentinel = `PERF_DONE_${Date.now()}`;
@@ -107,7 +109,7 @@ test.describe("Terminal wire performance", () => {
const terminalId = result.terminal.id;
try {
await navigateToTerminal(page, { cwd: tempRepo.path, terminalId });
await navigateToTerminal(page, { workspaceId, terminalId });
await setupDeterministicPrompt(page);
// Ensure clean prompt state
+2 -5
View File
@@ -95,15 +95,12 @@ test.describe("Workspace cwd correctness", () => {
if (!workspaceResult.workspace) {
throw new Error(workspaceResult.error ?? `Failed to open project ${worktreePath}`);
}
const workspaceName = workspaceResult.workspace.name;
const workspaceId = String(workspaceResult.workspace.id);
// Use sidebar navigation to avoid Expo Router hydration issues
// with direct URL navigation to the 2nd+ workspace.
await openHomeWithProject(page, repo.path);
const sidebarWorkspace = page.getByRole("button", { name: workspaceName });
await expect(sidebarWorkspace).toBeVisible({ timeout: 30_000 });
await sidebarWorkspace.click();
await waitForTabBar(page);
await navigateToWorkspaceViaSidebar(page, workspaceId);
await clickTerminal(page);
@@ -9,6 +9,24 @@ import {
openHomeWithProject,
} from "./helpers/workspace-setup";
function getServerId(): string {
const serverId = process.env.E2E_SERVER_ID;
if (!serverId) {
throw new Error("E2E_SERVER_ID is not set.");
}
return serverId;
}
async function navigateToWorkspaceViaSidebar(
page: import("@playwright/test").Page,
workspaceId: string,
): Promise<void> {
const row = page.getByTestId(`sidebar-workspace-row-${getServerId()}:${workspaceId}`);
await expect(row).toBeVisible({ timeout: 30_000 });
await row.click();
await waitForTabBar(page);
}
test.describe("Workspace setup runtime authority", () => {
test.describe.configure({ retries: 1 });
@@ -24,6 +42,7 @@ test.describe("Workspace setup runtime authority", () => {
cwd: repo.path,
worktreeSlug: `setup-chat-${Date.now()}`,
});
const workspaceId = String(workspace.id);
const wsInfo = await findWorktreeWorkspaceForProject(client, repo.path);
expect(wsInfo.workspaceDirectory).not.toBe(repo.path);
@@ -31,9 +50,7 @@ test.describe("Workspace setup runtime authority", () => {
// Navigate to the workspace via sidebar
await openHomeWithProject(page, repo.path);
const wsButton = page.getByRole("button", { name: workspace.name });
await expect(wsButton).toBeVisible({ timeout: 30_000 });
await wsButton.click();
await navigateToWorkspaceViaSidebar(page, workspaceId);
await expect(page).toHaveURL(/\/workspace\//, { timeout: 30_000 });
} finally {
await client.close();
@@ -61,16 +78,13 @@ test.describe("Workspace setup runtime authority", () => {
throw new Error(result.error ?? "Failed to create workspace");
}
const workspaceDir = result.workspace.workspaceDirectory;
const workspaceName = result.workspace.name;
const workspaceId = String(result.workspace.id);
// Navigate to the worktree workspace via sidebar click (direct URL
// navigation for freshly created worktree workspaces can race with
// Expo Router hydration, so we use the sidebar which is authoritative).
await openHomeWithProject(page, repo.path);
const sidebarWorkspace = page.getByRole("button", { name: workspaceName });
await expect(sidebarWorkspace).toBeVisible({ timeout: 30_000 });
await sidebarWorkspace.click();
await waitForTabBar(page);
await navigateToWorkspaceViaSidebar(page, workspaceId);
await clickTerminal(page);
+2
View File
@@ -18,6 +18,8 @@ import { parseDuration } from "../../utils/duration.js";
import { collectMultiple } from "../../utils/command-options.js";
import { resolveProviderAndModel } from "../../utils/provider-model.js";
export { resolveProviderAndModel } from "../../utils/provider-model.js";
export function addRunOptions(cmd: Command): Command {
return cmd
.description("Create and start an agent with a task")