mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
Rewrites 7 spec files to use DSL helpers throughout — zero raw page.locator/getByText/getByTestId in test() bodies. Adds 30+ new helper primitives across 7 existing helper modules.
This commit is contained in:
@@ -4,6 +4,17 @@ function composerInput(page: Page) {
|
|||||||
return page.getByRole("textbox", { name: "Message agent..." }).first();
|
return page.getByRole("textbox", { name: "Message agent..." }).first();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function composerLocator(page: Page) {
|
||||||
|
return composerInput(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function expectComposerVisible(
|
||||||
|
page: Page,
|
||||||
|
options?: { timeout?: number },
|
||||||
|
): Promise<void> {
|
||||||
|
await expect(composerInput(page)).toBeVisible({ timeout: options?.timeout ?? 15_000 });
|
||||||
|
}
|
||||||
|
|
||||||
export async function submitMessage(page: Page, text: string): Promise<void> {
|
export async function submitMessage(page: Page, text: string): Promise<void> {
|
||||||
const input = composerInput(page);
|
const input = composerInput(page);
|
||||||
await expect(input).toBeEditable({ timeout: 30_000 });
|
await expect(input).toBeEditable({ timeout: 30_000 });
|
||||||
|
|||||||
@@ -174,6 +174,19 @@ export async function sampleTabsDuringTransition(
|
|||||||
return snapshots;
|
return snapshots;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function terminalSurfaceLocator(page: Page) {
|
||||||
|
return page.locator('[data-testid="terminal-surface"]').first();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function expectAgentTabActive(page: Page, agentId: string): Promise<void> {
|
||||||
|
const tabTestId = `workspace-tab-agent_${agentId}`;
|
||||||
|
await expect(page.getByTestId(tabTestId).filter({ visible: true })).toHaveAttribute(
|
||||||
|
"aria-selected",
|
||||||
|
"true",
|
||||||
|
);
|
||||||
|
await expect(getActiveTabTestId(page)).resolves.toBe(tabTestId);
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Workspace setup ───────────────────────────────────────────────────────
|
// ─── Workspace setup ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
/** Create a temp git repo and return its path with a cleanup function. */
|
/** Create a temp git repo and return its path with a cleanup function. */
|
||||||
|
|||||||
@@ -165,3 +165,74 @@ export async function expectAboutContent(page: Page): Promise<void> {
|
|||||||
export async function expectGeneralContent(page: Page): Promise<void> {
|
export async function expectGeneralContent(page: Page): Promise<void> {
|
||||||
await expect(page.getByText("Theme", { exact: true }).first()).toBeVisible();
|
await expect(page.getByText("Theme", { exact: true }).first()).toBeVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function expectHostLabelDisplayed(page: Page): Promise<void> {
|
||||||
|
await expect(page.getByTestId("host-page-label-edit-button")).toBeVisible();
|
||||||
|
await expect(page.getByTestId("host-page-label-input")).toHaveCount(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function clickEditHostLabel(page: Page): Promise<void> {
|
||||||
|
await page.getByTestId("host-page-label-edit-button").click();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function expectHostLabelEditMode(page: Page, expectedLabel: string): Promise<void> {
|
||||||
|
await expect(page.getByTestId("host-page-label-input")).toBeVisible();
|
||||||
|
await expect(page.getByTestId("host-page-label-input")).toHaveValue(expectedLabel);
|
||||||
|
await expect(page.getByTestId("host-page-label-save")).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function expectHostConnectionsCard(page: Page, port: string): Promise<void> {
|
||||||
|
const card = page.getByTestId("host-page-connections-card");
|
||||||
|
await expect(card).toBeVisible();
|
||||||
|
await expect(page.getByText("Connections", { exact: true })).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
card.getByText(new RegExp(`TCP \\((localhost|127\\.0\\.0\\.1):${port}\\)`)),
|
||||||
|
).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function expectHostInjectMcpCard(page: Page): Promise<void> {
|
||||||
|
const card = page.getByTestId("host-page-inject-mcp-card");
|
||||||
|
await expect(card).toBeVisible();
|
||||||
|
await expect(card.getByRole("switch", { name: "Inject Paseo tools" })).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function expectHostActionCards(page: Page): Promise<void> {
|
||||||
|
await expect(page.getByTestId("host-page-restart-card")).toBeVisible();
|
||||||
|
await expect(page.getByTestId("host-page-restart-button")).toBeVisible();
|
||||||
|
await expect(page.getByTestId("host-page-providers-card")).toBeVisible();
|
||||||
|
await expect(page.getByTestId("host-page-remove-host-card")).toBeVisible();
|
||||||
|
await expect(page.getByTestId("host-page-remove-host-button")).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function expectHostNoLocalOnlyRows(page: Page): Promise<void> {
|
||||||
|
await expect(page.getByTestId("host-page-pair-device-row")).toHaveCount(0);
|
||||||
|
await expect(page.getByTestId("host-page-daemon-lifecycle-card")).toHaveCount(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function expectRetiredSidebarSectionsAbsent(page: Page): Promise<void> {
|
||||||
|
const sidebar = page.getByTestId("settings-sidebar");
|
||||||
|
await expect(sidebar).toBeVisible();
|
||||||
|
await expect(sidebar.getByRole("button", { name: "Hosts", exact: true })).toHaveCount(0);
|
||||||
|
await expect(sidebar.getByRole("button", { name: "Providers", exact: true })).toHaveCount(0);
|
||||||
|
await expect(sidebar.getByRole("button", { name: "Pair device", exact: true })).toHaveCount(0);
|
||||||
|
await expect(sidebar.getByRole("button", { name: "Daemon", exact: true })).toHaveCount(0);
|
||||||
|
await expect(sidebar.getByRole("button", { name: "General", exact: true })).toBeVisible();
|
||||||
|
await expect(sidebar.getByRole("button", { name: "Diagnostics", exact: true })).toBeVisible();
|
||||||
|
await expect(sidebar.getByRole("button", { name: "About", exact: true })).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function expectHostPageVisible(page: Page, serverId: string): Promise<void> {
|
||||||
|
await expect(page.getByTestId(`settings-host-page-${serverId}`)).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function expectLocalHostEntryFirst(page: Page, serverId: string): Promise<void> {
|
||||||
|
const sidebar = page.getByTestId("settings-sidebar");
|
||||||
|
await expect(sidebar).toBeVisible({ timeout: 15_000 });
|
||||||
|
await expect(sidebar.locator('[data-testid^="settings-host-entry-"]').first()).toHaveAttribute(
|
||||||
|
"data-testid",
|
||||||
|
`settings-host-entry-${serverId}`,
|
||||||
|
);
|
||||||
|
const localHostEntry = page.getByTestId(`settings-host-entry-${serverId}`);
|
||||||
|
await expect(localHostEntry.getByTestId("settings-host-local-marker")).toBeVisible();
|
||||||
|
await expect(localHostEntry.getByText("Local", { exact: true })).toBeVisible();
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Page } from "@playwright/test";
|
import { expect, type Page } from "@playwright/test";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { pathToFileURL } from "node:url";
|
import { pathToFileURL } from "node:url";
|
||||||
import { randomUUID } from "node:crypto";
|
import { randomUUID } from "node:crypto";
|
||||||
@@ -263,6 +263,34 @@ export async function measureKeystrokeLatency(page: Page, char: string): Promise
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function expectTerminalSurfaceVisible(
|
||||||
|
page: Page,
|
||||||
|
options?: { timeout?: number },
|
||||||
|
): Promise<void> {
|
||||||
|
await expect(page.locator('[data-testid="terminal-surface"]').first()).toBeVisible({
|
||||||
|
timeout: options?.timeout ?? 20_000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function focusTerminalSurface(page: Page): Promise<void> {
|
||||||
|
await expectTerminalSurfaceVisible(page);
|
||||||
|
await page.locator('[data-testid="terminal-surface"]').first().click();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function typeInTerminal(page: Page, text: string): Promise<void> {
|
||||||
|
await page
|
||||||
|
.locator('[data-testid="terminal-surface"]')
|
||||||
|
.first()
|
||||||
|
.pressSequentially(text, { delay: 0 });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function waitForTerminalAttached(page: Page): Promise<void> {
|
||||||
|
await page
|
||||||
|
.locator('[data-testid="terminal-attach-loading"]')
|
||||||
|
.waitFor({ state: "hidden", timeout: 10_000 })
|
||||||
|
.catch(() => undefined);
|
||||||
|
}
|
||||||
|
|
||||||
export function computePercentile(samples: number[], p: number): number {
|
export function computePercentile(samples: number[], p: number): number {
|
||||||
const sorted = [...samples].sort((a, b) => a - b);
|
const sorted = [...samples].sort((a, b) => a - b);
|
||||||
const index = Math.ceil((p / 100) * sorted.length) - 1;
|
const index = Math.ceil((p / 100) * sorted.length) - 1;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { expect, type Page } from "@playwright/test";
|
|||||||
import { parseHostWorkspaceRouteFromPathname } from "../../src/utils/host-routes";
|
import { parseHostWorkspaceRouteFromPathname } from "../../src/utils/host-routes";
|
||||||
import { gotoAppShell } from "./app";
|
import { gotoAppShell } from "./app";
|
||||||
import { createNodeWebSocketFactory, type NodeWebSocketFactory } from "./node-ws-factory";
|
import { createNodeWebSocketFactory, type NodeWebSocketFactory } from "./node-ws-factory";
|
||||||
|
import { switchWorkspaceViaSidebar } from "./workspace-ui";
|
||||||
import type { SessionOutboundMessage } from "@server/shared/messages";
|
import type { SessionOutboundMessage } from "@server/shared/messages";
|
||||||
|
|
||||||
interface WorkspaceSetupDaemonClient {
|
interface WorkspaceSetupDaemonClient {
|
||||||
@@ -315,6 +316,30 @@ export async function fetchWorkspaceById(
|
|||||||
return workspace;
|
return workspace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function navigateToWorkspaceViaSidebar(
|
||||||
|
page: Page,
|
||||||
|
workspaceId: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const serverId = process.env.E2E_SERVER_ID;
|
||||||
|
if (!serverId) {
|
||||||
|
throw new Error("E2E_SERVER_ID is not set.");
|
||||||
|
}
|
||||||
|
await switchWorkspaceViaSidebar({ page, serverId, targetWorkspacePath: workspaceId });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function openWorkspaceScriptsMenu(page: Page): Promise<void> {
|
||||||
|
await page.getByTestId("workspace-scripts-button").click();
|
||||||
|
await expect(page.getByTestId("workspace-scripts-menu")).toBeVisible({ timeout: 10_000 });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function startWorkspaceScriptFromMenu(page: Page, scriptName: string): Promise<void> {
|
||||||
|
await page.getByTestId(`workspace-scripts-start-${scriptName}`).click();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function closeWorkspaceScriptsMenu(page: Page): Promise<void> {
|
||||||
|
await page.getByTestId("workspace-scripts-menu-backdrop").click();
|
||||||
|
}
|
||||||
|
|
||||||
export async function waitForWorkspaceSetupProgress(
|
export async function waitForWorkspaceSetupProgress(
|
||||||
client: WorkspaceSetupDaemonClient,
|
client: WorkspaceSetupDaemonClient,
|
||||||
predicate: (payload: WorkspaceSetupProgressPayload) => boolean,
|
predicate: (payload: WorkspaceSetupProgressPayload) => boolean,
|
||||||
|
|||||||
@@ -70,6 +70,29 @@ export async function ensureWorkspaceAgentPaneVisible(page: Page): Promise<void>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function expectWorkspaceTabsAbsent(page: Page): Promise<void> {
|
||||||
|
await expect(page.getByTestId("workspace-tabs-row")).toHaveCount(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function expectNoTerminalTabs(page: Page): Promise<void> {
|
||||||
|
await expect(page.locator('[data-testid^="workspace-tab-terminal_"]')).toHaveCount(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function clickFirstTerminalTab(
|
||||||
|
page: Page,
|
||||||
|
options?: { timeout?: number },
|
||||||
|
): Promise<void> {
|
||||||
|
const tab = page.locator('[data-testid^="workspace-tab-terminal_"]').first();
|
||||||
|
await expect(tab).toBeVisible({ timeout: options?.timeout ?? 30_000 });
|
||||||
|
await tab.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function expectFirstTerminalTabContains(page: Page, text: string): Promise<void> {
|
||||||
|
await expect(page.locator('[data-testid^="workspace-tab-terminal_"]').first()).toContainText(
|
||||||
|
text,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export async function sampleWorkspaceTabIds(
|
export async function sampleWorkspaceTabIds(
|
||||||
page: Page,
|
page: Page,
|
||||||
options: { durationMs?: number; intervalMs?: number } = {},
|
options: { durationMs?: number; intervalMs?: number } = {},
|
||||||
|
|||||||
@@ -114,6 +114,49 @@ export async function expectWorkspaceHeader(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function expectReconnectingToastVisible(
|
||||||
|
page: Page,
|
||||||
|
options?: { timeout?: number },
|
||||||
|
): Promise<void> {
|
||||||
|
await expect(page.getByTestId("agent-reconnecting-toast")).toBeVisible({
|
||||||
|
timeout: options?.timeout ?? 30_000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function expectReconnectingToastGone(
|
||||||
|
page: Page,
|
||||||
|
options?: { timeout?: number },
|
||||||
|
): Promise<void> {
|
||||||
|
await expect(page.getByTestId("agent-reconnecting-toast")).toHaveCount(0, {
|
||||||
|
timeout: options?.timeout ?? 30_000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function expectHostConnectingOrOffline(
|
||||||
|
page: Page,
|
||||||
|
options?: { timeout?: number },
|
||||||
|
): Promise<void> {
|
||||||
|
await expect(
|
||||||
|
page.getByText(/^Connecting$|localhost is offline|Cannot reach localhost/i),
|
||||||
|
).toBeVisible({ timeout: options?.timeout ?? 30_000 });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function expectMenuButtonVisible(page: Page): Promise<void> {
|
||||||
|
await expect(page.getByTestId("menu-button")).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function expectWorkspaceHeaderAbsent(page: Page): Promise<void> {
|
||||||
|
await expect(page.getByTestId("workspace-header-title")).toHaveCount(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function workspaceDeckEntryLocator(page: Page, serverId: string, workspaceId: string) {
|
||||||
|
return page.getByTestId(`workspace-deck-entry-${serverId}:${workspaceId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function expectWorkspaceDeckEntryCount(page: Page, count: number): Promise<void> {
|
||||||
|
await expect(page.locator('[data-testid^="workspace-deck-entry-"]')).toHaveCount(count);
|
||||||
|
}
|
||||||
|
|
||||||
export async function seedWorkspaceActivity(page: Page, marker: string): Promise<void> {
|
export async function seedWorkspaceActivity(page: Page, marker: string): Promise<void> {
|
||||||
const input = page.getByRole("textbox", { name: "Message agent..." });
|
const input = page.getByRole("textbox", { name: "Message agent..." });
|
||||||
await expect(input).toBeEditable({ timeout: 30_000 });
|
await expect(input).toBeEditable({ timeout: 30_000 });
|
||||||
|
|||||||
@@ -13,7 +13,10 @@ import {
|
|||||||
waitForTabWithTitle,
|
waitForTabWithTitle,
|
||||||
measureTileTransition,
|
measureTileTransition,
|
||||||
sampleTabsDuringTransition,
|
sampleTabsDuringTransition,
|
||||||
|
terminalSurfaceLocator,
|
||||||
} from "./helpers/launcher";
|
} from "./helpers/launcher";
|
||||||
|
import { expectComposerVisible, composerLocator } from "./helpers/composer";
|
||||||
|
import { expectTerminalSurfaceVisible } from "./helpers/terminal-perf";
|
||||||
import {
|
import {
|
||||||
connectTerminalClient,
|
connectTerminalClient,
|
||||||
setupDeterministicPrompt,
|
setupDeterministicPrompt,
|
||||||
@@ -49,9 +52,7 @@ test.describe("Tab creation", () => {
|
|||||||
|
|
||||||
await pressNewTabShortcut(page);
|
await pressNewTabShortcut(page);
|
||||||
|
|
||||||
// Should show the composer directly (no launcher panel)
|
await expectComposerVisible(page);
|
||||||
const composer = page.getByRole("textbox", { name: "Message agent..." });
|
|
||||||
await expect(composer.first()).toBeVisible({ timeout: 15_000 });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("opening two new tabs creates two draft tabs", async ({ page }) => {
|
test("opening two new tabs creates two draft tabs", async ({ page }) => {
|
||||||
@@ -78,9 +79,7 @@ test.describe("Tab creation", () => {
|
|||||||
|
|
||||||
await clickNewChat(page);
|
await clickNewChat(page);
|
||||||
|
|
||||||
// Draft composer should appear (the agent message input)
|
await expectComposerVisible(page);
|
||||||
const composer = page.getByRole("textbox", { name: "Message agent..." });
|
|
||||||
await expect(composer.first()).toBeVisible({ timeout: 15_000 });
|
|
||||||
|
|
||||||
const tabsAfter = await getTabTestIds(page);
|
const tabsAfter = await getTabTestIds(page);
|
||||||
const draftCountAfter = tabsAfter.filter((id) => id.includes("draft")).length;
|
const draftCountAfter = tabsAfter.filter((id) => id.includes("draft")).length;
|
||||||
@@ -93,9 +92,7 @@ test.describe("Tab creation", () => {
|
|||||||
|
|
||||||
await clickNewTerminal(page);
|
await clickNewTerminal(page);
|
||||||
|
|
||||||
// Terminal surface should appear
|
await expectTerminalSurfaceVisible(page);
|
||||||
const terminal = page.locator('[data-testid="terminal-surface"]');
|
|
||||||
await expect(terminal.first()).toBeVisible({ timeout: 20_000 });
|
|
||||||
|
|
||||||
const tabsAfter = await getTabTestIds(page);
|
const tabsAfter = await getTabTestIds(page);
|
||||||
const terminalTabs = tabsAfter.filter((id) => id.includes("terminal"));
|
const terminalTabs = tabsAfter.filter((id) => id.includes("terminal"));
|
||||||
@@ -142,17 +139,16 @@ test.describe("Terminal title propagation", () => {
|
|||||||
await gotoWorkspace(page, workspaceId);
|
await gotoWorkspace(page, workspaceId);
|
||||||
await clickNewTerminal(page);
|
await clickNewTerminal(page);
|
||||||
|
|
||||||
const terminal = page.locator('[data-testid="terminal-surface"]');
|
await expectTerminalSurfaceVisible(page);
|
||||||
await expect(terminal.first()).toBeVisible({ timeout: 20_000 });
|
await terminalSurfaceLocator(page).click();
|
||||||
await terminal.first().click();
|
|
||||||
|
|
||||||
await setupDeterministicPrompt(page);
|
await setupDeterministicPrompt(page);
|
||||||
|
|
||||||
// Send OSC 0 (set window title) escape sequence
|
// Send OSC 0 (set window title) escape sequence
|
||||||
const testTitle = `E2E-Title-${Date.now()}`;
|
const testTitle = `E2E-Title-${Date.now()}`;
|
||||||
await terminal
|
await terminalSurfaceLocator(page).pressSequentially(`printf '\\033]0;${testTitle}\\007'\n`, {
|
||||||
.first()
|
delay: 0,
|
||||||
.pressSequentially(`printf '\\033]0;${testTitle}\\007'\n`, { delay: 0 });
|
});
|
||||||
|
|
||||||
// Wait for the tab to reflect the new title
|
// Wait for the tab to reflect the new title
|
||||||
await waitForTabWithTitle(page, testTitle, 15_000);
|
await waitForTabWithTitle(page, testTitle, 15_000);
|
||||||
@@ -172,22 +168,22 @@ test.describe("Terminal title propagation", () => {
|
|||||||
await gotoWorkspace(page, workspaceId);
|
await gotoWorkspace(page, workspaceId);
|
||||||
await clickNewTerminal(page);
|
await clickNewTerminal(page);
|
||||||
|
|
||||||
const terminal = page.locator('[data-testid="terminal-surface"]');
|
await expectTerminalSurfaceVisible(page);
|
||||||
await expect(terminal.first()).toBeVisible({ timeout: 20_000 });
|
await terminalSurfaceLocator(page).click();
|
||||||
await terminal.first().click();
|
|
||||||
|
|
||||||
await setupDeterministicPrompt(page);
|
await setupDeterministicPrompt(page);
|
||||||
|
|
||||||
// Fire many rapid title changes — only the last should stick
|
// Fire many rapid title changes — only the last should stick
|
||||||
const finalTitle = `Final-${Date.now()}`;
|
const finalTitle = `Final-${Date.now()}`;
|
||||||
for (let i = 0; i < 5; i++) {
|
for (let i = 0; i < 5; i++) {
|
||||||
await terminal
|
await terminalSurfaceLocator(page).pressSequentially(`printf '\\033]0;Rapid-${i}\\007'\n`, {
|
||||||
.first()
|
delay: 0,
|
||||||
.pressSequentially(`printf '\\033]0;Rapid-${i}\\007'\n`, { delay: 0 });
|
});
|
||||||
}
|
}
|
||||||
await terminal
|
await terminalSurfaceLocator(page).pressSequentially(
|
||||||
.first()
|
`printf '\\033]0;${finalTitle}\\007'\n`,
|
||||||
.pressSequentially(`printf '\\033]0;${finalTitle}\\007'\n`, { delay: 0 });
|
{ delay: 0 },
|
||||||
|
);
|
||||||
|
|
||||||
// The tab should eventually settle on the final title
|
// The tab should eventually settle on the final title
|
||||||
await waitForTabWithTitle(page, finalTitle, 15_000);
|
await waitForTabWithTitle(page, finalTitle, 15_000);
|
||||||
@@ -227,11 +223,10 @@ test.describe("Tab transitions (no flash)", () => {
|
|||||||
test.setTimeout(30_000);
|
test.setTimeout(30_000);
|
||||||
await gotoWorkspace(page, workspaceId);
|
await gotoWorkspace(page, workspaceId);
|
||||||
|
|
||||||
const terminal = page.locator('[data-testid="terminal-surface"]');
|
|
||||||
const elapsed = await measureTileTransition(
|
const elapsed = await measureTileTransition(
|
||||||
page,
|
page,
|
||||||
() => clickNewTerminal(page),
|
() => clickNewTerminal(page),
|
||||||
terminal.first(),
|
terminalSurfaceLocator(page),
|
||||||
20_000,
|
20_000,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -244,9 +239,12 @@ test.describe("Tab transitions (no flash)", () => {
|
|||||||
test("New agent tab click shows composer without flash", async ({ page }) => {
|
test("New agent tab click shows composer without flash", async ({ page }) => {
|
||||||
await gotoWorkspace(page, workspaceId);
|
await gotoWorkspace(page, workspaceId);
|
||||||
|
|
||||||
const composer = page.getByRole("textbox", { name: "Message agent..." }).first();
|
const elapsed = await measureTileTransition(
|
||||||
|
page,
|
||||||
const elapsed = await measureTileTransition(page, () => clickNewChat(page), composer, 10_000);
|
() => clickNewChat(page),
|
||||||
|
composerLocator(page),
|
||||||
|
10_000,
|
||||||
|
);
|
||||||
|
|
||||||
// Draft creation is fully in-memory — should be fast
|
// Draft creation is fully in-memory — should be fast
|
||||||
// We use a generous budget here because CI can be slow, but the key assertion
|
// We use a generous budget here because CI can be slow, but the key assertion
|
||||||
|
|||||||
@@ -1,7 +1,20 @@
|
|||||||
import { test, expect, type Page } from "./fixtures";
|
import { test } from "./fixtures";
|
||||||
import { gotoAppShell, openSettings } from "./helpers/app";
|
import { gotoAppShell, openSettings } from "./helpers/app";
|
||||||
import { TEST_HOST_LABEL } from "./helpers/daemon-registry";
|
import { TEST_HOST_LABEL } from "./helpers/daemon-registry";
|
||||||
import { expectSettingsHeader, openSettingsHost } from "./helpers/settings";
|
import {
|
||||||
|
expectSettingsHeader,
|
||||||
|
openSettingsHost,
|
||||||
|
expectHostLabelDisplayed,
|
||||||
|
clickEditHostLabel,
|
||||||
|
expectHostLabelEditMode,
|
||||||
|
expectHostConnectionsCard,
|
||||||
|
expectHostInjectMcpCard,
|
||||||
|
expectHostActionCards,
|
||||||
|
expectHostNoLocalOnlyRows,
|
||||||
|
expectRetiredSidebarSectionsAbsent,
|
||||||
|
expectHostPageVisible,
|
||||||
|
expectLocalHostEntryFirst,
|
||||||
|
} from "./helpers/settings";
|
||||||
|
|
||||||
function getSeededServerId(): string {
|
function getSeededServerId(): string {
|
||||||
const serverId = process.env.E2E_SERVER_ID;
|
const serverId = process.env.E2E_SERVER_ID;
|
||||||
@@ -19,12 +32,6 @@ function getSeededDaemonPort(): string {
|
|||||||
return port;
|
return port;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function expectHostLabelHeader(page: Page) {
|
|
||||||
await expectSettingsHeader(page, TEST_HOST_LABEL);
|
|
||||||
await expect(page.getByTestId("host-page-label-edit-button")).toBeVisible();
|
|
||||||
await expect(page.getByTestId("host-page-label-input")).toHaveCount(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
test.describe("Settings host page", () => {
|
test.describe("Settings host page", () => {
|
||||||
test("host page shows seeded label, connection endpoint, inject MCP toggle, and all action rows", async ({
|
test("host page shows seeded label, connection endpoint, inject MCP toggle, and all action rows", async ({
|
||||||
page,
|
page,
|
||||||
@@ -36,26 +43,11 @@ test.describe("Settings host page", () => {
|
|||||||
await openSettings(page);
|
await openSettings(page);
|
||||||
await openSettingsHost(page, serverId);
|
await openSettingsHost(page, serverId);
|
||||||
|
|
||||||
// Label renders in the detail header with a pencil edit affordance; the input is hidden until edit.
|
await expectSettingsHeader(page, TEST_HOST_LABEL);
|
||||||
await expectHostLabelHeader(page);
|
await expectHostLabelDisplayed(page);
|
||||||
|
await expectHostConnectionsCard(page, port);
|
||||||
// Connections is its own section with a "Connections" heading and the seeded endpoint row.
|
await expectHostInjectMcpCard(page);
|
||||||
const connectionsCard = page.getByTestId("host-page-connections-card");
|
await expectHostActionCards(page);
|
||||||
await expect(connectionsCard).toBeVisible();
|
|
||||||
await expect(page.getByText("Connections", { exact: true })).toBeVisible();
|
|
||||||
await expect(
|
|
||||||
connectionsCard.getByText(new RegExp(`TCP \\((localhost|127\\.0\\.0\\.1):${port}\\)`)),
|
|
||||||
).toBeVisible();
|
|
||||||
|
|
||||||
const injectMcpCard = page.getByTestId("host-page-inject-mcp-card");
|
|
||||||
await expect(injectMcpCard).toBeVisible();
|
|
||||||
await expect(injectMcpCard.getByRole("switch", { name: "Inject Paseo tools" })).toBeVisible();
|
|
||||||
|
|
||||||
await expect(page.getByTestId("host-page-restart-card")).toBeVisible();
|
|
||||||
await expect(page.getByTestId("host-page-restart-button")).toBeVisible();
|
|
||||||
await expect(page.getByTestId("host-page-providers-card")).toBeVisible();
|
|
||||||
await expect(page.getByTestId("host-page-remove-host-card")).toBeVisible();
|
|
||||||
await expect(page.getByTestId("host-page-remove-host-button")).toBeVisible();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("clicking the label pencil reveals the inline editor", async ({ page }) => {
|
test("clicking the label pencil reveals the inline editor", async ({ page }) => {
|
||||||
@@ -65,13 +57,9 @@ test.describe("Settings host page", () => {
|
|||||||
await openSettings(page);
|
await openSettings(page);
|
||||||
await openSettingsHost(page, serverId);
|
await openSettingsHost(page, serverId);
|
||||||
|
|
||||||
await expect(page.getByTestId("host-page-label-input")).toHaveCount(0);
|
await expectHostLabelDisplayed(page);
|
||||||
|
await clickEditHostLabel(page);
|
||||||
await page.getByTestId("host-page-label-edit-button").click();
|
await expectHostLabelEditMode(page, TEST_HOST_LABEL);
|
||||||
|
|
||||||
await expect(page.getByTestId("host-page-label-input")).toBeVisible();
|
|
||||||
await expect(page.getByTestId("host-page-label-input")).toHaveValue(TEST_HOST_LABEL);
|
|
||||||
await expect(page.getByTestId("host-page-label-save")).toBeVisible();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("host page does not render pair-device or daemon-lifecycle rows for a remote daemon", async ({
|
test("host page does not render pair-device or daemon-lifecycle rows for a remote daemon", async ({
|
||||||
@@ -84,28 +72,14 @@ test.describe("Settings host page", () => {
|
|||||||
await openSettingsHost(page, serverId);
|
await openSettingsHost(page, serverId);
|
||||||
|
|
||||||
// TODO: add local-daemon fixture for positive Pair/Daemon coverage.
|
// TODO: add local-daemon fixture for positive Pair/Daemon coverage.
|
||||||
// Pair-device now lives behind a row that only the local host sees
|
await expectHostNoLocalOnlyRows(page);
|
||||||
// (gated by useIsLocalDaemon); the seeded host is remote, so it must
|
|
||||||
// not appear. The daemon-lifecycle card is still local-host only.
|
|
||||||
await expect(page.getByTestId("host-page-pair-device-row")).toHaveCount(0);
|
|
||||||
await expect(page.getByTestId("host-page-daemon-lifecycle-card")).toHaveCount(0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("settings sidebar does not expose retired top-level sections", async ({ page }) => {
|
test("settings sidebar does not expose retired top-level sections", async ({ page }) => {
|
||||||
await gotoAppShell(page);
|
await gotoAppShell(page);
|
||||||
await openSettings(page);
|
await openSettings(page);
|
||||||
|
|
||||||
const sidebar = page.getByTestId("settings-sidebar");
|
await expectRetiredSidebarSectionsAbsent(page);
|
||||||
await expect(sidebar).toBeVisible();
|
|
||||||
|
|
||||||
await expect(sidebar.getByRole("button", { name: "Hosts", exact: true })).toHaveCount(0);
|
|
||||||
await expect(sidebar.getByRole("button", { name: "Providers", exact: true })).toHaveCount(0);
|
|
||||||
await expect(sidebar.getByRole("button", { name: "Pair device", exact: true })).toHaveCount(0);
|
|
||||||
await expect(sidebar.getByRole("button", { name: "Daemon", exact: true })).toHaveCount(0);
|
|
||||||
|
|
||||||
await expect(sidebar.getByRole("button", { name: "General", exact: true })).toBeVisible();
|
|
||||||
await expect(sidebar.getByRole("button", { name: "Diagnostics", exact: true })).toBeVisible();
|
|
||||||
await expect(sidebar.getByRole("button", { name: "About", exact: true })).toBeVisible();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("navigating to /settings/hosts/[serverId] directly renders the host page", async ({
|
test("navigating to /settings/hosts/[serverId] directly renders the host page", async ({
|
||||||
@@ -116,9 +90,10 @@ test.describe("Settings host page", () => {
|
|||||||
await gotoAppShell(page);
|
await gotoAppShell(page);
|
||||||
await page.goto(`/settings/hosts/${encodeURIComponent(serverId)}`);
|
await page.goto(`/settings/hosts/${encodeURIComponent(serverId)}`);
|
||||||
|
|
||||||
await expect(page.getByTestId(`settings-host-page-${serverId}`)).toBeVisible();
|
await expectHostPageVisible(page, serverId);
|
||||||
await expectHostLabelHeader(page);
|
await expectSettingsHeader(page, TEST_HOST_LABEL);
|
||||||
await expect(page.getByTestId("host-page-remove-host-card")).toBeVisible();
|
await expectHostLabelDisplayed(page);
|
||||||
|
await expectHostActionCards(page);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("sidebar pins the local daemon host first with a Local marker", async ({ page }) => {
|
test("sidebar pins the local daemon host first with a Local marker", async ({ page }) => {
|
||||||
@@ -160,18 +135,6 @@ test.describe("Settings host page", () => {
|
|||||||
|
|
||||||
await gotoAppShell(page);
|
await gotoAppShell(page);
|
||||||
await openSettings(page);
|
await openSettings(page);
|
||||||
|
await expectLocalHostEntryFirst(page, serverId);
|
||||||
const sidebar = page.getByTestId("settings-sidebar");
|
|
||||||
await expect(sidebar).toBeVisible({ timeout: 15000 });
|
|
||||||
|
|
||||||
const hostEntries = sidebar.locator('[data-testid^="settings-host-entry-"]');
|
|
||||||
await expect(hostEntries.first()).toHaveAttribute(
|
|
||||||
"data-testid",
|
|
||||||
`settings-host-entry-${serverId}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const localHostEntry = page.getByTestId(`settings-host-entry-${serverId}`);
|
|
||||||
await expect(localHostEntry.getByTestId("settings-host-local-marker")).toBeVisible();
|
|
||||||
await expect(localHostEntry.getByText("Local", { exact: true })).toBeVisible();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { buildHostAgentDetailRoute, buildHostWorkspaceRoute } from "@/utils/host-routes";
|
import { buildHostAgentDetailRoute, buildHostWorkspaceRoute } from "@/utils/host-routes";
|
||||||
import { expect, test, type Page } from "./fixtures";
|
import { expect, test } from "./fixtures";
|
||||||
import {
|
import {
|
||||||
archiveAgentFromDaemon,
|
archiveAgentFromDaemon,
|
||||||
connectArchiveTabDaemonClient,
|
connectArchiveTabDaemonClient,
|
||||||
createIdleAgent,
|
createIdleAgent,
|
||||||
openWorkspaceWithAgents,
|
openWorkspaceWithAgents,
|
||||||
} from "./helpers/archive-tab";
|
} from "./helpers/archive-tab";
|
||||||
import { getActiveTabTestId, waitForTabBar } from "./helpers/launcher";
|
import { waitForTabBar, expectAgentTabActive } from "./helpers/launcher";
|
||||||
import { createTempGitRepo } from "./helpers/workspace";
|
import { createTempGitRepo } from "./helpers/workspace";
|
||||||
|
|
||||||
function getServerId(): string {
|
function getServerId(): string {
|
||||||
@@ -17,12 +17,15 @@ function getServerId(): string {
|
|||||||
return serverId;
|
return serverId;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pressSettingsToggleShortcut(page: Page) {
|
async function pressSettingsToggleShortcut(page: import("@playwright/test").Page) {
|
||||||
const modifier = process.platform === "darwin" ? "Meta" : "Control";
|
const modifier = process.platform === "darwin" ? "Meta" : "Control";
|
||||||
await page.keyboard.press(`${modifier}+Comma`);
|
await page.keyboard.press(`${modifier}+Comma`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function expectSendBehavior(page: Page, expected: "interrupt" | "queue") {
|
async function expectSendBehavior(
|
||||||
|
page: import("@playwright/test").Page,
|
||||||
|
expected: "interrupt" | "queue",
|
||||||
|
) {
|
||||||
await expect
|
await expect
|
||||||
.poll(async () => {
|
.poll(async () => {
|
||||||
const raw = await page.evaluate(() => localStorage.getItem("@paseo:app-settings"));
|
const raw = await page.evaluate(() => localStorage.getItem("@paseo:app-settings"));
|
||||||
@@ -35,12 +38,11 @@ async function expectSendBehavior(page: Page, expected: "interrupt" | "queue") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function openAgentRouteAndExpectFocused(input: {
|
async function openAgentRouteAndExpectFocused(input: {
|
||||||
page: Page;
|
page: import("@playwright/test").Page;
|
||||||
serverId: string;
|
serverId: string;
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
agentId: string;
|
agentId: string;
|
||||||
}) {
|
}) {
|
||||||
const expectedActiveTabId = `workspace-tab-agent_${input.agentId}`;
|
|
||||||
await input.page.goto(
|
await input.page.goto(
|
||||||
buildHostAgentDetailRoute(input.serverId, input.agentId, input.workspaceId),
|
buildHostAgentDetailRoute(input.serverId, input.agentId, input.workspaceId),
|
||||||
);
|
);
|
||||||
@@ -49,10 +51,7 @@ async function openAgentRouteAndExpectFocused(input: {
|
|||||||
{ timeout: 60_000 },
|
{ timeout: 60_000 },
|
||||||
);
|
);
|
||||||
await waitForTabBar(input.page);
|
await waitForTabBar(input.page);
|
||||||
await expect(
|
await expectAgentTabActive(input.page, input.agentId);
|
||||||
input.page.getByTestId(expectedActiveTabId).filter({ visible: true }),
|
|
||||||
).toHaveAttribute("aria-selected", "true");
|
|
||||||
await expect(getActiveTabTestId(input.page)).resolves.toBe(expectedActiveTabId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test.describe("Settings toggle tab regression", () => {
|
test.describe("Settings toggle tab regression", () => {
|
||||||
@@ -79,13 +78,7 @@ test.describe("Settings toggle tab regression", () => {
|
|||||||
|
|
||||||
await openWorkspaceWithAgents(page, [firstAgent, secondAgent]);
|
await openWorkspaceWithAgents(page, [firstAgent, secondAgent]);
|
||||||
await waitForTabBar(page);
|
await waitForTabBar(page);
|
||||||
|
await expectAgentTabActive(page, secondAgent.id);
|
||||||
const expectedActiveTabId = `workspace-tab-agent_${secondAgent.id}`;
|
|
||||||
await expect(page.getByTestId(expectedActiveTabId).filter({ visible: true })).toHaveAttribute(
|
|
||||||
"aria-selected",
|
|
||||||
"true",
|
|
||||||
);
|
|
||||||
await expect(getActiveTabTestId(page)).resolves.toBe(expectedActiveTabId);
|
|
||||||
|
|
||||||
await pressSettingsToggleShortcut(page);
|
await pressSettingsToggleShortcut(page);
|
||||||
await expect(page).toHaveURL(/\/settings\/general$/);
|
await expect(page).toHaveURL(/\/settings\/general$/);
|
||||||
@@ -98,19 +91,11 @@ test.describe("Settings toggle tab regression", () => {
|
|||||||
await pressSettingsToggleShortcut(page);
|
await pressSettingsToggleShortcut(page);
|
||||||
await expect(page).toHaveURL(buildHostWorkspaceRoute(serverId, repo.path));
|
await expect(page).toHaveURL(buildHostWorkspaceRoute(serverId, repo.path));
|
||||||
await waitForTabBar(page);
|
await waitForTabBar(page);
|
||||||
await expect(page.getByTestId(expectedActiveTabId).filter({ visible: true })).toHaveAttribute(
|
await expectAgentTabActive(page, secondAgent.id);
|
||||||
"aria-selected",
|
|
||||||
"true",
|
|
||||||
);
|
|
||||||
await expect(getActiveTabTestId(page)).resolves.toBe(expectedActiveTabId);
|
|
||||||
|
|
||||||
await page.reload();
|
await page.reload();
|
||||||
await waitForTabBar(page);
|
await waitForTabBar(page);
|
||||||
await expect(page.getByTestId(expectedActiveTabId).filter({ visible: true })).toHaveAttribute(
|
await expectAgentTabActive(page, secondAgent.id);
|
||||||
"aria-selected",
|
|
||||||
"true",
|
|
||||||
);
|
|
||||||
await expect(getActiveTabTestId(page)).resolves.toBe(expectedActiveTabId);
|
|
||||||
} finally {
|
} finally {
|
||||||
for (const agentId of agentIds) {
|
for (const agentId of agentIds) {
|
||||||
await archiveAgentFromDaemon(client, agentId).catch(() => undefined);
|
await archiveAgentFromDaemon(client, agentId).catch(() => undefined);
|
||||||
@@ -152,14 +137,10 @@ test.describe("Settings toggle tab regression", () => {
|
|||||||
agentId: secondAgent.id,
|
agentId: secondAgent.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const expectedActiveTabId = `workspace-tab-agent_${secondAgent.id}`;
|
|
||||||
for (let attempt = 0; attempt < 5; attempt += 1) {
|
for (let attempt = 0; attempt < 5; attempt += 1) {
|
||||||
await page.reload();
|
await page.reload();
|
||||||
await waitForTabBar(page);
|
await waitForTabBar(page);
|
||||||
await expect(
|
await expectAgentTabActive(page, secondAgent.id);
|
||||||
page.getByTestId(expectedActiveTabId).filter({ visible: true }),
|
|
||||||
).toHaveAttribute("aria-selected", "true");
|
|
||||||
await expect(getActiveTabTestId(page)).resolves.toBe(expectedActiveTabId);
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
for (const agentId of agentIds) {
|
for (const agentId of agentIds) {
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import { expect, test } from "./fixtures";
|
import { test } from "./fixtures";
|
||||||
import { clickNewTerminal } from "./helpers/launcher";
|
import { clickNewTerminal } from "./helpers/launcher";
|
||||||
import { setupDeterministicPrompt, waitForTerminalContent } from "./helpers/terminal-perf";
|
import {
|
||||||
|
expectTerminalSurfaceVisible,
|
||||||
|
focusTerminalSurface,
|
||||||
|
typeInTerminal,
|
||||||
|
setupDeterministicPrompt,
|
||||||
|
waitForTerminalContent,
|
||||||
|
} from "./helpers/terminal-perf";
|
||||||
|
|
||||||
test.describe("Workspace cwd correctness", () => {
|
test.describe("Workspace cwd correctness", () => {
|
||||||
test("main checkout workspace opens terminals in the project root", async ({
|
test("main checkout workspace opens terminals in the project root", async ({
|
||||||
@@ -13,13 +19,10 @@ test.describe("Workspace cwd correctness", () => {
|
|||||||
await workspace.navigateTo();
|
await workspace.navigateTo();
|
||||||
await clickNewTerminal(page);
|
await clickNewTerminal(page);
|
||||||
|
|
||||||
const terminal = page.locator('[data-testid="terminal-surface"]');
|
await expectTerminalSurfaceVisible(page);
|
||||||
await expect(terminal.first()).toBeVisible({ timeout: 20_000 });
|
await focusTerminalSurface(page);
|
||||||
await terminal.first().click();
|
|
||||||
|
|
||||||
await setupDeterministicPrompt(page, `PWD_READY_${Date.now()}`);
|
await setupDeterministicPrompt(page, `PWD_READY_${Date.now()}`);
|
||||||
await terminal.first().pressSequentially("pwd\n", { delay: 0 });
|
await typeInTerminal(page, "pwd\n");
|
||||||
|
|
||||||
await waitForTerminalContent(page, (text) => text.includes(workspace.repoPath), 10_000);
|
await waitForTerminalContent(page, (text) => text.includes(workspace.repoPath), 10_000);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -33,12 +36,10 @@ test.describe("Workspace cwd correctness", () => {
|
|||||||
await workspace.navigateTo();
|
await workspace.navigateTo();
|
||||||
await clickNewTerminal(page);
|
await clickNewTerminal(page);
|
||||||
|
|
||||||
const terminal = page.locator('[data-testid="terminal-surface"]');
|
await expectTerminalSurfaceVisible(page);
|
||||||
await expect(terminal.first()).toBeVisible({ timeout: 20_000 });
|
await focusTerminalSurface(page);
|
||||||
await terminal.first().click();
|
|
||||||
|
|
||||||
await setupDeterministicPrompt(page, `PWD_READY_${Date.now()}`);
|
await setupDeterministicPrompt(page, `PWD_READY_${Date.now()}`);
|
||||||
await terminal.first().pressSequentially("pwd\n", { delay: 0 });
|
await typeInTerminal(page, "pwd\n");
|
||||||
await waitForTerminalContent(page, (text) => text.includes(workspace.repoPath), 10_000);
|
await waitForTerminalContent(page, (text) => text.includes(workspace.repoPath), 10_000);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { buildHostAgentDetailRoute, buildHostWorkspaceRoute } from "@/utils/host-routes";
|
import { buildHostAgentDetailRoute, buildHostWorkspaceRoute } from "@/utils/host-routes";
|
||||||
import type { WebSocketRoute } from "@playwright/test";
|
import type { WebSocketRoute } from "@playwright/test";
|
||||||
import { expect, test, type Page } from "./fixtures";
|
import { expect, test, type Page } from "./fixtures";
|
||||||
import { gotoAppShell } from "./helpers/app";
|
import { gotoAppShell, openSettings } from "./helpers/app";
|
||||||
import {
|
import {
|
||||||
archiveAgentFromDaemon,
|
archiveAgentFromDaemon,
|
||||||
connectArchiveTabDaemonClient,
|
connectArchiveTabDaemonClient,
|
||||||
@@ -15,17 +15,26 @@ import {
|
|||||||
connectNewWorkspaceDaemonClient,
|
connectNewWorkspaceDaemonClient,
|
||||||
openProjectViaDaemon,
|
openProjectViaDaemon,
|
||||||
} from "./helpers/new-workspace";
|
} from "./helpers/new-workspace";
|
||||||
|
import { expectComposerVisible } from "./helpers/composer";
|
||||||
import { createTempGitRepo } from "./helpers/workspace";
|
import { createTempGitRepo } from "./helpers/workspace";
|
||||||
import {
|
import {
|
||||||
getVisibleWorkspaceAgentTabIds,
|
getVisibleWorkspaceAgentTabIds,
|
||||||
expectOnlyWorkspaceAgentTabsVisible,
|
expectOnlyWorkspaceAgentTabsVisible,
|
||||||
waitForWorkspaceTabsVisible,
|
waitForWorkspaceTabsVisible,
|
||||||
|
expectWorkspaceTabsAbsent,
|
||||||
} from "./helpers/workspace-tabs";
|
} from "./helpers/workspace-tabs";
|
||||||
import {
|
import {
|
||||||
expectSidebarWorkspaceSelected,
|
expectSidebarWorkspaceSelected,
|
||||||
expectWorkspaceHeader,
|
expectWorkspaceHeader,
|
||||||
|
expectWorkspaceHeaderAbsent,
|
||||||
|
expectMenuButtonVisible,
|
||||||
|
expectHostConnectingOrOffline,
|
||||||
|
expectReconnectingToastVisible,
|
||||||
|
expectReconnectingToastGone,
|
||||||
switchWorkspaceViaSidebar,
|
switchWorkspaceViaSidebar,
|
||||||
waitForSidebarHydration,
|
waitForSidebarHydration,
|
||||||
|
workspaceDeckEntryLocator,
|
||||||
|
expectWorkspaceDeckEntryCount,
|
||||||
} from "./helpers/workspace-ui";
|
} from "./helpers/workspace-ui";
|
||||||
|
|
||||||
const LOADING_WORKSPACE_TEXT_PATTERN = /Loading workspace/i;
|
const LOADING_WORKSPACE_TEXT_PATTERN = /Loading workspace/i;
|
||||||
@@ -62,6 +71,10 @@ async function expectNoLoadingWorkspacePane(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function expectNoLoadingPane(page: Page): Promise<void> {
|
||||||
|
await expect(page.getByText(LOADING_WORKSPACE_TEXT_PATTERN)).toHaveCount(0);
|
||||||
|
}
|
||||||
|
|
||||||
async function installDaemonWebSocketGate(page: Page, daemonPort: string) {
|
async function installDaemonWebSocketGate(page: Page, daemonPort: string) {
|
||||||
let acceptingConnections = true;
|
let acceptingConnections = true;
|
||||||
const activeSockets = new Set<WebSocketRoute>();
|
const activeSockets = new Set<WebSocketRoute>();
|
||||||
@@ -159,35 +172,31 @@ test.describe("Workspace navigation regression", () => {
|
|||||||
title: workspace.workspaceName,
|
title: workspace.workspaceName,
|
||||||
subtitle: workspace.projectDisplayName,
|
subtitle: workspace.projectDisplayName,
|
||||||
});
|
});
|
||||||
await expect(page.getByTestId("workspace-tabs-row")).toBeVisible({ timeout: 30_000 });
|
await waitForWorkspaceTabsVisible(page);
|
||||||
await expectWorkspaceTabVisible(page, agent.id);
|
await expectWorkspaceTabVisible(page, agent.id);
|
||||||
|
|
||||||
await daemonGate.drop();
|
await daemonGate.drop();
|
||||||
await expect(page.getByTestId("agent-reconnecting-toast")).toBeVisible({
|
await expectReconnectingToastVisible(page);
|
||||||
timeout: 30_000,
|
|
||||||
});
|
|
||||||
await expectWorkspaceHeader(page, {
|
await expectWorkspaceHeader(page, {
|
||||||
title: workspace.workspaceName,
|
title: workspace.workspaceName,
|
||||||
subtitle: workspace.projectDisplayName,
|
subtitle: workspace.projectDisplayName,
|
||||||
});
|
});
|
||||||
await expect(page.getByTestId("workspace-tabs-row")).toBeVisible();
|
await waitForWorkspaceTabsVisible(page);
|
||||||
await expect(page.getByRole("textbox", { name: "Message agent..." })).toBeVisible();
|
await expectComposerVisible(page);
|
||||||
await expect(page.getByText(LOADING_WORKSPACE_TEXT_PATTERN)).toHaveCount(0);
|
await expectNoLoadingPane(page);
|
||||||
|
|
||||||
const monitorReconnect = expectNoLoadingWorkspacePane(page, {
|
const monitorReconnect = expectNoLoadingWorkspacePane(page, {
|
||||||
label: "host reconnect",
|
label: "host reconnect",
|
||||||
});
|
});
|
||||||
daemonGate.restore();
|
daemonGate.restore();
|
||||||
await expect(page.getByTestId("agent-reconnecting-toast")).toHaveCount(0, {
|
await expectReconnectingToastGone(page);
|
||||||
timeout: 30_000,
|
|
||||||
});
|
|
||||||
await monitorReconnect;
|
await monitorReconnect;
|
||||||
await expectWorkspaceHeader(page, {
|
await expectWorkspaceHeader(page, {
|
||||||
title: workspace.workspaceName,
|
title: workspace.workspaceName,
|
||||||
subtitle: workspace.projectDisplayName,
|
subtitle: workspace.projectDisplayName,
|
||||||
});
|
});
|
||||||
await expect(page.getByTestId("workspace-tabs-row")).toBeVisible();
|
await waitForWorkspaceTabsVisible(page);
|
||||||
await expect(page.getByRole("textbox", { name: "Message agent..." })).toBeVisible();
|
await expectComposerVisible(page);
|
||||||
} finally {
|
} finally {
|
||||||
daemonGate.restore();
|
daemonGate.restore();
|
||||||
for (const agentId of agentIds) {
|
for (const agentId of agentIds) {
|
||||||
@@ -222,16 +231,11 @@ test.describe("Workspace navigation regression", () => {
|
|||||||
`/h/${encodeURIComponent(serverId)}/workspace/${encodeURIComponent("/tmp/paseo-missing-workspace")}`,
|
`/h/${encodeURIComponent(serverId)}/workspace/${encodeURIComponent("/tmp/paseo-missing-workspace")}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
await expect(
|
await expectHostConnectingOrOffline(page);
|
||||||
page.getByText(/^Connecting$|localhost is offline|Cannot reach localhost/i),
|
await expectMenuButtonVisible(page);
|
||||||
).toBeVisible({ timeout: 30_000 });
|
await expectWorkspaceHeaderAbsent(page);
|
||||||
await expect(page.getByTestId("menu-button")).toBeVisible();
|
await expectWorkspaceTabsAbsent(page);
|
||||||
await expect(page.getByTestId("workspace-header-title")).toHaveCount(0);
|
await openSettings(page);
|
||||||
await expect(page.getByTestId("workspace-tabs-row")).toHaveCount(0);
|
|
||||||
|
|
||||||
const settingsButton = page.locator('[data-testid="sidebar-settings"]:visible').first();
|
|
||||||
await expect(settingsButton).toBeVisible({ timeout: 30_000 });
|
|
||||||
await settingsButton.click();
|
|
||||||
await expect(page).toHaveURL(/\/settings\/general$/);
|
await expect(page).toHaveURL(/\/settings\/general$/);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -270,6 +274,13 @@ test.describe("Workspace navigation regression", () => {
|
|||||||
await waitForSidebarHydration(page);
|
await waitForSidebarHydration(page);
|
||||||
await openWorkspaceWithAgents(page, [firstAgent, secondAgent]);
|
await openWorkspaceWithAgents(page, [firstAgent, secondAgent]);
|
||||||
|
|
||||||
|
const firstDeckEntry = workspaceDeckEntryLocator(page, serverId, firstWorkspace.workspaceId);
|
||||||
|
const secondDeckEntry = workspaceDeckEntryLocator(
|
||||||
|
page,
|
||||||
|
serverId,
|
||||||
|
secondWorkspace.workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
await switchWorkspaceViaSidebar({
|
await switchWorkspaceViaSidebar({
|
||||||
page,
|
page,
|
||||||
serverId,
|
serverId,
|
||||||
@@ -300,13 +311,6 @@ test.describe("Workspace navigation regression", () => {
|
|||||||
await expect(getVisibleWorkspaceAgentTabIds(page)).resolves.toEqual([
|
await expect(getVisibleWorkspaceAgentTabIds(page)).resolves.toEqual([
|
||||||
`workspace-tab-agent_${firstAgent.id}`,
|
`workspace-tab-agent_${firstAgent.id}`,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const firstDeckEntry = page.getByTestId(
|
|
||||||
`workspace-deck-entry-${serverId}:${firstWorkspace.workspaceId}`,
|
|
||||||
);
|
|
||||||
const secondDeckEntry = page.getByTestId(
|
|
||||||
`workspace-deck-entry-${serverId}:${secondWorkspace.workspaceId}`,
|
|
||||||
);
|
|
||||||
await expect(firstDeckEntry).toBeVisible({ timeout: 30_000 });
|
await expect(firstDeckEntry).toBeVisible({ timeout: 30_000 });
|
||||||
|
|
||||||
await switchWorkspaceViaSidebar({
|
await switchWorkspaceViaSidebar({
|
||||||
@@ -342,7 +346,7 @@ test.describe("Workspace navigation regression", () => {
|
|||||||
await expect(firstDeckEntry).toBeAttached();
|
await expect(firstDeckEntry).toBeAttached();
|
||||||
await expect(firstDeckEntry).toBeHidden();
|
await expect(firstDeckEntry).toBeHidden();
|
||||||
await expect(secondDeckEntry).toBeVisible({ timeout: 30_000 });
|
await expect(secondDeckEntry).toBeVisible({ timeout: 30_000 });
|
||||||
await expect(page.locator('[data-testid^="workspace-deck-entry-"]')).toHaveCount(2);
|
await expectWorkspaceDeckEntryCount(page, 2);
|
||||||
|
|
||||||
await page.evaluate(
|
await page.evaluate(
|
||||||
({ agentId, serverId: targetServerId }) => {
|
({ agentId, serverId: targetServerId }) => {
|
||||||
@@ -371,7 +375,7 @@ test.describe("Workspace navigation regression", () => {
|
|||||||
await expectOnlyWorkspaceAgentTabsVisible(page, [secondAgent.id]);
|
await expectOnlyWorkspaceAgentTabsVisible(page, [secondAgent.id]);
|
||||||
await expect(firstDeckEntry).toBeAttached();
|
await expect(firstDeckEntry).toBeAttached();
|
||||||
await expect(firstDeckEntry).toBeHidden();
|
await expect(firstDeckEntry).toBeHidden();
|
||||||
await expect(page.locator('[data-testid^="workspace-deck-entry-"]')).toHaveCount(2);
|
await expectWorkspaceDeckEntryCount(page, 2);
|
||||||
|
|
||||||
await switchWorkspaceViaSidebar({
|
await switchWorkspaceViaSidebar({
|
||||||
page,
|
page,
|
||||||
@@ -385,7 +389,7 @@ test.describe("Workspace navigation regression", () => {
|
|||||||
await expect(firstDeckEntry).toBeVisible({ timeout: 30_000 });
|
await expect(firstDeckEntry).toBeVisible({ timeout: 30_000 });
|
||||||
await expect(secondDeckEntry).toBeAttached();
|
await expect(secondDeckEntry).toBeAttached();
|
||||||
await expect(secondDeckEntry).toBeHidden();
|
await expect(secondDeckEntry).toBeHidden();
|
||||||
await expect(page.locator('[data-testid^="workspace-deck-entry-"]')).toHaveCount(2);
|
await expectWorkspaceDeckEntryCount(page, 2);
|
||||||
|
|
||||||
await page.reload();
|
await page.reload();
|
||||||
await waitForSidebarHydration(page);
|
await waitForSidebarHydration(page);
|
||||||
|
|||||||
@@ -1,32 +1,16 @@
|
|||||||
import { existsSync } from "node:fs";
|
import { existsSync } from "node:fs";
|
||||||
import { expect, test } from "./fixtures";
|
import { expect, test } from "./fixtures";
|
||||||
import { createTempGitRepo } from "./helpers/workspace";
|
import { createTempGitRepo } from "./helpers/workspace";
|
||||||
import { clickNewTerminal, waitForTabBar } from "./helpers/launcher";
|
import { clickNewTerminal } from "./helpers/launcher";
|
||||||
|
import { expectTerminalSurfaceVisible } from "./helpers/terminal-perf";
|
||||||
import {
|
import {
|
||||||
connectWorkspaceSetupClient,
|
connectWorkspaceSetupClient,
|
||||||
createWorkspaceThroughDaemon,
|
createWorkspaceThroughDaemon,
|
||||||
findWorktreeWorkspaceForProject,
|
findWorktreeWorkspaceForProject,
|
||||||
|
navigateToWorkspaceViaSidebar,
|
||||||
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 });
|
||||||
|
|
||||||
@@ -48,7 +32,6 @@ test.describe("Workspace setup runtime authority", () => {
|
|||||||
expect(wsInfo.workspaceDirectory).not.toBe(repo.path);
|
expect(wsInfo.workspaceDirectory).not.toBe(repo.path);
|
||||||
expect(existsSync(wsInfo.workspaceDirectory)).toBe(true);
|
expect(existsSync(wsInfo.workspaceDirectory)).toBe(true);
|
||||||
|
|
||||||
// Navigate to the workspace via sidebar
|
|
||||||
await openHomeWithProject(page, repo.path);
|
await openHomeWithProject(page, repo.path);
|
||||||
await navigateToWorkspaceViaSidebar(page, workspaceId);
|
await navigateToWorkspaceViaSidebar(page, workspaceId);
|
||||||
await expect(page).toHaveURL(/\/workspace\//, { timeout: 30_000 });
|
await expect(page).toHaveURL(/\/workspace\//, { timeout: 30_000 });
|
||||||
@@ -87,9 +70,7 @@ test.describe("Workspace setup runtime authority", () => {
|
|||||||
await navigateToWorkspaceViaSidebar(page, workspaceId);
|
await navigateToWorkspaceViaSidebar(page, workspaceId);
|
||||||
|
|
||||||
await clickNewTerminal(page);
|
await clickNewTerminal(page);
|
||||||
|
await expectTerminalSurfaceVisible(page);
|
||||||
const terminal = page.locator('[data-testid="terminal-surface"]');
|
|
||||||
await expect(terminal.first()).toBeVisible({ timeout: 20_000 });
|
|
||||||
|
|
||||||
// Verify terminal is listed under the worktree directory, not the original repo
|
// Verify terminal is listed under the worktree directory, not the original repo
|
||||||
await expect
|
await expect
|
||||||
|
|||||||
@@ -1,23 +1,28 @@
|
|||||||
import { test, expect } from "./fixtures";
|
import { test, expect } from "./fixtures";
|
||||||
import { createTempGitRepo } from "./helpers/workspace";
|
import { createTempGitRepo } from "./helpers/workspace";
|
||||||
import { waitForWorkspaceTabsVisible } from "./helpers/workspace-tabs";
|
import {
|
||||||
|
waitForWorkspaceTabsVisible,
|
||||||
|
expectNoTerminalTabs,
|
||||||
|
clickFirstTerminalTab,
|
||||||
|
expectFirstTerminalTabContains,
|
||||||
|
} from "./helpers/workspace-tabs";
|
||||||
|
import { clickNewChat } from "./helpers/launcher";
|
||||||
|
import { expectComposerVisible } from "./helpers/composer";
|
||||||
|
import { openFileExplorer, expectExplorerEntryVisible } from "./helpers/file-explorer";
|
||||||
|
import { expectTerminalSurfaceVisible, waitForTerminalAttached } from "./helpers/terminal-perf";
|
||||||
import {
|
import {
|
||||||
connectWorkspaceSetupClient,
|
connectWorkspaceSetupClient,
|
||||||
createWorkspaceThroughDaemon,
|
createWorkspaceThroughDaemon,
|
||||||
expectSetupPanel,
|
expectSetupPanel,
|
||||||
openHomeWithProject,
|
openHomeWithProject,
|
||||||
|
navigateToWorkspaceViaSidebar,
|
||||||
|
openWorkspaceScriptsMenu,
|
||||||
|
startWorkspaceScriptFromMenu,
|
||||||
|
closeWorkspaceScriptsMenu,
|
||||||
seedProjectForWorkspaceSetup,
|
seedProjectForWorkspaceSetup,
|
||||||
waitForWorkspaceSetupProgress,
|
waitForWorkspaceSetupProgress,
|
||||||
} 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface WorkspaceScriptStarter {
|
interface WorkspaceScriptStarter {
|
||||||
startWorkspaceScript(
|
startWorkspaceScript(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
@@ -30,18 +35,6 @@ interface WorkspaceScriptStarter {
|
|||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Click the sidebar row for a workspace (by ID) and wait for navigation. */
|
|
||||||
async function navigateToWorkspaceViaSidebar(
|
|
||||||
page: import("@playwright/test").Page,
|
|
||||||
workspaceId: string,
|
|
||||||
): Promise<void> {
|
|
||||||
const testId = `sidebar-workspace-row-${getServerId()}:${workspaceId}`;
|
|
||||||
const row = page.getByTestId(testId);
|
|
||||||
await expect(row).toBeVisible({ timeout: 30_000 });
|
|
||||||
await row.click();
|
|
||||||
await expect(page).toHaveURL(/\/workspace\//, { timeout: 30_000 });
|
|
||||||
}
|
|
||||||
|
|
||||||
test.describe("Workspace setup streaming", () => {
|
test.describe("Workspace setup streaming", () => {
|
||||||
test("opens the setup tab when a workspace is created from the sidebar", async ({ page }) => {
|
test("opens the setup tab when a workspace is created from the sidebar", async ({ page }) => {
|
||||||
const client = await connectWorkspaceSetupClient();
|
const client = await connectWorkspaceSetupClient();
|
||||||
@@ -100,31 +93,15 @@ test.describe("Workspace setup streaming", () => {
|
|||||||
});
|
});
|
||||||
await completed;
|
await completed;
|
||||||
|
|
||||||
// Navigate to workspace and verify it's usable
|
|
||||||
await openHomeWithProject(page, repo.path);
|
await openHomeWithProject(page, repo.path);
|
||||||
await navigateToWorkspaceViaSidebar(page, workspace.id);
|
await navigateToWorkspaceViaSidebar(page, workspace.id);
|
||||||
|
|
||||||
await waitForWorkspaceTabsVisible(page);
|
await waitForWorkspaceTabsVisible(page);
|
||||||
await page.getByTestId("workspace-new-agent-tab").filter({ visible: true }).first().click();
|
await clickNewChat(page);
|
||||||
await expect(page.getByRole("textbox", { name: "Message agent..." }).first()).toBeVisible({
|
await expectComposerVisible(page, { timeout: 30_000 });
|
||||||
timeout: 30_000,
|
await openFileExplorer(page);
|
||||||
});
|
await expectExplorerEntryVisible(page, "README.md");
|
||||||
|
await expectExplorerEntryVisible(page, "src");
|
||||||
const explorerToggle = page.getByTestId("workspace-explorer-toggle").first();
|
|
||||||
if ((await explorerToggle.getAttribute("aria-label")) === "Open explorer") {
|
|
||||||
await explorerToggle.click();
|
|
||||||
}
|
|
||||||
await expect(explorerToggle).toHaveAttribute("aria-label", "Close explorer", {
|
|
||||||
timeout: 30_000,
|
|
||||||
});
|
|
||||||
await page.getByTestId("explorer-tab-files").click();
|
|
||||||
await expect(page.getByTestId("file-explorer-tree-scroll")).toBeVisible({ timeout: 30_000 });
|
|
||||||
await expect(page.getByText("README.md", { exact: true }).first()).toBeVisible({
|
|
||||||
timeout: 30_000,
|
|
||||||
});
|
|
||||||
await expect(page.getByText("src", { exact: true }).first()).toBeVisible({
|
|
||||||
timeout: 30_000,
|
|
||||||
});
|
|
||||||
} finally {
|
} finally {
|
||||||
await client.close();
|
await client.close();
|
||||||
await repo.cleanup();
|
await repo.cleanup();
|
||||||
@@ -273,33 +250,14 @@ test.describe("Workspace setup streaming", () => {
|
|||||||
await navigateToWorkspaceViaSidebar(page, workspace.id);
|
await navigateToWorkspaceViaSidebar(page, workspace.id);
|
||||||
|
|
||||||
await waitForWorkspaceTabsVisible(page);
|
await waitForWorkspaceTabsVisible(page);
|
||||||
|
await expectNoTerminalTabs(page);
|
||||||
await expect(page.locator('[data-testid^="workspace-tab-terminal_"]')).toHaveCount(0);
|
await openWorkspaceScriptsMenu(page);
|
||||||
await page.getByTestId("workspace-scripts-button").click();
|
await startWorkspaceScriptFromMenu(page, "web");
|
||||||
await expect(page.getByTestId("workspace-scripts-menu")).toBeVisible({ timeout: 10_000 });
|
await closeWorkspaceScriptsMenu(page);
|
||||||
await page.getByTestId("workspace-scripts-start-web").click();
|
await clickFirstTerminalTab(page);
|
||||||
await page.getByTestId("workspace-scripts-menu-backdrop").click();
|
await expectTerminalSurfaceVisible(page, { timeout: 10_000 });
|
||||||
|
await waitForTerminalAttached(page);
|
||||||
const terminalTab = page.locator('[data-testid^="workspace-tab-terminal_"]').first();
|
await expectFirstTerminalTabContains(page, "web");
|
||||||
await expect(terminalTab).toBeVisible({ timeout: 30_000 });
|
|
||||||
|
|
||||||
await terminalTab.click();
|
|
||||||
|
|
||||||
// Verify the terminal surface rendered
|
|
||||||
const terminalSurface = page.getByTestId("terminal-surface").first();
|
|
||||||
await expect(terminalSurface).toBeVisible({ timeout: 10_000 });
|
|
||||||
|
|
||||||
// Wait for terminal to fully attach (loading overlay gone)
|
|
||||||
await page
|
|
||||||
.locator('[data-testid="terminal-attach-loading"]')
|
|
||||||
.waitFor({ state: "hidden", timeout: 10_000 })
|
|
||||||
.catch(() => {
|
|
||||||
// overlay may never appear if attachment is instant
|
|
||||||
});
|
|
||||||
|
|
||||||
await terminalSurface.click();
|
|
||||||
|
|
||||||
await expect(terminalTab).toContainText("web");
|
|
||||||
} finally {
|
} finally {
|
||||||
await client.close();
|
await client.close();
|
||||||
await repo.cleanup();
|
await repo.cleanup();
|
||||||
|
|||||||
Reference in New Issue
Block a user