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