From 8427edcdb5de534ac11bc95d6c5e78ee90e2572a Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Oct 2026 11:07:25 +0000 Subject: [PATCH 1/3] fix(monkey): recognise non-English sign-out controls, configurable per project (#469) SIGN_OUT matched English captions only, so a Dutch app's "Afmelden" was clicked, the monkey landed on login.html and scored a false CRASH. The default now covers nl/de/fr/es/pt/it captions, a project adds its own via project.config.js signOutPattern or SIGN_OUT_PATTERN, and rapidTab steps past a focused sign-out control instead of pressing Enter on it. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw --- CHANGELOG.md | 1 + project-tests/e2e/config.js | 3 ++ project-tests/e2e/monkey.js | 44 ++++++++++++++++++-- project-tests/e2e/project.config.template.js | 5 +++ tests/wave2/test-monkey-left-app.sh | 27 ++++++++++++ 5 files changed, 77 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index edafc87e..4ac533ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ Sections dated before 2026-09-19 predate the cycle and stay as they are. Named m ## Unreleased +- fix(monkey): **the monkey no longer signs itself out of a non-English app** (#469). `monkey.js` recognised only English sign-out captions, so in a Dutch app it clicked "Afmelden", landed on `login.html`, scored a false CRASH and fuzzed nothing more (seed 204262061: 3 observations, 1 crash-class; 24 and 0 with the label excluded). The default now also covers Dutch, German, French, Spanish, Portuguese and Italian sign-out captions (sign-in captions such as "Aanmelden" stay fuzzable); a project adds its own in `project.config.js` `signOutPattern` or `SIGN_OUT_PATTERN`, added to the default, never replacing it. `rapidTab` no longer presses Enter while focus is on a sign-out control (top bar, nav item, aria-label or title), the route by which a layout control outside `.mx-page` was reached. `test-monkey-left-app.sh` gains the issue's captions, sign-in near-misses, the project pattern and the rapidTab guard: 5 FAIL on master, 18/18 with the fix. — jopterhorst ## v2026.10.19 - new(progress-report): **the progress report gains a Project pages section: a compact folder tree of the HTML pages found in the project's output folders (gate dashboard, analysis reports, blueprint, design system, process map, wireframes, UI reviews, test reports), each a relative link from `architecture/` with its modified date.** Pages are found by scanning the project's files on every render, never from a stored list; only the top-level folders in `PAGE_DIRS` are scanned (each named with its producer; a symlinked one is skipped), `node_modules`, `.mpr-snapshots`, `deployment`, `mprcontents`, `theme`, `.git`, vendor and dot folders are never entered, a folder with more than 8 pages folds behind a count, every folder keeps its row and lists at most 100 links (`+K more`), and the report never lists itself. Dates are shown as days, so the section is unchanged on the same day unless a listed page changed and a `--refresh` leaves the page alone. Test: `tests/wave2/test-progress-report-pages.sh` (single and two-tree layouts, Windows hrefs) — MendixMau (request 2026-10-19) diff --git a/project-tests/e2e/config.js b/project-tests/e2e/config.js index d0c72b1b..1ff9a4a5 100644 --- a/project-tests/e2e/config.js +++ b/project-tests/e2e/config.js @@ -206,6 +206,9 @@ const cfg = { // this app has no nav tree, so '.mx-navigationtree, .mx-page' alone is not // enough to prove it routed). readySelectors: PROJ.readySelectors || ['.mx-navigationtree', '.mx-page'], + // Extra sign-out captions for monkey.js, ADDED to its multilingual default (#469): a RegExp + // or regex string from project.config.js `signOutPattern`, or SIGN_OUT_PATTERN in the env. + signOutPattern: process.env.SIGN_OUT_PATTERN || PROJ.signOutPattern || null, // This project runs natively under Studio Pro (no Docker), so `mxcli oql -p` // alone would try to route through `docker compose exec` and fail. --direct // connects straight to the M2EE admin API on the host instead. diff --git a/project-tests/e2e/monkey.js b/project-tests/e2e/monkey.js index a3dd128c..65c1dd7e 100644 --- a/project-tests/e2e/monkey.js +++ b/project-tests/e2e/monkey.js @@ -153,7 +153,42 @@ async function typeSafely(page, handle, text) { // toggle), so Back left the app — to login.html, then to about:blank. The oracle scored the // non-app page "blank page", every later round on that target scored it again, and the next // target was never reached: 10 crash-class findings, one target unfuzzed, no app defect. -const SIGN_OUT = /\b(sign|log)\s*(out|off)\b/i; +// +// SIGN_OUT IS MULTILINGUAL, AND A PROJECT CAN ADD TO IT (#469). The English-only pattern missed +// a Dutch app's "Afmelden": the monkey signed itself out in round 1, scored login.html as a +// CRASH, and fuzzed nothing more (seed 204262061: 3 observations, 1 crash-class; 24 and 0 once +// the label was excluded). The default covers the common captions of the languages Mendix apps +// ship in most; anything else goes in project.config.js `signOutPattern` (a RegExp or a regex +// string) or the SIGN_OUT_PATTERN env var, and is ADDED to the default, never a replacement. +const SIGN_OUT_DEFAULT = new RegExp('\\b(?:' + [ + '(?:sign|log)\\s*(?:out|off)', // en: Sign out, Log off, Logout + 'afmelden', 'uitloggen', // nl + 'abmelden', 'ausloggen', // de + 'se\\s+d[ée]connecter', 'd[ée]connexion', // fr + 'cerrar\\s+sesi[óo]n', // es + '(?:terminar|encerrar)\\s+sess[ãa]o', // pt + 'disconnetti', // it +].join('|') + ')\\b', 'i'); +function signOutPattern(extra) { + if (!extra) return SIGN_OUT_DEFAULT; + const src = extra instanceof RegExp ? extra.source : String(extra); + try { return new RegExp(`(?:${SIGN_OUT_DEFAULT.source})|(?:${src})`, 'i'); } + catch (e) { + console.log(` ⚠ signOutPattern ${JSON.stringify(src)} is not a valid regex (${e.message}) — using the built-in sign-out captions only`); + return SIGN_OUT_DEFAULT; + } +} +const SIGN_OUT = signOutPattern(cfg.signOutPattern); +// The keyboard can reach controls the button locator never offers: the layout's top bar, a +// navigation item, an icon-only button whose caption is its aria-label or title. +async function focusIsSignOut(page) { + const label = await page.evaluate(() => { + const e = document.activeElement; + if (!e || e === document.body) return ''; + return [e.innerText || e.textContent || '', e.getAttribute('aria-label') || '', e.getAttribute('title') || ''].join(' | '); + }).catch(() => ''); + return SIGN_OUT.test(label); +} function leftApp(url, baseUrl) { const base = String(baseUrl).replace(/\/+$/, ''); if (!url || (url !== base && !url.startsWith(base + '/'))) return true; @@ -208,7 +243,10 @@ async function round(page, target, bucket) { } } else if (abuse === 'rapidTab') { for (let i = 0; i < 15; i++) await page.keyboard.press('Tab').catch(() => {}); - await page.keyboard.press('Enter').catch(() => {}); + // Never press Enter on a sign-out control (#469): step past it, a few Tabs at most. + for (let k = 0; k < 5 && await focusIsSignOut(page); k++) await page.keyboard.press('Tab').catch(() => {}); + if (await focusIsSignOut(page)) what += ' (focus stayed on a sign-out control: Enter skipped)'; + else await page.keyboard.press('Enter').catch(() => {}); } else if (abuse === 'reloadMidFlow') { if (btn) btn.click({ force: true, timeout: 4000 }).catch(() => {}); await page.waitForTimeout(300); @@ -224,7 +262,7 @@ async function round(page, target, bucket) { } // leftApp is exported for tests/wave2/test-monkey-left-app.sh; requiring this file runs nothing. -module.exports = { leftApp, SIGN_OUT }; +module.exports = { leftApp, SIGN_OUT, SIGN_OUT_DEFAULT, signOutPattern }; // ── main ──────────────────────────────────────────────────────────────────── if (require.main === module) (async () => { diff --git a/project-tests/e2e/project.config.template.js b/project-tests/e2e/project.config.template.js index 790e23c2..0bb35334 100644 --- a/project-tests/e2e/project.config.template.js +++ b/project-tests/e2e/project.config.template.js @@ -586,6 +586,11 @@ module.exports = { mxcli: process.env.MXCLI || './mxcli', credentials, readySelectors: READY_SELECTORS, + // Sign-out captions monkey.js must never click, ADDED to its built-in list (English, Dutch, + // German, French, Spanish, Portuguese, Italian). Set it when the app's sign-out control + // reads otherwise, e.g. /\bkirjaudu ulos\b/i — a monkey that signs itself out scores the + // login page as a crash and fuzzes nothing more (#469). + signOutPattern: null, dsCss: DS_CSS, NAV, TARGETS, diff --git a/tests/wave2/test-monkey-left-app.sh b/tests/wave2/test-monkey-left-app.sh index 654260e8..37fa1645 100755 --- a/tests/wave2/test-monkey-left-app.sh +++ b/tests/wave2/test-monkey-left-app.sh @@ -64,6 +64,14 @@ L('OTHER', 'http://localhost:80801/p/cases'); L('SLASHBASE', 'http://localhost:8080/p/cases', base + '/'); const S = t => console.log(`S:${t} ${M.SIGN_OUT.test(t)}`); ['Sign out', 'Log off', 'Logout', 'Switch theme', 'Signature', 'Log in'].forEach(S); +// #469: the captions the issue names, verbatim, and near-misses that must stay fuzzable. +['Afmelden', 'Uitloggen', 'Abmelden', 'Se déconnecter', 'Aanmelden', 'Inloggen', 'Anmelden', 'Se connecter', 'Melden'].forEach(S); +const X = (k, extra, t) => console.log(`X:${k} ${typeof M.signOutPattern === 'function' ? M.signOutPattern(extra).test(t) : 'NOFN'}`); +X('cfgre', /\bkirjaudu ulos\b/i, 'Kirjaudu ulos'); +X('cfgstr', '\\bkirjaudu ulos\\b', 'Kirjaudu ulos'); +X('cfgkeeps', /\bkirjaudu ulos\b/i, 'Afmelden'); +X('cfgbad', '(', 'Afmelden'); +X('cfgnone', null, 'Kirjaudu ulos'); process.exit(0); EOF OUT="$(cd "$WORK/tests/e2e" && printf '%s' "$JS" | APP_PORT=1 timeout 30 node - 2>&1)" @@ -91,6 +99,25 @@ s() { printf '%s\n' "$OUT" | sed -n "s/^S:$1 //p"; } [ "$(s 'Switch theme')" = false ] && [ "$(s 'Signature')" = false ] && [ "$(s 'Log in')" = false ] \ && ok "SIGN_OUT leaves Switch theme (captured), Signature, Log in fuzzable" || bad "SIGN_OUT over-matches" "$(s 'Switch theme')/$(s 'Signature')/$(s 'Log in')" +# #469 — non-English sign-out captions (verbatim from the issue's field run and its list). +[ "$(s 'Afmelden')" = true ] && [ "$(s 'Uitloggen')" = true ] && [ "$(s 'Abmelden')" = true ] && [ "$(s 'Se déconnecter')" = true ] \ + && ok "SIGN_OUT matches Afmelden (captured), Uitloggen, Abmelden, Se déconnecter" \ + || bad "a non-English sign-out label is clicked — the #469 false CRASH" "$(s 'Afmelden')/$(s 'Uitloggen')/$(s 'Abmelden')/$(s 'Se déconnecter')" +[ "$(s 'Aanmelden')" = false ] && [ "$(s 'Inloggen')" = false ] && [ "$(s 'Anmelden')" = false ] && [ "$(s 'Se connecter')" = false ] && [ "$(s 'Melden')" = false ] \ + && ok "SIGN_OUT leaves sign-IN and look-alike captions fuzzable (Aanmelden, Inloggen, Anmelden, Se connecter, Melden)" \ + || bad "SIGN_OUT over-matches a sign-in caption" "$(s 'Aanmelden')/$(s 'Inloggen')/$(s 'Anmelden')/$(s 'Se connecter')/$(s 'Melden')" +x() { printf '%s\n' "$OUT" | sed -n "s/^X:$1 //p"; } +[ "$(x cfgre)" = true ] && [ "$(x cfgstr)" = true ] && ok "a project signOutPattern (RegExp or string) is honoured" \ + || bad "project signOutPattern ignored" "$(x cfgre)/$(x cfgstr)" +[ "$(x cfgkeeps)" = true ] && ok "a project pattern adds to the default, it does not replace it" || bad "project pattern dropped the default" "$(x cfgkeeps)" +[ "$(x cfgbad)" = true ] && [ "$(x cfgnone)" = false ] && ok "an invalid project pattern falls back to the default; none means default only" \ + || bad "invalid/absent project pattern mishandled" "$(x cfgbad)/$(x cfgnone)" +grep -q 'signOutPattern: *process.env.SIGN_OUT_PATTERN' "$E2E/config.js" && grep -q '^ *signOutPattern:' "$CFG" \ + && ok "config.js reads signOutPattern (env, then project.config) and the template declares it" \ + || bad "signOutPattern is not plumbed through config.js / the template (a consumer with no producer)" +awk '/rapidTab/ {r=1} r && /focusIsSignOut\(page\)/ {f=1} f && /keyboard\.press\(.Enter.\)/ {e=1} END{exit !e}' "$SUT" \ + && ok "rapidTab checks focus for a sign-out control before Enter" || bad "rapidTab presses Enter without checking what has focus (#469)" + # The tested functions must be the ones the rounds use. awk '/hasNotText: *SIGN_OUT/ {f=1} END{exit !f}' "$SUT" && ok "round()'s button locator filters SIGN_OUT" \ || bad "the button locator does not filter SIGN_OUT — the tested regex is not the one that runs" From d751f8df163dd73a3d4fafc1fae3d7813934da9f Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Oct 2026 11:50:41 +0000 Subject: [PATCH 2/3] fix(monkey): whole-caption match for non-English sign-out, keep project flags (#469 review) The non-English words are ordinary verbs, so a word match left "Ziek afmelden", "Afmelden nieuwsbrief", "Vom Kurs abmelden" and "Disconnetti dispositivo" unfuzzed; they now match only as the whole caption, and the focus check tests text, aria-label and title one by one. A project RegExp keeps its flags (g/y dropped). The rapidTab static guard is scoped to that branch: the old one passed a blind-Enter mutation, the new one fails it. Fixture 22/22; 8 FAIL on master, 3 on the previous head. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw --- CHANGELOG.md | 3 ++- project-tests/e2e/monkey.js | 42 ++++++++++++++++++----------- tests/wave2/test-monkey-left-app.sh | 21 +++++++++++++-- 3 files changed, 47 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ac533ca..c280f530 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,8 @@ Sections dated before 2026-09-19 predate the cycle and stay as they are. Named m ## Unreleased -- fix(monkey): **the monkey no longer signs itself out of a non-English app** (#469). `monkey.js` recognised only English sign-out captions, so in a Dutch app it clicked "Afmelden", landed on `login.html`, scored a false CRASH and fuzzed nothing more (seed 204262061: 3 observations, 1 crash-class; 24 and 0 with the label excluded). The default now also covers Dutch, German, French, Spanish, Portuguese and Italian sign-out captions (sign-in captions such as "Aanmelden" stay fuzzable); a project adds its own in `project.config.js` `signOutPattern` or `SIGN_OUT_PATTERN`, added to the default, never replacing it. `rapidTab` no longer presses Enter while focus is on a sign-out control (top bar, nav item, aria-label or title), the route by which a layout control outside `.mx-page` was reached. `test-monkey-left-app.sh` gains the issue's captions, sign-in near-misses, the project pattern and the rapidTab guard: 5 FAIL on master, 18/18 with the fix. — jopterhorst +- fix(monkey): **the monkey no longer signs itself out of a non-English app** (#469). `monkey.js` recognised only English sign-out captions, so in a Dutch app it clicked "Afmelden", landed on `login.html`, scored a false CRASH and fuzzed nothing more (seed 204262061: 3 observations, 1 crash-class; 24 and 0 with the label excluded). The default now also covers Dutch, German, French, Spanish, Portuguese and Italian sign-out captions, matched as the whole caption so ordinary verbs stay fuzzable ("Ziek afmelden", "Vom Kurs abmelden", and sign-in captions such as "Aanmelden"); a project adds its own in `project.config.js` `signOutPattern` or `SIGN_OUT_PATTERN`, added to the default, never replacing it, with its own regex flags kept. `rapidTab` no longer presses Enter while focus is on a sign-out control (top bar, nav item, aria-label or title, each tested on its own), the route by which a layout control outside `.mx-page` was reached. `test-monkey-left-app.sh` gains the issue's captions, sign-in near-misses, the over-match captions, the project pattern and its flags, and a rapidTab guard scoped to that branch (it fails on a blind-Enter mutation): 8 FAIL on master, 22/22 with the fix. — jopterhorst + ## v2026.10.19 - new(progress-report): **the progress report gains a Project pages section: a compact folder tree of the HTML pages found in the project's output folders (gate dashboard, analysis reports, blueprint, design system, process map, wireframes, UI reviews, test reports), each a relative link from `architecture/` with its modified date.** Pages are found by scanning the project's files on every render, never from a stored list; only the top-level folders in `PAGE_DIRS` are scanned (each named with its producer; a symlinked one is skipped), `node_modules`, `.mpr-snapshots`, `deployment`, `mprcontents`, `theme`, `.git`, vendor and dot folders are never entered, a folder with more than 8 pages folds behind a count, every folder keeps its row and lists at most 100 links (`+K more`), and the report never lists itself. Dates are shown as days, so the section is unchanged on the same day unless a listed page changed and a `--refresh` leaves the page alone. Test: `tests/wave2/test-progress-report-pages.sh` (single and two-tree layouts, Windows hrefs) — MendixMau (request 2026-10-19) diff --git a/project-tests/e2e/monkey.js b/project-tests/e2e/monkey.js index 65c1dd7e..8bb2263f 100644 --- a/project-tests/e2e/monkey.js +++ b/project-tests/e2e/monkey.js @@ -160,19 +160,27 @@ async function typeSafely(page, handle, text) { // the label was excluded). The default covers the common captions of the languages Mendix apps // ship in most; anything else goes in project.config.js `signOutPattern` (a RegExp or a regex // string) or the SIGN_OUT_PATTERN env var, and is ADDED to the default, never a replacement. -const SIGN_OUT_DEFAULT = new RegExp('\\b(?:' + [ - '(?:sign|log)\\s*(?:out|off)', // en: Sign out, Log off, Logout - 'afmelden', 'uitloggen', // nl - 'abmelden', 'ausloggen', // de - 'se\\s+d[ée]connecter', 'd[ée]connexion', // fr - 'cerrar\\s+sesi[óo]n', // es - '(?:terminar|encerrar)\\s+sess[ãa]o', // pt - 'disconnetti', // it -].join('|') + ')\\b', 'i'); +// +// The non-English words match the WHOLE caption only. They are also ordinary verbs, so a word +// match left "Ziek afmelden" (report sick), "Afmelden nieuwsbrief", "Vom Kurs abmelden" and +// "Disconnetti dispositivo" unfuzzed. The English phrase keeps its word match, as before. +const SIGN_OUT_DEFAULT = new RegExp( + '\\b(?:sign|log)\\s*(?:out|off)\\b' + // en: Sign out, Log off, Logout + '|^\\s*(?:' + [ + 'afmelden', 'uitloggen', // nl + 'abmelden', 'ausloggen', // de + 'se\\s+d[ée]connecter', 'd[ée]connexion', // fr + 'cerrar\\s+sesi[óo]n', // es + '(?:terminar|encerrar)\\s+sess[ãa]o', // pt + 'disconnetti', // it + ].join('|') + ')\\s*$', 'i'); +// A project RegExp keeps its own flags (m, s, u, …), merged with the default's `i`; `g` and `y` +// are dropped because they make .test() stateful across calls. function signOutPattern(extra) { if (!extra) return SIGN_OUT_DEFAULT; const src = extra instanceof RegExp ? extra.source : String(extra); - try { return new RegExp(`(?:${SIGN_OUT_DEFAULT.source})|(?:${src})`, 'i'); } + const flags = [...new Set(('i' + (extra instanceof RegExp ? extra.flags : '')).replace(/[gy]/g, ''))].join(''); + try { return new RegExp(`(?:${SIGN_OUT_DEFAULT.source})|(?:${src})`, flags); } catch (e) { console.log(` ⚠ signOutPattern ${JSON.stringify(src)} is not a valid regex (${e.message}) — using the built-in sign-out captions only`); return SIGN_OUT_DEFAULT; @@ -182,13 +190,15 @@ const SIGN_OUT = signOutPattern(cfg.signOutPattern); // The keyboard can reach controls the button locator never offers: the layout's top bar, a // navigation item, an icon-only button whose caption is its aria-label or title. async function focusIsSignOut(page) { - const label = await page.evaluate(() => { + const labels = await page.evaluate(() => { const e = document.activeElement; - if (!e || e === document.body) return ''; - return [e.innerText || e.textContent || '', e.getAttribute('aria-label') || '', e.getAttribute('title') || ''].join(' | '); - }).catch(() => ''); - return SIGN_OUT.test(label); + if (!e || e === document.body) return []; + return [e.innerText || e.textContent || '', e.getAttribute('aria-label') || '', e.getAttribute('title') || '']; + }).catch(() => []); + return isSignOutLabel(labels); } +// Each label on its own: the non-English captions are anchored to the whole caption. +function isSignOutLabel(labels) { return labels.some(t => t && SIGN_OUT.test(t)); } function leftApp(url, baseUrl) { const base = String(baseUrl).replace(/\/+$/, ''); if (!url || (url !== base && !url.startsWith(base + '/'))) return true; @@ -262,7 +272,7 @@ async function round(page, target, bucket) { } // leftApp is exported for tests/wave2/test-monkey-left-app.sh; requiring this file runs nothing. -module.exports = { leftApp, SIGN_OUT, SIGN_OUT_DEFAULT, signOutPattern }; +module.exports = { leftApp, SIGN_OUT, SIGN_OUT_DEFAULT, signOutPattern, isSignOutLabel }; // ── main ──────────────────────────────────────────────────────────────────── if (require.main === module) (async () => { diff --git a/tests/wave2/test-monkey-left-app.sh b/tests/wave2/test-monkey-left-app.sh index 37fa1645..2c31aa4c 100755 --- a/tests/wave2/test-monkey-left-app.sh +++ b/tests/wave2/test-monkey-left-app.sh @@ -72,6 +72,13 @@ X('cfgstr', '\\bkirjaudu ulos\\b', 'Kirjaudu ulos'); X('cfgkeeps', /\bkirjaudu ulos\b/i, 'Afmelden'); X('cfgbad', '(', 'Afmelden'); X('cfgnone', null, 'Kirjaudu ulos'); +// Review of #481: ordinary verbs inside a longer caption are not sign-out controls. +['Ziek afmelden', 'Afmelden nieuwsbrief', 'Vom Kurs abmelden', 'Disconnetti dispositivo', ' Afmelden '].forEach(S); +X('cfgflags', /^kirjaudu ulos$/m, 'Valikko\nKirjaudu ulos'); +const I = (k, labels) => console.log(`I:${k} ${typeof M.isSignOutLabel === 'function' ? M.isSignOutLabel(labels) : 'NOFN'}`); +I('aria', ['', 'Afmelden', '']); +I('title', ['\ue000', '', 'Uitloggen']); +I('none', ['Ziek afmelden', 'Menu', '']); process.exit(0); EOF OUT="$(cd "$WORK/tests/e2e" && printf '%s' "$JS" | APP_PORT=1 timeout 30 node - 2>&1)" @@ -93,7 +100,7 @@ fi [ "$(line OTHER)" = true ] && ok "a lookalike origin (port prefix match) is outside the app" || bad "prefix match leaks another origin in" "$(line OTHER)" [ "$(line SLASHBASE)" = false ] && ok "a baseUrl with a trailing slash still matches its routes" || bad "trailing-slash baseUrl breaks the match" "$(line SLASHBASE)" -s() { printf '%s\n' "$OUT" | sed -n "s/^S:$1 //p"; } +s() { printf '%s\n' "$OUT" | K="S:$1" awk '{b=$NF; sub(/ [^ ]*$/, ""); if ($0 == ENVIRON["K"]) print b}'; } # exact label: "Afmelden" must not read "Afmelden nieuwsbrief" [ "$(s 'Sign out')" = true ] && [ "$(s 'Log off')" = true ] && [ "$(s 'Logout')" = true ] \ && ok "SIGN_OUT matches Sign out (captured), Log off, Logout" || bad "a sign-out label is not excluded" "$(s 'Sign out')/$(s 'Log off')/$(s 'Logout')" [ "$(s 'Switch theme')" = false ] && [ "$(s 'Signature')" = false ] && [ "$(s 'Log in')" = false ] \ @@ -112,10 +119,20 @@ x() { printf '%s\n' "$OUT" | sed -n "s/^X:$1 //p"; } [ "$(x cfgkeeps)" = true ] && ok "a project pattern adds to the default, it does not replace it" || bad "project pattern dropped the default" "$(x cfgkeeps)" [ "$(x cfgbad)" = true ] && [ "$(x cfgnone)" = false ] && ok "an invalid project pattern falls back to the default; none means default only" \ || bad "invalid/absent project pattern mishandled" "$(x cfgbad)/$(x cfgnone)" +[ "$(s 'Ziek afmelden')" = false ] && [ "$(s 'Afmelden nieuwsbrief')" = false ] && [ "$(s 'Vom Kurs abmelden')" = false ] && [ "$(s 'Disconnetti dispositivo')" = false ] \ + && ok "a non-English verb inside a longer caption stays fuzzable (Ziek afmelden, Afmelden nieuwsbrief, Vom Kurs abmelden, Disconnetti dispositivo)" \ + || bad "SIGN_OUT over-matches an ordinary caption" "$(s 'Ziek afmelden')/$(s 'Afmelden nieuwsbrief')/$(s 'Vom Kurs abmelden')/$(s 'Disconnetti dispositivo')" +[ "$(s ' Afmelden ')" = true ] && ok "a whole caption with surrounding whitespace still matches" || bad "padded caption missed" "$(s ' Afmelden ')" +[ "$(x cfgflags)" = true ] && ok "a project RegExp keeps its own flags (m)" || bad "project RegExp flags dropped" "$(x cfgflags)" +i() { printf '%s\n' "$OUT" | sed -n "s/^I:$1 //p"; } +[ "$(i aria)" = true ] && [ "$(i title)" = true ] && [ "$(i none)" = false ] \ + && ok "focus labels (text, aria-label, title) are tested one by one" || bad "focus label test wrong" "$(i aria)/$(i title)/$(i none)" grep -q 'signOutPattern: *process.env.SIGN_OUT_PATTERN' "$E2E/config.js" && grep -q '^ *signOutPattern:' "$CFG" \ && ok "config.js reads signOutPattern (env, then project.config) and the template declares it" \ || bad "signOutPattern is not plumbed through config.js / the template (a consumer with no producer)" -awk '/rapidTab/ {r=1} r && /focusIsSignOut\(page\)/ {f=1} f && /keyboard\.press\(.Enter.\)/ {e=1} END{exit !e}' "$SUT" \ +# Scoped to the rapidTab branch itself: the ABUSES list and focusIsSignOut's definition also +# mention these names, and an unscoped match passed a blind Enter (review of #481). +awk '/abuse === .rapidTab./{r=1;next} r&&/else if \(abuse ===/{r=0} r&&/focusIsSignOut\(page\)/{f=1} r&&/press\(.Enter.\)/{if(!f)bad=1; e=1} END{exit !(e&&!bad)}' "$SUT" \ && ok "rapidTab checks focus for a sign-out control before Enter" || bad "rapidTab presses Enter without checking what has focus (#469)" # The tested functions must be the ones the rounds use. From bab64c52631dd6a98eb374ba4239f83d4cd57a83 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Oct 2026 12:07:36 +0000 Subject: [PATCH 3/3] fix(monkey): correct the master FAIL count to 9 (#481 review) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index caf24d3b..577914c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ Sections dated before 2026-09-19 predate the cycle and stay as they are. Named m ## Unreleased -- fix(monkey): **the monkey no longer signs itself out of a non-English app** (#469). `monkey.js` recognised only English sign-out captions, so in a Dutch app it clicked "Afmelden", landed on `login.html`, scored a false CRASH and fuzzed nothing more (seed 204262061: 3 observations, 1 crash-class; 24 and 0 with the label excluded). The default now also covers Dutch, German, French, Spanish, Portuguese and Italian sign-out captions, matched as the whole caption so ordinary verbs stay fuzzable ("Ziek afmelden", "Vom Kurs abmelden", and sign-in captions such as "Aanmelden"); a project adds its own in `project.config.js` `signOutPattern` or `SIGN_OUT_PATTERN`, added to the default, never replacing it, with its own regex flags kept. `rapidTab` no longer presses Enter while focus is on a sign-out control (top bar, nav item, aria-label or title, each tested on its own), the route by which a layout control outside `.mx-page` was reached. `test-monkey-left-app.sh` gains the issue's captions, sign-in near-misses, the over-match captions, the project pattern and its flags, and a rapidTab guard scoped to that branch (it fails on a blind-Enter mutation): 8 FAIL on master, 22/22 with the fix. — jopterhorst +- fix(monkey): **the monkey no longer signs itself out of a non-English app** (#469). `monkey.js` recognised only English sign-out captions, so in a Dutch app it clicked "Afmelden", landed on `login.html`, scored a false CRASH and fuzzed nothing more (seed 204262061: 3 observations, 1 crash-class; 24 and 0 with the label excluded). The default now also covers Dutch, German, French, Spanish, Portuguese and Italian sign-out captions, matched as the whole caption so ordinary verbs stay fuzzable ("Ziek afmelden", "Vom Kurs abmelden", and sign-in captions such as "Aanmelden"); a project adds its own in `project.config.js` `signOutPattern` or `SIGN_OUT_PATTERN`, added to the default, never replacing it, with its own regex flags kept. `rapidTab` no longer presses Enter while focus is on a sign-out control (top bar, nav item, aria-label or title, each tested on its own), the route by which a layout control outside `.mx-page` was reached. `test-monkey-left-app.sh` gains the issue's captions, sign-in near-misses, the over-match captions, the project pattern and its flags, and a rapidTab guard scoped to that branch (it fails on a blind-Enter mutation): 9 FAIL on master, 22/22 with the fix. — jopterhorst - fix(progress-report): **the build plan is shown once: the phase cards are the plan.** Expanding a card now shows its full rows (#, Kind, Step, Produces/Proves, Depends on, State, Scripts) and Claims, which only the duplicate "Full plan tables" block carried; that block is gone and views A (script folders) and B (module proof) are their own section. The `now` phase renders open with a selected style, opening another phase closes the previous one (accordion; `#phase-N` links still open their card), and a viewer's saved open/closed state survives the 30 s reload and wins over the default (#474) — field feedback from a requirements-driven project at Stage 5 — MendixMau (field feedback 2026-10-20) ## v2026.10.19