{"record":{"id":"e39cac9974c3c9de","repo":"jackwener/OpenCLI","slug":"resolution-code","errorCode":"${resolution.code}","errorMessage":"${resolution.message}","messagePattern":"\\$\\{resolution\\.message\\}","errorType":"error_code","errorClass":"TargetError","httpStatus":null,"severity":"error","filePath":"src/browser/base-page.ts","lineNumber":124,"sourceCode":"  frame?: { id?: string; url?: string; unreachableUrl?: string; name?: string };\n  childFrames?: CdpFrameTreeNode[];\n}\n\n/**\n * Execute `resolveTargetJs` once, throw structured `TargetError` on failure.\n * Single helper so click/typeText/scrollTo share one resolution pathway,\n * which is what the selector-first contract promises agents.\n */\nasync function runResolve(\n  page: { evaluate(js: string): Promise<unknown> },\n  ref: string,\n  opts: ResolveOptions = {},\n): Promise<ResolveSuccess> {\n  const resolution = (await page.evaluate(resolveTargetJs(ref, opts))) as\n    | { ok: true; matches_n: number; match_level: TargetMatchLevel }\n    | { ok: false; code: TargetErrorCode; message: string; hint: string; candidates?: string[]; matches_n?: number };\n  if (!resolution.ok) {\n    throw new TargetError({\n      code: resolution.code,\n      message: resolution.message,\n      hint: resolution.hint,\n      candidates: resolution.candidates,\n      matches_n: resolution.matches_n,\n    });\n  }\n  return { matches_n: resolution.matches_n, match_level: resolution.match_level };\n}\n\nfunction previewText(text: string | undefined): string | undefined {\n  const preview = (text ?? '').replace(/\\s+/g, ' ').trim().slice(0, 300);\n  return preview ? `Response preview: ${preview}` : undefined;\n}\n\nfunction parseKeyChord(rawKey: string): { key: string; modifiers: string[] } {\n  const parts = rawKey.split('+').map(part => part.trim()).filter(Boolean);\n  if (parts.length <= 1) return { key: rawKey, modifiers: [] };","sourceCodeStart":106,"sourceCodeEnd":142,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/src/browser/base-page.ts#L106-L142","documentation":"runResolve (src/browser/base-page.ts:124) executes a target-resolution script in the page via page.evaluate and expects a structured result. When the in-page resolver returns {ok:false}, the failure (code, message, hint, optional candidate list, match count) is re-thrown as a TargetError. This is the library's normal way of reporting 'your selector/target reference did not resolve to an element', not an unexpected crash.","triggerScenarios":"Resolving an element target whose CSS/XPath/text/role selector matches zero elements (code like not-found), matches ambiguously, or whose ref/frame the resolver can't locate in the current DOM — e.g. the page changed since the snapshot that produced the ref.","commonSituations":"DOM updated by a re-render or framework hydration so refs from a previous snapshot no longer exist; typos in selectors; targeting elements inside iframes without proper frame options; dynamic content not yet rendered when resolution runs; strict-mode ambiguity with several matches.","solutions":["Read resolution.code, hint and candidates in the TargetError — the candidates array lists near-misses to correct your selector against.","Re-take the element ref/snapshot from the current page state before retrying; stale refs are the most common cause.","Add a wait for the element to appear (waitForSelector or the library's auto-wait options) before resolving.","Narrow or broaden the selector: use a more specific CSS/XPath for ambiguity, or a text/role selector for brittle class-name-based ones.","If the element is in an iframe, pass the appropriate frame option to the resolver."],"exampleFix":"// before\nconst el = await runResolve(page, { ref: 'e12' }); // stale snapshot ref\n// after\nawait page.waitForSelector('[data-testid=\"checkout-btn\"]', { timeout: 5000 });\nconst el = await runResolve(page, { ref: await takeFreshSnapshot(page), opts: { timeout: 5000 } });","handlingStrategy":"try-catch","validationCode":"// pre-check the selector before resolving:\nconst count = await page.evaluate(`document.querySelectorAll(${JSON.stringify(selector)}).length`);\nif (count === 0) throw new SkipError(`selector matches nothing: ${selector}`);","typeGuard":"function isTargetError(err: unknown): err is TargetError {\n  return err instanceof TargetError || (typeof err === 'object' && err !== null && 'code' in err && 'hint' in err);\n}","tryCatchPattern":"try {\n  return await runResolve(page, ref, opts);\n} catch (err) {\n  if (isTargetError(err)) {\n    console.warn(`resolve failed [${err.code}]: ${err.hint}`, err.candidates);\n    await page.waitForTimeout(1000);\n    return runResolve(page, ref, opts); // retry once after wait\n  }\n  throw err;\n}","preventionTips":["Always re-take element refs from a fresh snapshot; refs are invalid after DOM re-renders.","Read err.code and err.candidates — they tell you exactly why the selector failed and what was near.","Add explicit waits for dynamic content before resolving.","Prefer resilient selectors (data-testid, text, role) over generated class names."],"tags":["target-resolution","selector","dom","playwright-style"],"backgroundTag":"element-not-found","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}