{"record":{"id":"526b93ceaecc3e26","repo":"jackwener/OpenCLI","slug":"element-not-found-ref-ref","errorCode":null,"errorMessage":"Element not found: ref=${ref}","messagePattern":"Element not found: ref=(.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/browser/dom-snapshot.ts","lineNumber":85,"sourceCode":"  /** Previous snapshot hash set (JSON array of hashes) for diff marking (default null) */\n  previousHashes?: string | null;\n}\n\n// ─── Utility JS Generators ───────────────────────────────────────────\n\n/**\n * Generate JS to scroll to an element identified by data-opencli-ref.\n * Completes the snapshot→action loop: snapshot identifies `[3]<button>`,\n * caller can then `scrollToRef('3')` to bring it into view.\n */\nexport function scrollToRefJs(ref: string): string {\n  const safeRef = JSON.stringify(ref);\n  return `\n    (() => {\n      const ref = ${safeRef};\n      const el = document.querySelector('[data-opencli-ref=\"' + ref + '\"]')\n        || document.querySelector('[data-ref=\"' + ref + '\"]');\n      if (!el) throw new Error('Element not found: ref=' + ref);\n      el.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' });\n      return { scrolled: true, tag: el.tagName.toLowerCase(), text: (el.textContent || '').trim().slice(0, 80) };\n    })()\n  `.trim();\n}\n\n/**\n * Generate JS to extract all form field values from the page.\n * Returns structured JSON: { forms: [{ id, action, fields: [{ tag, type, name, value, ... }] }] }\n */\nexport function getFormStateJs(): string {\n  return `\n    (() => {\n      const result = { forms: [], orphanFields: [] };\n\n      // Collect all forms\n      for (const form of document.forms) {\n        const formData = {","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/src/browser/dom-snapshot.ts#L67-L103","documentation":"scrollToRefJs builds an in-page script that resolves an element by its snapshot ref attribute (data-opencli-ref or data-ref) and scrolls it into view. If no element with that ref exists in the current document, the injected script throws 'Element not found: ref=<ref>'.","triggerScenarios":"Calling scroll (via js/scrollToRefJs) with a ref string that does not match any element carrying data-opencli-ref/data-ref — typically a stale ref from a previous snapshot or a snapshot taken in a different frame/page.","commonSituations":"The page navigated or re-rendered after the DOM snapshot was taken, invalidating refs; using a ref from another tab/iframe; typo in the ref value.","solutions":["Re-take the DOM snapshot to obtain fresh refs and retry with the new ref","Verify you are targeting the same tab/frame the snapshot came from","Guard the call in try/catch and fall back to re-snapshotting when the ref is stale"],"exampleFix":"// before\nawait page.scroll(refFromOldSnapshot);\n// after\nconst snap = await page.snapshot();\nawait page.scroll(snap.refOfElement);","handlingStrategy":"try-catch","validationCode":"const exists = await page.evaluate(`!!document.querySelector('[data-opencli-ref=\"${ref}\"]')`);\nif (!exists) await page.snapshot(); // refresh refs first","typeGuard":"function isRefLive(exists: boolean): boolean { return exists === true; }","tryCatchPattern":"try { await page.scroll(ref); }\ncatch (e) {\n  if (String(e.message).startsWith('Element not found')) {\n    await page.snapshot();\n    ref = /* fresh ref */;\n    await page.scroll(ref);\n  } else throw e;\n}","preventionTips":["Always use refs from the most recent snapshot","Re-snapshot after navigation or DOM mutations","Confirm the same tab/frame used for the snapshot"],"tags":["dom","stale-reference","browser-automation"],"backgroundTag":"stale-element-reference","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}