From 6f1f31516f298c9444ab9fed8ca405a8235a54c9 Mon Sep 17 00:00:00 2001 From: MelekTaus Date: Tue, 6 Oct 2026 11:34:35 +0200 Subject: [PATCH] Find asset types below the fold of the + menu in e2e #331 Since the Instrument joined the + menu, its last entries can sit below the fold of the test window. CI for 8a4e4a6 stopped in step 12 with 'widget with text "Texture" not found', and every step after it was skipped. - pickAssetType looked first with locate, which throws on a row scrolled out of its list. So it never reached its own scroll. It now looks with find and scrolls when the hit is not in the menu. - A hit outside the menu counts as a miss, so the centre tab "Texture" cannot take the click meant for the entry. - Steps 12, 26 and 28 picked their entry by hand and now go through pickAssetType ("Texture", "State machine", "Shader"). Tests: - e2e 1-12, 26, 28 (melektaus-atx): ALL PASSED, 635 checks. Before the fix step 12 failed there as in CI. Co-Authored-By: Claude Opus 5.5 --- tools/e2e/12-textures.mjs | 13 ++++++------- tools/e2e/26-state-machine.mjs | 4 ++-- tools/e2e/28-shader.mjs | 3 ++- tools/e2e/lib/scaffold.mjs | 13 +++++++------ 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/tools/e2e/12-textures.mjs b/tools/e2e/12-textures.mjs index a8b60f60..57ba5fab 100644 --- a/tools/e2e/12-textures.mjs +++ b/tools/e2e/12-textures.mjs @@ -1,5 +1,5 @@ import { colourDistance } from '../lib/harness.mjs'; -import { inView } from './lib/scaffold.mjs'; +import { inView, pickAssetType } from './lib/scaffold.mjs'; /** * Uploading a picture and wearing it. Runs last because it adds an asset and repaints the @@ -41,7 +41,7 @@ const PASTE_SPARE = `(async () => { export async function textures(t) { const { check, sleep, json, evaluate, shot, mutate, press } = t; - const { api, click, clickText, doubleClick, locate, undo, play, stop } = t; + const { api, click, clickText, doubleClick, undo, play, stop } = t; // The previous step leaves a picker open and a text field focused, and paste is deliberately // ignored while an input has focus, so that Ctrl+V inside a field still edits text. @@ -327,9 +327,9 @@ export async function textures(t) { await evaluate('__gm.console.clear()'); await clickText('+'); // By position, not by text: the menu entry "Texture" shares its label with the centre tab, and - // `locate` would hand back whichever it walked into first. - const entry = await locate('Texture', true); - await click(entry.x, entry.y); + // `locate` would hand back whichever it walked into first. The menu scrolls once it outgrows the + // screen, and "Texture" sits near its end. + await pickAssetType(t, 'Texture'); await sleep(1500); const picker = await json('__gm.imagePicker()'); const stored = (await api('textures')).map((item) => item.file); @@ -429,8 +429,7 @@ export async function textures(t) { // Reusing a file already in the project must not make a second asset on it – two assets on one // picture would disagree about its filter. await clickText('+'); - const entryAgain = await locate('Texture', true); - await click(entryAgain.x, entryAgain.y); + await pickAssetType(t, 'Texture'); await sleep(1500); const reopened = await json('__gm.imagePicker()'); check( diff --git a/tools/e2e/26-state-machine.mjs b/tools/e2e/26-state-machine.mjs index a131c47b..b3cc2cd7 100644 --- a/tools/e2e/26-state-machine.mjs +++ b/tools/e2e/26-state-machine.mjs @@ -1,5 +1,5 @@ import { edge, node } from '../lib/builders.mjs'; -import { LANES, scaffoldBehavior } from './lib/scaffold.mjs'; +import { LANES, pickAssetType, scaffoldBehavior } from './lib/scaffold.mjs'; /** * State machines: an asset made from the + menu, its states edited as nodes in the State machine @@ -51,7 +51,7 @@ export async function stateMachine(t) { // 26a. The + menu creates a state machine; selected, it opens its own tab. await clickText('+'); - await clickLast('State machine'); + await pickAssetType(t, 'State machine'); const made = (await machines())[0]; check( 'the + menu creates a state machine asset', diff --git a/tools/e2e/28-shader.mjs b/tools/e2e/28-shader.mjs index 1d538acf..652994b4 100644 --- a/tools/e2e/28-shader.mjs +++ b/tools/e2e/28-shader.mjs @@ -7,6 +7,7 @@ import { PASTE_DOT, PASTE_HALVES, } from './lib/pictures.mjs'; +import { pickAssetType } from './lib/scaffold.mjs'; /** * Shader assets: made from the + menu, built from nodes in the Shader tab, worn by a sprite through @@ -67,7 +68,7 @@ export async function shader(t) { JSON.stringify(seeded), ); await clickText('+'); - await clickLast('Shader'); + await pickAssetType(t, 'Shader'); const made = (await shaders()).find((s) => s.id !== ADJUST); const selection = await json('__gm.state.selection'); check( diff --git a/tools/e2e/lib/scaffold.mjs b/tools/e2e/lib/scaffold.mjs index 48970222..e3e98a30 100644 --- a/tools/e2e/lib/scaffold.mjs +++ b/tools/e2e/lib/scaffold.mjs @@ -203,16 +203,17 @@ export async function clickDocumentTab(t, doc) { /** * Picks an asset type from the already-open `+` menu. Since #43 the menu may grow to the height of - * the canvas, so on this window every type is usually on screen at once; the scroll stays in as - * insurance for a shorter window, where entries below the mask edge are still found by `locate` - * but a click there lands on nothing. That is what the fourteenth type (`Variables`, #91) did to - * `Font` and to step 30. + * the canvas, and since the Instrument (#331) its last entries can sit below the fold of this + * window (`Texture` in step 12). `locate` refuses a row scrolled out of its list, so the first look is `find`, + * and a hit outside the menu (the centre tab "Texture") counts as a miss. */ export async function pickAssetType(t, label) { const box = await t.json('__gm.overlayRect()'); - let p = await t.locate(label, true); + let p = await t.find(label, true); let scrolled = false; - if (box && (!p || p.y > box.y + box.h - 4)) { + const inMenu = (at) => + at && at.x >= box.x && at.x <= box.x + box.w && at.y >= box.y && at.y <= box.y + box.h - 4; + if (box && !inMenu(p)) { await t.wheel(box.x + box.w / 2, box.y + box.h / 2, 300); p = await t.locate(label, true); scrolled = true;