Files
paseo/packages/app/e2e/browser/settings-toggle-tab-regression.spec.ts
a54cc6dd11 chore(acp): refresh pinned provider packages (#7781)
* chore(acp): refresh pinned provider packages

Co-authored-by: Kim <androidj@qq.com>
Co-authored-by: Louis Deconinck <louis.dck@gmail.com>

* test(app): capture the visible composer across retained tabs

The settings regression sampler selected the first mounted composer even when it belonged to an inactive retained tab. Mount both tabs before capturing so the test consistently covers that case, and sample the visible composer.

---------

Co-authored-by: Kim <androidj@qq.com>
Co-authored-by: Louis Deconinck <louis.dck@gmail.com>
2026-10-24 12:36:10 +07:00

277 lines
9.7 KiB
TypeScript

import { buildHostAgentDetailRoute, buildHostWorkspaceRoute } from "@/utils/host-routes";
import { expect, test } from "../support/fixtures";
import { createMockIdleAgent, openWorkspaceWithAgents } from "../support/helpers/archive-tab";
import { waitForTabBar, expectAgentTabActive } from "../support/helpers/launcher";
import { seedWorkspace } from "../support/helpers/seed-client";
import { getServerId } from "../support/helpers/server-id";
async function pressSettingsToggleShortcut(page: import("@playwright/test").Page) {
const modifier = process.platform === "darwin" ? "Meta" : "Control";
await page.keyboard.press(`${modifier}+Comma`);
}
async function openComposerTab(page: import("@playwright/test").Page, agentId: string) {
await page
.getByTestId(`workspace-tab-agent_${agentId}`)
.filter({ visible: true })
.first()
.click();
await expectAgentTabActive(page, agentId);
await expect(page.getByRole("textbox", { name: "Message agent..." })).toBeVisible();
}
interface ComposerFrame {
at: number;
visible: boolean;
rootHeight: number;
inputHeight: number;
inputWidth: number;
inputStyleHeight: string;
inputStyleMaxHeight: string;
}
async function startComposerFrameCapture(page: import("@playwright/test").Page) {
await page.evaluate(() => {
const capture = { frames: [] as ComposerFrame[], stopped: false };
Object.assign(window, { __composerFrameCapture: capture });
const sample = () => {
if (capture.stopped) return;
const root = Array.from(
document.querySelectorAll<HTMLElement>('[data-testid="message-input-root"]'),
).find((candidate) => candidate.offsetParent !== null);
const input = root?.querySelector<HTMLTextAreaElement>("textarea");
if (root && input) {
const style = getComputedStyle(input);
capture.frames.push({
at: performance.now(),
visible: root.offsetParent !== null,
rootHeight: root.getBoundingClientRect().height,
inputHeight: input.getBoundingClientRect().height,
inputWidth: input.clientWidth,
inputStyleHeight: style.height,
inputStyleMaxHeight: style.maxHeight,
});
}
requestAnimationFrame(sample);
};
requestAnimationFrame(sample);
});
}
async function stopComposerFrameCapture(
page: import("@playwright/test").Page,
): Promise<ComposerFrame[]> {
return page.evaluate(() => {
const capture = (
window as typeof window & {
__composerFrameCapture?: { frames: ComposerFrame[]; stopped: boolean };
}
).__composerFrameCapture;
if (!capture) throw new Error("Composer frame capture was not installed");
capture.stopped = true;
return capture.frames;
});
}
async function expectSendBehavior(
page: import("@playwright/test").Page,
expected: "interrupt" | "queue" | "steer",
) {
await expect
.poll(async () => {
const raw = await page.evaluate(() => localStorage.getItem("@paseo:app-settings"));
if (!raw) {
return null;
}
return (JSON.parse(raw) as { sendBehavior?: string }).sendBehavior ?? null;
})
.toBe(expected);
}
async function openAgentRouteAndExpectFocused(input: {
page: import("@playwright/test").Page;
serverId: string;
workspaceId: string;
agentId: string;
}) {
await input.page.goto(
buildHostAgentDetailRoute(input.serverId, input.agentId, input.workspaceId),
);
await input.page.waitForURL(
(url) => url.pathname.includes("/workspace/") && !url.searchParams.has("open"),
{ timeout: 60_000 },
);
await waitForTabBar(input.page);
await expectAgentTabActive(input.page, input.agentId);
}
test.describe("Settings toggle tab regression", () => {
test.describe.configure({ timeout: 180_000 });
test("toggling settings after changing a setting returns to the same workspace tab", async ({
page,
}) => {
const serverId = getServerId();
const workspace = await seedWorkspace({ repoPrefix: "settings-toggle-tab-" });
try {
const firstAgent = await createMockIdleAgent(workspace.client, {
cwd: workspace.repoPath,
workspaceId: workspace.workspaceId,
title: `settings-toggle-a-${Date.now()}`,
});
const secondAgent = await createMockIdleAgent(workspace.client, {
cwd: workspace.repoPath,
workspaceId: workspace.workspaceId,
title: `settings-toggle-b-${Date.now()}`,
});
await openWorkspaceWithAgents(page, [firstAgent, secondAgent]);
await waitForTabBar(page);
await expectAgentTabActive(page, secondAgent.id);
await pressSettingsToggleShortcut(page);
await expect(page).toHaveURL(/\/settings\/general$/);
const defaultSendTrigger = page.getByRole("button", {
name: "Default send: Steer",
exact: true,
});
await expect(defaultSendTrigger).toBeVisible();
await expect(page.getByRole("menuitem", { name: "Queue", exact: true })).toHaveCount(0);
await defaultSendTrigger.click();
await expect(page.getByRole("menuitem", { name: "Steer", exact: true })).toHaveAttribute(
"aria-checked",
"true",
);
await page.getByRole("menuitem", { name: "Queue", exact: true }).click();
await expectSendBehavior(page, "queue");
const queuedDefaultSendTrigger = page.getByRole("button", {
name: "Default send: Queue",
exact: true,
});
await expect(queuedDefaultSendTrigger).toBeVisible();
await queuedDefaultSendTrigger.click();
await page.getByRole("menuitem", { name: "Interrupt", exact: true }).click();
await expectSendBehavior(page, "interrupt");
await pressSettingsToggleShortcut(page);
await expect(page).toHaveURL(buildHostWorkspaceRoute(serverId, workspace.workspaceId));
await waitForTabBar(page);
await expectAgentTabActive(page, secondAgent.id);
await page.reload();
await waitForTabBar(page);
await expectAgentTabActive(page, secondAgent.id);
} finally {
await workspace.cleanup();
}
});
test("returning from settings does not flash the composer at maximum height", async ({
page,
}) => {
const workspace = await seedWorkspace({ repoPrefix: "settings-composer-height-" });
try {
const firstAgent = await createMockIdleAgent(workspace.client, {
cwd: workspace.repoPath,
workspaceId: workspace.workspaceId,
title: `settings-composer-height-a-${Date.now()}`,
});
const secondAgent = await createMockIdleAgent(workspace.client, {
cwd: workspace.repoPath,
workspaceId: workspace.workspaceId,
title: `settings-composer-height-b-${Date.now()}`,
});
await openWorkspaceWithAgents(page, [firstAgent, secondAgent]);
await waitForTabBar(page);
await openComposerTab(page, firstAgent.id);
await openComposerTab(page, secondAgent.id);
const input = page.getByRole("textbox", { name: "Message agent..." });
await input.fill("Keep this short draft in the composer");
const root = page.getByTestId("message-input-root").filter({ visible: true }).first();
const baselineHeight = (await root.boundingBox())?.height;
expect(baselineHeight).toBeDefined();
await startComposerFrameCapture(page);
await pressSettingsToggleShortcut(page);
await expect(page).toHaveURL(/\/settings\/general$/);
await pressSettingsToggleShortcut(page);
await expect(page).not.toHaveURL(/\/settings(\/|$)/);
await expect(input).toHaveValue("Keep this short draft in the composer");
await page.waitForTimeout(250);
const frames = await stopComposerFrameCapture(page);
const visibleFrames = frames.filter((frame) => frame.visible);
expect(visibleFrames.length, "Capture must observe the visible composer").toBeGreaterThan(0);
const tallestIndex = visibleFrames.reduce(
(index, frame, candidateIndex) =>
frame.rootHeight > visibleFrames[index].rootHeight ? candidateIndex : index,
0,
);
const tallestFrame = visibleFrames[tallestIndex];
const tallestFrameIndex = frames.findIndex((frame) => frame.at === tallestFrame.at);
expect(
tallestFrame.rootHeight,
JSON.stringify(
{
baselineHeight,
tallestFrame,
nearbyFrames: frames.slice(Math.max(0, tallestFrameIndex - 3), tallestFrameIndex + 4),
},
null,
2,
),
).toBeLessThanOrEqual((baselineHeight ?? 0) + 5);
} finally {
await workspace.cleanup();
}
});
test("refresh after navigating between agent routes keeps the latest agent focused", async ({
page,
}) => {
const serverId = getServerId();
const workspace = await seedWorkspace({ repoPrefix: "agent-route-refresh-" });
try {
const firstAgent = await createMockIdleAgent(workspace.client, {
cwd: workspace.repoPath,
workspaceId: workspace.workspaceId,
title: `agent-route-refresh-a-${Date.now()}`,
});
const secondAgent = await createMockIdleAgent(workspace.client, {
cwd: workspace.repoPath,
workspaceId: workspace.workspaceId,
title: `agent-route-refresh-b-${Date.now()}`,
});
await openAgentRouteAndExpectFocused({
page,
serverId,
workspaceId: workspace.workspaceId,
agentId: firstAgent.id,
});
await openAgentRouteAndExpectFocused({
page,
serverId,
workspaceId: workspace.workspaceId,
agentId: secondAgent.id,
});
for (let attempt = 0; attempt < 5; attempt += 1) {
await page.reload();
await waitForTabBar(page);
await expectAgentTabActive(page, secondAgent.id);
}
} finally {
await workspace.cleanup();
}
});
});