* 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
137 lines
5.1 KiB
TypeScript
137 lines
5.1 KiB
TypeScript
import { expect, type Locator, type Page } from "@playwright/test";
|
|
import { gotoAppShell } from "./app";
|
|
import { addConnectedHostsAndReload, waitForConnectedHost } from "./hosts";
|
|
import { openProjectSettings } from "./project-settings";
|
|
import { selectSettingsHost } from "./settings";
|
|
import { waitForSidebarHydration } from "./workspace-ui";
|
|
import { buildProjectsSettingsRoute } from "@/utils/host-routes";
|
|
|
|
const PROJECT_VISIBILITY_TIMEOUT = 30_000;
|
|
|
|
function projectGroup(page: Page, projectName: string): Locator {
|
|
return page.getByRole("group", { name: projectName, exact: true });
|
|
}
|
|
|
|
function projectGroupWithWorkspace(
|
|
page: Page,
|
|
projectName: string,
|
|
workspaceName: string,
|
|
): Locator {
|
|
return projectGroup(page, projectName).filter({ hasText: workspaceName });
|
|
}
|
|
|
|
export async function openProjectDirectory(page: Page): Promise<void> {
|
|
await gotoAppShell(page);
|
|
await waitForSidebarHydration(page);
|
|
}
|
|
|
|
export async function openProjectDirectoryWithHosts(
|
|
page: Page,
|
|
input: {
|
|
hosts: Array<{ serverId: string; label: string; port: number }>;
|
|
primaryLabel?: string;
|
|
},
|
|
): Promise<void> {
|
|
await gotoAppShell(page);
|
|
await addConnectedHostsAndReload(page, input.hosts, { primaryLabel: input.primaryLabel });
|
|
for (const host of input.hosts) {
|
|
await waitForConnectedHost(page, {
|
|
serverId: host.serverId,
|
|
endpoint: `localhost:${host.port}`,
|
|
});
|
|
}
|
|
await waitForSidebarHydration(page);
|
|
}
|
|
|
|
export async function expectProjectContainsWorkspaces(
|
|
page: Page,
|
|
input: { projectName: string; workspaceNames: string[] },
|
|
): Promise<void> {
|
|
const group = projectGroup(page, input.projectName);
|
|
await expect(group).toHaveCount(1, { timeout: PROJECT_VISIBILITY_TIMEOUT });
|
|
for (const workspaceName of input.workspaceNames) {
|
|
await expect(group.getByRole("button").filter({ hasText: workspaceName })).toBeVisible({
|
|
timeout: PROJECT_VISIBILITY_TIMEOUT,
|
|
});
|
|
}
|
|
}
|
|
|
|
export async function expectSeparateProjects(
|
|
page: Page,
|
|
projects: Array<{ projectName: string; workspaceName: string }>,
|
|
): Promise<void> {
|
|
for (const project of projects) {
|
|
await expect(
|
|
projectGroupWithWorkspace(page, project.projectName, project.workspaceName),
|
|
).toHaveCount(1, { timeout: PROJECT_VISIBILITY_TIMEOUT });
|
|
}
|
|
}
|
|
|
|
export async function beginWorkspaceFromProject(page: Page, projectName: string): Promise<void> {
|
|
const group = projectGroup(page, projectName);
|
|
await expect(group).toBeVisible({ timeout: PROJECT_VISIBILITY_TIMEOUT });
|
|
await group.hover();
|
|
await group.getByLabel(`Create a new workspace for ${projectName}`).click();
|
|
await expect(page.getByRole("button", { name: "Workspace project", exact: true })).toContainText(
|
|
projectName,
|
|
{ timeout: PROJECT_VISIBILITY_TIMEOUT },
|
|
);
|
|
}
|
|
|
|
export async function selectWorkspaceHost(page: Page, hostName: string): Promise<void> {
|
|
await page.getByRole("button", { name: "Host", exact: true }).click();
|
|
await page.getByRole("button", { name: hostName, exact: true }).click();
|
|
await expect(page.getByRole("button", { name: "Host", exact: true })).toContainText(hostName);
|
|
}
|
|
|
|
export async function createWorkspaceWithoutAgent(page: Page): Promise<void> {
|
|
await page.getByRole("button", { name: "Create", exact: true }).click();
|
|
}
|
|
|
|
export async function expectProjectWorkspaceCountForHost(
|
|
page: Page,
|
|
input: { projectName: string; hostName: string; count: number },
|
|
): Promise<void> {
|
|
const group = projectGroup(page, input.projectName);
|
|
await expect(group.getByRole("button").filter({ hasText: input.hostName })).toHaveCount(
|
|
input.count,
|
|
{ timeout: PROJECT_VISIBILITY_TIMEOUT },
|
|
);
|
|
}
|
|
|
|
export async function renameProject(page: Page, name: string): Promise<void> {
|
|
await page.getByRole("button", { name: "Edit project", exact: true }).click();
|
|
await page.getByRole("textbox", { name: "Project name", exact: true }).fill(name);
|
|
await page.getByRole("button", { name: "Save changes", exact: true }).click();
|
|
await expect(page.getByText("Project updated", { exact: true })).toBeVisible({
|
|
timeout: PROJECT_VISIBILITY_TIMEOUT,
|
|
});
|
|
}
|
|
|
|
export async function openGroupedProjectSettings(
|
|
page: Page,
|
|
input: { serverId: string; projectName: string },
|
|
): Promise<void> {
|
|
await page.getByRole("button", { name: "Settings", exact: true }).click();
|
|
await expect(page).toHaveURL(/\/settings\/general$/);
|
|
await selectSettingsHost(page, input.serverId);
|
|
await page.locator('[data-testid="settings-host-section-projects"]:visible').click();
|
|
await expect(page).toHaveURL(/\/settings\/hosts\/[^/]+\/projects$/);
|
|
await openProjectSettings(page, input.projectName);
|
|
}
|
|
|
|
export async function openProjectsForSettingsHost(
|
|
page: Page,
|
|
input: { serverId: string; projectName: string },
|
|
): Promise<void> {
|
|
await selectSettingsHost(page, input.serverId);
|
|
await expect(page).toHaveURL(buildProjectsSettingsRoute(input.serverId));
|
|
await openProjectSettings(page, input.projectName);
|
|
}
|
|
|
|
export async function expectProjectSettingsName(page: Page, projectName: string): Promise<void> {
|
|
await expect(page.getByRole("main").getByText(projectName, { exact: true })).toBeVisible({
|
|
timeout: PROJECT_VISIBILITY_TIMEOUT,
|
|
});
|
|
}
|