{"record":{"id":"4be181734b0c4074","repo":"jackwener/OpenCLI","slug":"mercury-returned-malformed-click-result","errorCode":null,"errorMessage":"Mercury returned malformed click result","messagePattern":"Mercury returned malformed click result","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/mercury/utils.js","lineNumber":165,"sourceCode":"}\n\nexport async function clickText(page, labels) {\n    const result = await page.evaluate(`(() => {\n        const labels = ${JSON.stringify(labels)};\n        const norm = (s) => String(s || '').replace(/\\\\s+/g, ' ').trim().toLowerCase();\n        const wanted = labels.map(norm);\n        const candidates = Array.from(document.querySelectorAll('button, a, [role=\"button\"], [role=\"link\"]'))\n          .filter((node) => {\n            const style = window.getComputedStyle(node);\n            return style.visibility !== 'hidden' && style.display !== 'none' && node.offsetParent !== null;\n          });\n        const el = candidates.find((node) => wanted.includes(norm(node.innerText || node.textContent || '')));\n        if (!el) return { clicked: false, labels };\n        el.click();\n        return { clicked: true, text: el.innerText || el.textContent || '' };\n    })()`);\n    const payload = assertObject(result, 'click result');\n    if (typeof payload.clicked !== 'boolean') throw new CommandExecutionError('Mercury returned malformed click result');\n    return payload;\n}\n\nexport async function clickCreateExpenseButton(page) {\n    const result = await page.evaluate(`(() => {\n        const norm = (s) => String(s || '').replace(/\\\\s+/g, ' ').trim().toLowerCase();\n        const wanted = new Set(['submit expense', 'new expense']);\n        const candidates = Array.from(document.querySelectorAll('button, a, [role=\"button\"], [role=\"link\"]'))\n          .filter((node) => {\n            const style = window.getComputedStyle(node);\n            return style.visibility !== 'hidden' && style.display !== 'none' && node.offsetParent !== null;\n          })\n          .filter((node) => wanted.has(norm(node.innerText || node.textContent || '')));\n        const dangerous = candidates.find((node) => {\n          const container = node.closest('[role=\"dialog\"], dialog, form, [aria-modal=\"true\"]');\n          const context = String(container?.innerText || container?.textContent || '').replace(/\\\\s+/g, ' ').trim();\n          return Boolean(container) || /Review|receipt|amount|merchant|category|notes|expense date/i.test(context);\n        });","sourceCodeStart":147,"sourceCodeEnd":183,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/mercury/utils.js#L147-L183","documentation":"clickText() runs a page.evaluate() script in the browser to find and click a button/link matching the given labels, then validates the returned payload. The library throws this CommandExecutionError when the evaluate() result is an object but its `clicked` field is not a boolean, i.e. the browser-side script returned an unexpected shape instead of { clicked: boolean, ... }. This guards against silently acting on malformed automation results from a changed or broken page environment.","triggerScenarios":"page.evaluate() returns an object lacking a boolean `clicked` property — e.g. the injected script was altered, an older/cached script version is running, or the page context returned a proxy/non-serializable value that deserialized differently.","commonSituations":"Automating a Mercury page whose DOM/context changed so evaluate returns an unexpected shape; custom page wrappers or patched evaluate implementations that transform results; browser extensions injecting scripts that interfere with evaluate return values.","solutions":["Log the raw `result` from page.evaluate() before the throw and check its shape for the `clicked` key.","Ensure the page is fully loaded and not navigated mid-evaluate; re-run inspectMercury() then retry clickText().","Update to a matching version of the mercury CLI utils so the evaluate script and its validation agree.","Catch CommandExecutionError and retry with a fresh page if it is transient."],"exampleFix":"// before\nconst payload = assertObject(result, 'click result');\nif (typeof payload.clicked !== 'boolean') throw new CommandExecutionError('Mercury returned malformed click result');\n// after\nconst payload = assertObject(result, 'click result');\nif (typeof payload.clicked !== 'boolean') {\n  console.error('click result was', JSON.stringify(result));\n  throw new CommandExecutionError(`Mercury returned malformed click result: ${JSON.stringify(result)}`);\n}","handlingStrategy":"type-guard","validationCode":"const payload = await page.evaluate(`(() => { ... })()`);\nif (payload && typeof payload === 'object' && typeof payload.clicked === 'boolean') {\n  await clickText(page, ['Save']);\n}","typeGuard":"function isClickResult(v) {\n  return v != null && typeof v === 'object' && typeof v.clicked === 'boolean';\n}","tryCatchPattern":"try {\n  const res = await clickText(page, ['Save']);\n} catch (err) {\n  if (err instanceof CommandExecutionError && /malformed click result/.test(err.message)) {\n    // re-inspect page state and retry on a fresh evaluate\n  } else throw err;\n}","preventionTips":["Log raw evaluate results in debug mode to spot shape drift early.","Keep page.evaluate scripts and their validators in the same function so they cannot drift.","Wrap automation steps with retry-once-on-fresh-page logic.","Add a pre-check (inspectMercury) that the page is loaded and stable before clicking."],"tags":["browser-automation","page-evaluate","schema-validation-failed","mercury"],"backgroundTag":"page-evaluate-result-mismatch","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}