Files
paseo/packages/app/e2e/browser/new-workspace-preselect.spec.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

274 lines
8.8 KiB
TypeScript

import { expect, test } from "../support/fixtures";
import { gotoAppShell } from "../support/helpers/app";
import { getE2EDaemonPort } from "../support/helpers/daemon-port";
import {
expectNewWorkspaceProjectSelected,
openGlobalNewWorkspaceComposer,
} from "../support/helpers/new-workspace";
import { seedWorkspace, type SeededWorkspace } from "../support/helpers/seed-client";
import { getServerId } from "../support/helpers/server-id";
import { seedSavedSettingsHosts } from "../support/helpers/settings";
import { LAST_WORKSPACE_SELECTION_STORAGE_KEY } from "@/stores/last-workspace-selection";
import { buildHostWorkspaceRoute, buildNewWorkspaceRoute } from "@/utils/host-routes";
import {
switchWorkspaceViaSidebar,
waitForSidebarHydration,
} from "../support/helpers/workspace-ui";
const OFFLINE_SERVER_IDS = [
"srv_e2e_preselect_offline_1",
"srv_e2e_preselect_offline_2",
"srv_e2e_preselect_offline_3",
];
// New Workspace preselection is a form-context decision, not startup routing.
// Entry points from a workspace should carry the current project context, and a
// plain /new must not let a stale remembered offline host steal the initial host
// when there is exactly one online saved host.
async function pressNewWorkspaceShortcut(page: import("@playwright/test").Page): Promise<void> {
const modifier = process.platform === "darwin" ? "Meta" : "Control";
await page.keyboard.press(`${modifier}+n`);
await expect(page).toHaveURL(/\/new(?:\?.*)?$/, { timeout: 30_000 });
}
async function expectProjectPreselectedWithin(
page: import("@playwright/test").Page,
projectDisplayName: string,
timeout: number,
): Promise<void> {
const projectPicker = page.getByRole("button", { name: "Workspace project" });
await expect(projectPicker).toContainText(projectDisplayName, { timeout });
}
async function expectAnyProjectPreselectedWithin(
page: import("@playwright/test").Page,
timeout: number,
): Promise<void> {
const projectPicker = page.getByRole("button", { name: "Workspace project" });
await expect(projectPicker).toBeVisible({ timeout });
await expect
.poll(
async () => {
const label = ((await projectPicker.textContent()) ?? "").trim();
return label || "Choose project";
},
{ timeout },
)
.not.toBe("Choose project");
}
async function openColdRestoredWorkspaceWithOfflineHostFirst(
page: import("@playwright/test").Page,
workspace: SeededWorkspace,
): Promise<void> {
const connectedServerId = getServerId();
await seedSavedSettingsHosts(page, [
...OFFLINE_SERVER_IDS.map((serverId, index) => ({
serverId,
label: `Offline host ${index + 1}`,
endpoint: `127.0.0.1:${index + 1}`,
})),
{
serverId: connectedServerId,
label: "Connected host",
endpoint: `127.0.0.1:${getE2EDaemonPort()}`,
},
]);
await page.evaluate(
({ storageKey, serverId, workspaceId }) => {
localStorage.setItem(storageKey, JSON.stringify({ serverId, workspaceId }));
},
{
storageKey: LAST_WORKSPACE_SELECTION_STORAGE_KEY,
serverId: connectedServerId,
workspaceId: workspace.workspaceId,
},
);
await page.goto("/");
await expect(page).toHaveURL(buildHostWorkspaceRoute(connectedServerId, workspace.workspaceId), {
timeout: 60_000,
});
await waitForSidebarHydration(page);
}
async function openNewWorkspaceWithStaleOfflineSelection(
page: import("@playwright/test").Page,
): Promise<void> {
const connectedServerId = getServerId();
await seedSavedSettingsHosts(page, [
...OFFLINE_SERVER_IDS.map((serverId, index) => ({
serverId,
label: `Offline host ${index + 1}`,
endpoint: `127.0.0.1:${index + 1}`,
})),
{
serverId: connectedServerId,
label: "Connected host",
endpoint: `127.0.0.1:${getE2EDaemonPort()}`,
},
]);
await page.evaluate(
({ storageKey, serverId }) => {
localStorage.setItem(
storageKey,
JSON.stringify({ serverId, workspaceId: "wks_stale_offline" }),
);
},
{
storageKey: LAST_WORKSPACE_SELECTION_STORAGE_KEY,
serverId: OFFLINE_SERVER_IDS[0]!,
},
);
await page.goto(buildNewWorkspaceRoute());
await expect(page.getByTestId("host-picker-trigger")).toBeVisible({ timeout: 60_000 });
}
async function seedOfflineHostsWithStaleSelection(
page: import("@playwright/test").Page,
): Promise<void> {
const connectedServerId = getServerId();
await seedSavedSettingsHosts(page, [
...OFFLINE_SERVER_IDS.map((serverId, index) => ({
serverId,
label: `Offline host ${index + 1}`,
endpoint: `127.0.0.1:${index + 1}`,
})),
{
serverId: connectedServerId,
label: "Connected host",
endpoint: `127.0.0.1:${getE2EDaemonPort()}`,
},
]);
await page.evaluate(
({ storageKey, serverId }) => {
localStorage.setItem(
storageKey,
JSON.stringify({ serverId, workspaceId: "wks_stale_offline" }),
);
},
{
storageKey: LAST_WORKSPACE_SELECTION_STORAGE_KEY,
serverId: OFFLINE_SERVER_IDS[0]!,
},
);
}
test.describe("New workspace preselects the open workspace's project", () => {
test.describe.configure({ timeout: 240_000 });
let projectA: SeededWorkspace;
let projectB: SeededWorkspace;
test.beforeEach(async () => {
projectA = await seedWorkspace({ repoPrefix: "preselect-a-" });
projectB = await seedWorkspace({ repoPrefix: "preselect-b-" });
});
test.afterEach(async () => {
await projectA?.cleanup();
await projectB?.cleanup();
});
test("Cmd+N preselects the project you are looking at", async ({ page }) => {
await gotoAppShell(page);
await waitForSidebarHydration(page);
await switchWorkspaceViaSidebar({
page,
serverId: getServerId(),
workspaceId: projectB.workspaceId,
});
await pressNewWorkspaceShortcut(page);
await expectNewWorkspaceProjectSelected(page, projectB.projectDisplayName);
await switchWorkspaceViaSidebar({
page,
serverId: getServerId(),
workspaceId: projectA.workspaceId,
});
await pressNewWorkspaceShortcut(page);
await expectNewWorkspaceProjectSelected(page, projectA.projectDisplayName);
});
test("New workspace button preselects the project you are looking at", async ({ page }) => {
await gotoAppShell(page);
await waitForSidebarHydration(page);
await switchWorkspaceViaSidebar({
page,
serverId: getServerId(),
workspaceId: projectB.workspaceId,
});
await openGlobalNewWorkspaceComposer(page);
await expectNewWorkspaceProjectSelected(page, projectB.projectDisplayName);
await switchWorkspaceViaSidebar({
page,
serverId: getServerId(),
workspaceId: projectA.workspaceId,
});
await openGlobalNewWorkspaceComposer(page);
await expectNewWorkspaceProjectSelected(page, projectA.projectDisplayName);
});
test("Cmd+N preselects the connected host project when an offline saved host is first", async ({
page,
}) => {
await openColdRestoredWorkspaceWithOfflineHostFirst(page, projectB);
await pressNewWorkspaceShortcut(page);
await expect(page.getByTestId("host-picker-trigger")).toContainText("Connected host", {
timeout: 8_000,
});
await expectProjectPreselectedWithin(page, projectB.projectDisplayName, 8_000);
});
test("New workspace button preselects the connected host project when an offline saved host is first", async ({
page,
}) => {
await openColdRestoredWorkspaceWithOfflineHostFirst(page, projectB);
await openGlobalNewWorkspaceComposer(page);
await expect(page.getByTestId("host-picker-trigger")).toContainText("Connected host", {
timeout: 8_000,
});
await expectProjectPreselectedWithin(page, projectB.projectDisplayName, 8_000);
});
test("plain /new ignores stale remembered offline hosts when only one saved host is connected", async ({
page,
}) => {
await openNewWorkspaceWithStaleOfflineSelection(page);
await expect(page.getByTestId("host-picker-trigger")).toContainText("Connected host", {
timeout: 8_000,
});
await expectAnyProjectPreselectedWithin(page, 8_000);
});
test("stale remembered offline host heals after visiting the connected workspace", async ({
page,
}) => {
const connectedServerId = getServerId();
await seedOfflineHostsWithStaleSelection(page);
await page.goto(buildHostWorkspaceRoute(connectedServerId, projectB.workspaceId));
await expect(page).toHaveURL(buildHostWorkspaceRoute(connectedServerId, projectB.workspaceId), {
timeout: 60_000,
});
await waitForSidebarHydration(page);
await openGlobalNewWorkspaceComposer(page);
await expect(page.getByTestId("host-picker-trigger")).toContainText("Connected host", {
timeout: 8_000,
});
await expectProjectPreselectedWithin(page, projectB.projectDisplayName, 8_000);
});
});