





















11// Control UI tests cover chat picker pagination behavior.
2+import { mkdir } from "node:fs/promises";
3+import path from "node:path";
24import { chromium, type Browser } from "playwright";
35import { afterAll, beforeAll, describe, expect, it } from "vitest";
46import {
@@ -19,7 +21,12 @@ const describeControlUiE2e = chromiumAvailable || !allowMissingChromium ? descri
1921let browser: Browser;
2022let server: ControlUiE2eServer;
212322-function sessionRow(key: string, label: string, updatedAt: number) {
24+function sessionRow(
25+key: string,
26+label: string,
27+updatedAt: number,
28+options: { spawnedBy?: string } = {},
29+) {
2330return {
2431contextTokens: null,
2532displayName: label,
@@ -32,6 +39,7 @@ function sessionRow(key: string, label: string, updatedAt: number) {
3239status: "done",
3340totalTokens: 0,
3441 updatedAt,
42+ ...(options.spawnedBy ? { spawnedBy: options.spawnedBy } : {}),
3543};
3644}
3745@@ -220,4 +228,115 @@ describeControlUiE2e("Control UI chat picker mocked Gateway E2E", () => {
220228await context.close();
221229}
222230});
231+232+it("skips hidden subagent-only pages when loading more chat sessions through the GUI", async () => {
233+const baseTime = Date.parse("2026-06-04T12:00:00.000Z");
234+const context = await browser.newContext({
235+locale: "en-US",
236+serviceWorkers: "block",
237+viewport: { height: 900, width: 1280 },
238+});
239+const page = await context.newPage();
240+const gateway = await installMockGateway(page, {
241+methodResponses: {
242+"sessions.list": {
243+cases: [
244+{
245+match: { offset: 4 },
246+response: sessionsListResponse(
247+[sessionRow("agent:main:work", "Main work", baseTime - 240_000)],
248+{ hasMore: false, nextOffset: null, offset: 4, totalCount: 177 },
249+),
250+},
251+{
252+match: { offset: 2 },
253+response: sessionsListResponse(
254+[
255+sessionRow(
256+"agent:main:spawn-child:second",
257+"Subagent second",
258+baseTime - 120_000,
259+{ spawnedBy: "agent:main:main" },
260+),
261+sessionRow("agent:main:spawn-child:third", "Subagent third", baseTime - 180_000, {
262+spawnedBy: "agent:main:main",
263+}),
264+],
265+{ hasMore: true, nextOffset: 4, offset: 2, totalCount: 177 },
266+),
267+},
268+{
269+match: {},
270+response: sessionsListResponse(
271+[
272+sessionRow("agent:main:main", "Main chat", baseTime - 60_000),
273+sessionRow("agent:main:spawn-child:first", "Subagent first", baseTime - 90_000, {
274+spawnedBy: "agent:main:main",
275+}),
276+],
277+{ hasMore: true, nextOffset: 2, totalCount: 177 },
278+),
279+},
280+],
281+},
282+},
283+sessionKey: "agent:main:main",
284+});
285+286+try {
287+await page.goto(`${server.baseUrl}chat`);
288+await page.getByRole("button", { name: "Chat session" }).click();
289+290+await page.getByRole("option", { name: /Main chat/u }).waitFor({ timeout: 10_000 });
291+await page.getByText("1", { exact: true }).waitFor({ timeout: 10_000 });
292+await expect
293+.poll(() => page.getByRole("option", { name: /Subagent first/u }).count())
294+.toBe(0);
295+296+await page.getByRole("button", { name: "Load more sessions" }).click();
297+298+const hiddenPageRequest = await waitForSessionsRequest(
299+gateway,
300+(params) => params.offset === 2,
301+);
302+expect(requestParams(hiddenPageRequest)).toMatchObject({
303+configuredAgentsOnly: true,
304+includeGlobal: true,
305+includeUnknown: true,
306+limit: 50,
307+offset: 2,
308+});
309+const visiblePageRequest = await waitForSessionsRequest(
310+gateway,
311+(params) => params.offset === 4,
312+);
313+expect(requestParams(visiblePageRequest)).toMatchObject({
314+configuredAgentsOnly: true,
315+includeGlobal: true,
316+includeUnknown: true,
317+limit: 50,
318+offset: 4,
319+});
320+321+await page.getByRole("option", { name: /Main work/u }).waitFor({ timeout: 10_000 });
322+await page.getByText("2", { exact: true }).waitFor({ timeout: 10_000 });
323+await expect
324+.poll(() => page.getByRole("option", { name: /Subagent second/u }).count())
325+.toBe(0);
326+await expect
327+.poll(() => page.getByRole("button", { name: "Load more sessions" }).count())
328+.toBe(0);
329+330+if (process.env.OPENCLAW_CAPTURE_UI_PROOF === "1") {
331+const artifactDir = path.join(process.cwd(), ".artifacts", "pr-89323-control-ui-proof");
332+await mkdir(artifactDir, { recursive: true });
333+await page.screenshot({
334+fullPage: true,
335+path: path.join(artifactDir, "chat-picker-hidden-page-skip.png"),
336+});
337+}
338+} finally {
339+await context.close();
340+}
341+});
223342});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。