diff --git a/tests/ui-tests.mjs b/tests/ui-tests.mjs index d6d2ae0..d26ef86 100644 --- a/tests/ui-tests.mjs +++ b/tests/ui-tests.mjs @@ -138,6 +138,84 @@ test("welcome: first launch shows the welcome page", async (app) => { } }); +// --- Welcome page (A2) — must run right after A1: navigating clicks the +// welcome page away --- +test('welcome: "Install now" navigates to Applications', async (app) => { + // Prefer the spec objectName; fall back to the button text until + // "welcomePage.installNow" exists in WelcomePage.qml. + let button = null; + await app.waitFor(async () => { + const byName = await app.findByProperty("objectName", "welcomePage.installNow"); + button = (byName.matches ?? [])[0] || null; + if (!button) { + const byText = await app.findByProperty("text", "Install now"); + button = (byText.matches ?? []).find((m) => (m.type ?? "").includes("Button")) || null; + } + if (!button) throw new Error('"Install now" button not found on the welcome page'); + }, { timeout: 10000, interval: 500, description: '"Install now" button to exist' }); + + // Signal-level click — coordinate hit-testing on offscreen is fragile + // (see installViaPmu); the onClicked handler chain is identical. + const clicked = await app.inspector.send("callMethod", { + objectId: button.id, method: "clicked", + }); + if (clicked.error) throw new Error(`callMethod(clicked) failed: ${clicked.error}`); + + await app.waitFor( + async () => { await app.expectTexts(["Install and manage applications."]); }, + { timeout: 10000, interval: 500, description: "Applications view to render" } + ); + + // The sidebar "Applications" button carries the section index it activates + // (onClicked passes _d.workspaceSections.length + index) — read it from the + // delegate's context instead of hard-coding the sidebar layout. Only + // objects in SidebarPanel's delegate scope can resolve the expression, so + // it also disambiguates the button from same-text headers. + const sidebarHits = await app.findByProperty("text", "Applications"); + let appsButtonId = null; + let applicationsIndex = null; + for (const m of sidebarHits.matches ?? []) { + const res = await app.inspector.send("evaluate", { + objectId: m.id, expression: "_d.workspaceSections.length + index", + }); + if (!res.error && typeof res.result === "number") { + appsButtonId = m.id; + applicationsIndex = res.result; + break; + } + } + if (appsButtonId === null) { + throw new Error('sidebar "Applications" button (with section index in scope) not found'); + } + + await app.waitFor(async () => { + const res = await app.inspector.send("evaluate", { + objectId: appsButtonId, expression: "backend.currentActiveSectionIndex", + }); + if (res.error) { + throw new Error(`evaluate(backend.currentActiveSectionIndex) failed: ${res.error}`); + } + if (res.result !== applicationsIndex) { + throw new Error( + `backend.currentActiveSectionIndex=${res.result} ` + + `(expected Applications index ${applicationsIndex})`); + } + }, { timeout: 10000, interval: 500, description: "active section to become Applications" }); + + // WelcomePage's own QML `visible` stays true inside its offscreen + // QQuickWidget host; what observably hides it is that host — WorkspaceArea + // (objectName "workspace"), the stack page the section switch left. + const wsHits = await app.findByProperty("objectName", "workspace"); + const workspace = (wsHits.matches ?? [])[0]; + if (!workspace) throw new Error("workspace area (welcome page host) not found"); + const props = await app.inspector.send("getProperties", { objectId: workspace.id }); + const visible = props.properties?.find((p) => p.name === "visible")?.value; + if (visible !== false) { + throw new Error( + `welcome page still visible: workspace visible=` + + `${JSON.stringify(visible)} (expected false)`); + } +}); // Click options that pin a click to a sidebar SECTION button and nothing else. // // qt-mcp's findAndClick is a breadth-first walk that SUBSTRING-matches the