Files
paseo/packages/app/e2e/support/helpers/sidebar.ts
T
Mohamed BoudraandGitHub 5a30affd7f Skip unrelated required checks without losing their names (#2542)
* fix(ci): skip unrelated required checks cleanly

Matrix jobs had to start no-op runners to preserve interpolated required-check names. Static named jobs let GitHub report genuine skipped conclusions while keeping those names stable.

* fix(ci): gate changes to install patching script

* fix(ci): route cross-package test contracts narrowly

* fix(ci): keep routed contracts merge-blocking

* fix(server): exclude nested test utilities from builds

* fix(ci): cover app-owned native and browser domains

* refactor(ci): align tests with runtime domains

Own browser specs, desktop specs, and Electron-only app code by stable directories so PR routing does not depend on filename conventions. Keep desktop integration coverage inside the required desktop check.

* fix(ci): keep routing and desktop suites isolated

The routing regression must run before dependency installation, and desktop Vitest must not collect Playwright-owned specs.

* fix(ci): isolate desktop e2e runtime

* perf(test): isolate Playwright workers

* perf(test): parallelize Playwright within specs

* fix(test): keep Playwright file scheduling

* fix(test): share dynamic Metro fixture

* fix(test): preserve own-host restart fixtures

* fix(ci): preserve routed contracts after rebase

* Stabilize Playwright wheel-scroll assertions
2026-08-02 00:56:05 +08:00

84 lines
3.5 KiB
TypeScript

import { expect, type Page } from "@playwright/test";
import { getServerId } from "./server-id";
export async function selectWorkspaceInSidebar(page: Page, workspaceId: string): Promise<void> {
const row = page.getByTestId(`sidebar-workspace-row-${getServerId()}:${workspaceId}`);
await expect(row).toBeVisible({ timeout: 30_000 });
await row.click();
}
async function openWorkspaceSidebarKebab(page: Page, workspaceId: string) {
const serverId = getServerId();
const row = page.getByTestId(`sidebar-workspace-row-${serverId}:${workspaceId}`);
await expect(row).toBeVisible({ timeout: 30_000 });
await row.hover();
const kebab = page.getByTestId(`sidebar-workspace-kebab-${serverId}:${workspaceId}`);
await expect(kebab).toBeVisible({ timeout: 10_000 });
await kebab.click();
return serverId;
}
export async function expectWorkspaceListed(page: Page, name: string): Promise<void> {
await expect(
page.locator('[data-testid^="sidebar-workspace-row-"]').filter({ hasText: name }).first(),
).toBeVisible({ timeout: 30_000 });
}
// The workspace row kebab and its menu items carry no web ARIA role, so the sidebar
// suite addresses them by the stable test ids the app assigns per workspace — the same
// convention the rename flow uses. The kebab only reveals on hover.
export async function clickArchiveWorkspaceMenuItem(
page: Page,
workspaceId: string,
): Promise<void> {
const serverId = await openWorkspaceSidebarKebab(page, workspaceId);
const archiveItem = page.getByTestId(`sidebar-workspace-menu-archive-${serverId}:${workspaceId}`);
await expect(archiveItem).toBeVisible({ timeout: 10_000 });
await archiveItem.click();
}
export async function pinWorkspaceFromSidebar(page: Page, workspaceId: string): Promise<void> {
const serverId = await openWorkspaceSidebarKebab(page, workspaceId);
const pinItem = page.getByTestId(`sidebar-workspace-menu-pin-${serverId}:${workspaceId}`);
await expect(pinItem).toBeVisible({ timeout: 10_000 });
await pinItem.click();
}
export async function archiveWorkspaceFromSidebar(page: Page, workspaceId: string): Promise<void> {
// A clean workspace archives with no prompt. Managed worktree backing may raise
// a browser confirm for unsynced work, so accept it when present.
page.once("dialog", (dialog) => void dialog.accept());
await clickArchiveWorkspaceMenuItem(page, workspaceId);
}
export async function expectWorkspaceAbsentFromSidebar(
page: Page,
workspaceId: string,
): Promise<void> {
await expect(
page.getByTestId(`sidebar-workspace-row-${getServerId()}:${workspaceId}`),
).toHaveCount(0, { timeout: 30_000 });
}
export async function openMobileAgentSidebar(page: Page): Promise<void> {
await page.getByRole("button", { name: "Open menu" }).click();
}
export async function closeMobileAgentSidebar(page: Page): Promise<void> {
const closeButton = page.getByTestId("sidebar-close");
await expect(closeButton).toBeInViewport({ ratio: 1, timeout: 5_000 });
await closeButton.click();
}
// The mobile sidebar panel animates via translateX. Waiting for its header to be fully visible
// prevents a close click from targeting a button while the panel is still moving.
export async function expectMobileAgentSidebarVisible(page: Page): Promise<void> {
await expect(page.getByTestId("sidebar-sessions")).toBeInViewport({ ratio: 1, timeout: 5_000 });
}
export async function expectMobileAgentSidebarHidden(page: Page): Promise<void> {
await expect(page.getByTestId("sidebar-sessions")).not.toBeInViewport({ timeout: 5_000 });
}