Files
paseo/packages/app/e2e/new-workspace.spec.ts
T
Mohamed Boudra 95ea02a87f fix: resolve 6 remaining Playwright e2e test failures on CI
- archive-tab: replace waitForFunction with waitForURL for ?open= intent
  consumption, increase test timeout to 300s for slow rootNavigationState
  init on CI
- launcher-tab: use Control+t on Linux instead of Meta+t to match app's
  keyboard shortcut definitions for non-Mac platforms
- new-workspace: add waitForSidebarHydration to wait for daemon
  connection before checking sidebar rows, add re-navigation fallback
  if app redirects during hydration, increase timeouts
- setup-streaming: use waitForTerminalContent (xterm buffer API) instead
  of fragile .xterm-rows CSS text matching, wait for terminal attachment
  before checking content
2026-04-14 10:54:38 +07:00

185 lines
6.2 KiB
TypeScript

import { buildHostWorkspaceRoute } from "@/utils/host-routes";
import { expect, test } from "./fixtures";
import {
archiveWorkspaceFromDaemon,
archiveLocalWorkspaceFromDaemon,
assertNewWorkspaceSidebarAndHeader,
clickNewWorkspaceButton,
connectNewWorkspaceDaemonClient,
createWorktreeViaDaemon,
openProjectViaDaemon,
} from "./helpers/new-workspace";
import { createTempGitRepo } from "./helpers/workspace";
import {
expectWorkspaceHeader,
switchWorkspaceViaSidebar,
waitForSidebarHydration,
waitForWorkspaceInSidebar,
workspaceLabelFromPath,
} from "./helpers/workspace-ui";
test.describe("New workspace flow", () => {
let client: Awaited<ReturnType<typeof connectNewWorkspaceDaemonClient>>;
const localWorkspaceIds = new Set<string>();
const createdWorktreeIds = new Set<string>();
test.describe.configure({ timeout: 240_000 });
test.beforeEach(async () => {
client = await connectNewWorkspaceDaemonClient();
});
test.afterEach(async () => {
if (client) {
for (const workspaceId of createdWorktreeIds) {
await archiveWorkspaceFromDaemon(client, workspaceId).catch(() => undefined);
}
for (const workspaceId of localWorkspaceIds) {
await archiveLocalWorkspaceFromDaemon(client, workspaceId).catch(() => undefined);
}
}
createdWorktreeIds.clear();
localWorkspaceIds.clear();
await client?.close().catch(() => undefined);
});
test("sidebar workspace navigation updates URL and header", async ({ page }) => {
const serverId = process.env.E2E_SERVER_ID;
if (!serverId) {
throw new Error("E2E_SERVER_ID is not set.");
}
const firstRepo = await createTempGitRepo("workspace-nav-a-");
const secondRepo = await createTempGitRepo("workspace-nav-b-");
try {
const firstWorkspace = await openProjectViaDaemon(client, firstRepo.path);
const secondWorkspace = await openProjectViaDaemon(client, secondRepo.path);
localWorkspaceIds.add(firstWorkspace.workspaceId);
localWorkspaceIds.add(secondWorkspace.workspaceId);
await page.goto(buildHostWorkspaceRoute(serverId, firstWorkspace.workspaceId));
await waitForSidebarHydration(page);
// The app may redirect to a different workspace before hydration completes.
// Re-navigate if we ended up on the wrong workspace.
const currentUrl = page.url();
const expectedUrl = buildHostWorkspaceRoute(serverId, firstWorkspace.workspaceId);
if (!currentUrl.includes(expectedUrl)) {
await page.goto(expectedUrl);
}
await waitForWorkspaceInSidebar(page, {
serverId,
workspaceId: firstWorkspace.workspaceId,
});
await expectWorkspaceHeader(page, {
title: firstWorkspace.workspaceName,
subtitle: firstWorkspace.projectDisplayName,
});
await switchWorkspaceViaSidebar({
page,
serverId,
targetWorkspacePath: secondWorkspace.workspaceId,
});
await waitForWorkspaceInSidebar(page, {
serverId,
workspaceId: secondWorkspace.workspaceId,
});
await expectWorkspaceHeader(page, {
title: secondWorkspace.workspaceName,
subtitle: secondWorkspace.projectDisplayName,
});
await switchWorkspaceViaSidebar({
page,
serverId,
targetWorkspacePath: firstWorkspace.workspaceId,
});
await expectWorkspaceHeader(page, {
title: firstWorkspace.workspaceName,
subtitle: firstWorkspace.projectDisplayName,
});
} finally {
await secondRepo.cleanup();
await firstRepo.cleanup();
}
});
test("clicking new workspace redirects, renders header, shows sidebar row, and keeps one draft tab", async ({
page,
}) => {
const serverId = process.env.E2E_SERVER_ID;
if (!serverId) {
throw new Error("E2E_SERVER_ID is not set.");
}
const tempRepo = await createTempGitRepo("new-workspace-");
try {
const openedProject = await openProjectViaDaemon(client, tempRepo.path);
localWorkspaceIds.add(openedProject.workspaceId);
await page.goto(buildHostWorkspaceRoute(serverId, openedProject.workspaceId));
await waitForSidebarHydration(page);
// Re-navigate if the app redirected before hydration completed.
const currentUrl = page.url();
const expectedUrl = buildHostWorkspaceRoute(serverId, openedProject.workspaceId);
if (!currentUrl.includes(expectedUrl)) {
await page.goto(expectedUrl);
}
await waitForWorkspaceInSidebar(page, {
serverId,
workspaceId: openedProject.workspaceId,
});
await expectWorkspaceHeader(page, {
title: openedProject.workspaceName,
subtitle: openedProject.projectDisplayName,
});
await clickNewWorkspaceButton(page, {
projectKey: openedProject.projectKey,
projectDisplayName: openedProject.projectDisplayName,
});
const createdWorkspace = await assertNewWorkspaceSidebarAndHeader(page, {
serverId,
previousWorkspaceId: openedProject.workspaceId,
projectDisplayName: openedProject.projectDisplayName,
});
createdWorktreeIds.add(createdWorkspace.workspaceId);
expect(createdWorkspace.workspaceId).not.toBe(openedProject.workspaceId);
await expect(page).toHaveURL(
buildHostWorkspaceRoute(serverId, createdWorkspace.workspaceId),
{
timeout: 30_000,
},
);
const createdWorkspaceRow = page.getByTestId(
`sidebar-workspace-row-${serverId}:${createdWorkspace.workspaceId}`,
);
await expect(createdWorkspaceRow).toBeVisible({ timeout: 30_000 });
await expectWorkspaceHeader(page, {
title: workspaceLabelFromPath(createdWorkspace.workspaceId),
subtitle: openedProject.projectDisplayName,
});
const draftTabs = page.locator('[data-testid^="workspace-tab-"]').filter({
has: page.getByText("New Agent", { exact: true }),
});
await expect(draftTabs).toHaveCount(1, { timeout: 30_000 });
const composer = page.getByRole("textbox", { name: "Message agent..." });
await expect(composer).toBeEditable({ timeout: 30_000 });
} finally {
await tempRepo.cleanup();
}
});
});