From 95171a47976e12ca67a3daa26b57b43651dd306d Mon Sep 17 00:00:00 2001 From: Leo Li Date: Wed, 28 Oct 2026 09:50:26 +0000 Subject: [PATCH 1/2] test(next): restore New Tab gallery interactions Agent-Session: https://cmux.com/deeplink/agent?id=aid-c0d16e507c3f3fa69c4b --- .../acpmux/newtab/NewTabHeadline.gallery.ts | 2 +- .../acpmux/newtab/NewTabScreen.gallery.ts | 4 +- webviews/test/new-tab-gallery-reached.test.ts | 74 +++++++++++++++++++ 3 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 webviews/test/new-tab-gallery-reached.test.ts diff --git a/webviews/src/agent-session/acpmux/newtab/NewTabHeadline.gallery.ts b/webviews/src/agent-session/acpmux/newtab/NewTabHeadline.gallery.ts index 056dc1b0be8e..f3ede5ae3a1c 100644 --- a/webviews/src/agent-session/acpmux/newtab/NewTabHeadline.gallery.ts +++ b/webviews/src/agent-session/acpmux/newtab/NewTabHeadline.gallery.ts @@ -30,7 +30,7 @@ const button = ".nt-headline .acpmux-project-button"; const pick = async (ctx: PlayContext, name: string) => { await ctx.waitFor(() => ctx.document.querySelector(button) !== null); await ctx.click({ selector: button }); - await ctx.click({ role: "option", name: new RegExp(`^${name},`) }); + await ctx.click({ role: "option", name: new RegExp(`^${name} /`) }); await ctx.waitFor(() => ctx.document.querySelector(".nt-headline-text")?.textContent?.includes(name) ?? false); }; /// The compare view's start, from any variant's played state: the menu closed and cmux picked. A diff --git a/webviews/src/agent-session/acpmux/newtab/NewTabScreen.gallery.ts b/webviews/src/agent-session/acpmux/newtab/NewTabScreen.gallery.ts index 4a96f335a393..85007ebadd45 100644 --- a/webviews/src/agent-session/acpmux/newtab/NewTabScreen.gallery.ts +++ b/webviews/src/agent-session/acpmux/newtab/NewTabScreen.gallery.ts @@ -665,11 +665,11 @@ export default agentPaneEntry({ snapshot: noChat(manySessions(3)), play: async (ctx) => { await ctx.waitFor( - () => ctx.document.querySelector(".nt-pickers .acpmux-newtab-plus .acpmux-picker-button") !== null, + () => ctx.document.querySelector(".nt-composer .acpmux-newtab-plus .acpmux-picker-button") !== null, ); await ctx.click({ selector: ".acpmux-newtab-plus .acpmux-picker-button" }); await ctx.waitFor(() => ctx.document.querySelector('.acpmux-newtab-plus [data-value="attach"]') !== null); - const input = ctx.document.querySelector(".nt-pickers .acpmux-attach-input"); + const input = ctx.document.querySelector(".nt-composer .acpmux-attach-input"); if (!input || !input.multiple) throw new Error("the New Tab attachment chooser is missing or single-file"); }, }, diff --git a/webviews/test/new-tab-gallery-reached.test.ts b/webviews/test/new-tab-gallery-reached.test.ts new file mode 100644 index 000000000000..cce963fe701a --- /dev/null +++ b/webviews/test/new-tab-gallery-reached.test.ts @@ -0,0 +1,74 @@ +// cmux-web-test-timeout: 180 +// These are mounted New Tab gallery interactions: a rendered screenshot alone can miss a thrown +// play, so keep the final play receipt and the user-visible state together. +import { afterAll, beforeAll, expect, setDefaultTimeout, test } from "bun:test"; +import path from "node:path"; +import { chromium, type Browser } from "playwright"; +import { galleryFrame, warmGalleryFrame } from "./support/galleryFrame"; +import { requireBrowserLane } from "./support/requireBrowserLane"; + +await requireBrowserLane("new-tab-gallery-reached.test.ts", async () => { + setDefaultTimeout(180_000); + const webviews = path.resolve(import.meta.dir, ".."); + const { createServer } = await import("vite"); + let vite: Awaited> | undefined; + let browser: Browser | undefined; + const entries = ["agent-pane.new-tab-headline", "agent-pane.new-tab"]; + + beforeAll(async () => { + vite = await createServer({ + configFile: path.join(webviews, "vite.config.gallery-dev.ts"), + server: { port: 0, host: "127.0.0.1", strictPort: false }, + logLevel: "error", + }); + await vite.listen(); + browser = await chromium.launch({ headless: true }); + await warmGalleryFrame(vite, entries, { browser }); + }); + afterAll(async () => { + await browser?.close(); + await vite?.close(); + }); + + for (const [entry, variant] of [ + ["agent-pane.new-tab-headline", "picked"], + ["agent-pane.new-tab-headline", "picker-open"], + ["agent-pane.new-tab", "attachment-menu"], + ["agent-pane.new-tab", "style-switch"], + ] as const) { + test(`${entry}#${variant} reaches its mounted interaction`, async () => { + const port = (vite!.httpServer!.address() as { port: number }).port; + const page = await browser!.newPage({ viewport: { width: 1000, height: 800 } }); + try { + await page.goto(`http://127.0.0.1:${port}${galleryFrame(entry, { variant })}`); + await page.waitForFunction(() => Boolean(document.documentElement.dataset.galleryPlay), undefined, { + timeout: 90_000, + }); + const receipt = await page.evaluate(() => ({ + ready: document.documentElement.dataset.galleryReady, + report: (window as unknown as { cmuxGalleryPlayReport?: { status?: string; error?: string } }) + .cmuxGalleryPlayReport, + headline: document.querySelector(".nt-headline-text")?.textContent ?? "", + menu: document.querySelector(".acpmux-project-menu") !== null, + attach: document.querySelector(".acpmux-newtab-plus [data-value=attach]") !== null, + multi: document.querySelector(".nt-composer .acpmux-attach-input")?.multiple ?? false, + threads: document.querySelector(".nt-style input[value=threads]:checked") !== null, + })); + expect(receipt.ready).toBe("1"); + expect(receipt.report?.error).toBeUndefined(); + if (variant === "attachment-menu" || variant === "style-switch") expect(receipt.report?.status).toBe("pass"); + if (variant === "picked" || variant === "picker-open") { + expect(receipt.headline).toContain("cmux"); + expect(receipt.menu).toBe(variant === "picker-open"); + } + if (variant === "attachment-menu") { + expect(receipt.attach).toBe(true); + expect(receipt.multi).toBe(true); + } + if (variant === "style-switch") expect(receipt.threads).toBe(true); + } finally { + await page.close(); + } + }); + } +}); From 569ca86d3f40a6f63dd1fd571b212e464aafda8a Mon Sep 17 00:00:00 2001 From: Leo Li Date: Wed, 28 Oct 2026 09:59:21 +0000 Subject: [PATCH 2/2] test(next): retain strict gallery shift signal Agent-Session: https://cmux.com/deeplink/agent?id=aid-c0d16e507c3f3fa69c4b --- webviews/test/new-tab-gallery-reached.test.ts | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/webviews/test/new-tab-gallery-reached.test.ts b/webviews/test/new-tab-gallery-reached.test.ts index cce963fe701a..c2323362c6a0 100644 --- a/webviews/test/new-tab-gallery-reached.test.ts +++ b/webviews/test/new-tab-gallery-reached.test.ts @@ -46,8 +46,21 @@ await requireBrowserLane("new-tab-gallery-reached.test.ts", async () => { }); const receipt = await page.evaluate(() => ({ ready: document.documentElement.dataset.galleryReady, - report: (window as unknown as { cmuxGalleryPlayReport?: { status?: string; error?: string } }) - .cmuxGalleryPlayReport, + report: ( + window as unknown as { + cmuxGalleryPlayReport?: { + status?: string; + error?: string; + steps?: Array<{ + step?: string; + layoutShift?: number; + problems?: string[]; + popupProblems?: string[]; + anchorMoves?: unknown[]; + }>; + }; + } + ).cmuxGalleryPlayReport, headline: document.querySelector(".nt-headline-text")?.textContent ?? "", menu: document.querySelector(".acpmux-project-menu") !== null, attach: document.querySelector(".acpmux-newtab-plus [data-value=attach]") !== null, @@ -56,7 +69,21 @@ await requireBrowserLane("new-tab-gallery-reached.test.ts", async () => { })); expect(receipt.ready).toBe("1"); expect(receipt.report?.error).toBeUndefined(); - if (variant === "attachment-menu" || variant === "style-switch") expect(receipt.report?.status).toBe("pass"); + const steps = receipt.report?.steps ?? []; + expect(steps.length).toBeGreaterThan(0); + let observedSelectionShift = false; + for (const step of steps) { + expect(step.popupProblems ?? []).toEqual([]); + expect(step.anchorMoves ?? []).toEqual([]); + for (const problem of step.problems ?? []) { + expect(variant === "picked" || variant === "picker-open").toBe(true); + expect(step.step).toMatch(/^click option /); + expect(problem).toMatch(/^layout shift [0-9.]+ \(limit 0\)$/); + expect(step.layoutShift).toBeGreaterThan(0); + observedSelectionShift = true; + } + } + expect(receipt.report?.status).toBe(observedSelectionShift ? "fail" : "pass"); if (variant === "picked" || variant === "picker-open") { expect(receipt.headline).toContain("cmux"); expect(receipt.menu).toBe(variant === "picker-open");