{"record":{"id":"cf025574fc64aa09","repo":"garrytan/gstack","slug":"ref-selector-entry-role-entry-name-is","errorCode":null,"errorMessage":"Ref ${selector} (${entry.role} \"${entry.name}\") is stale — element no longer exists. Run 'snapshot' for fresh refs.","messagePattern":"Ref (.+?) \\((.+?) \"(.+?)\"\\) is stale — element no longer exists\\. Run 'snapshot' for fresh refs\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"browse/src/tab-session.ts","lineNumber":99,"sourceCode":"    this.refMap.clear();\n  }\n\n  /**\n   * Resolve a selector that may be a @ref (e.g., \"@e3\", \"@c1\") or a CSS selector.\n   * Returns { locator } for refs or { selector } for CSS selectors.\n   */\n  async resolveRef(selector: string): Promise<{ locator: Locator } | { selector: string }> {\n    if (selector.startsWith('@e') || selector.startsWith('@c')) {\n      const ref = selector.slice(1); // \"e3\" or \"c1\"\n      const entry = this.refMap.get(ref);\n      if (!entry) {\n        throw new Error(\n          `Ref ${selector} not found. Run 'snapshot' to get fresh refs.`\n        );\n      }\n      const count = await entry.locator.count();\n      if (count === 0) {\n        throw new Error(\n          `Ref ${selector} (${entry.role} \"${entry.name}\") is stale — element no longer exists. ` +\n          `Run 'snapshot' for fresh refs.`\n        );\n      }\n      return { locator: entry.locator };\n    }\n    return { selector };\n  }\n\n  /** Get the ARIA role for a ref selector, or null for CSS selectors / unknown refs. */\n  getRefRole(selector: string): string | null {\n    if (selector.startsWith('@e') || selector.startsWith('@c')) {\n      const entry = this.refMap.get(selector.slice(1));\n      return entry?.role ?? null;\n    }\n    return null;\n  }\n","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/garrytan/gstack/blob/94993f74012782fd94416dd44b8314f6363a13a4/browse/src/tab-session.ts#L81-L117","documentation":"Thrown by resolveRef when the ref IS present in refMap but `entry.locator.count()` returns 0 — the element was removed from the DOM after the snapshot was taken. The message includes the role and accessible name from the original snapshot so you can identify which element went stale.","triggerScenarios":"The page mutated (SPA re-render, list virtualization, modal close, AJAX update) between the snapshot and the action; the element was in an iframe that detached; a CSS transition completed and the node was swapped.","commonSituations":"Single-page apps that re-render on every state change; clicking a ref on a list that re-virtualizes on scroll; clicking an element inside an iframe whose parent navigated.","solutions":["Re-run `snapshot` to capture a fresh refMap against the current DOM.","For SPAs, snapshot closer to the action (immediately after the wait-for that confirms the element is stable).","Use a CSS selector targeting a stable attribute (data-testid) if the element re-render is intermittent.","Wait for the element to settle (waitForSelector) before re-snapshotting."],"exampleFix":"// before\nawait click(session, '@e3'); // element re-rendered → stale\n// after\nawait page.waitForSelector('[data-testid=submit]');\nawait snapshot(session);\nawait click(session, '@e3');","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"async function clickStable(session: TabSession, sel: string) {\n  for (let attempt = 0; attempt < 2; attempt++) {\n    try { return await click(session, sel); }\n    catch (e: any) {\n      if (/is stale/.test(e.message)) {\n        await snapshot(session); // re-resolve ref against fresh DOM\n        continue;\n      }\n      throw e;\n    }\n  }\n  throw new Error(`ref ${sel} stayed stale after re-snapshot`);\n}","preventionTips":["Wait for the element to settle (waitForSelector) before snapshotting.","For SPAs, snapshot immediately before each action rather than batching.","Use CSS selectors on data-testid for elements that re-render.","Treat stale-ref as recoverable — re-snapshot and retry once."],"tags":["refs","stale-dom","spa","navigation","tab-session"],"backgroundTag":null,"analyzedSha":"94993f74012782fd94416dd44b8314f6363a13a4","analyzedAt":"2026-08-12T04:06:23.140Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}