{"record":{"id":"9b4782e06287d7d8","repo":"jackwener/OpenCLI","slug":"invalid-wait-for-selector-kwargs-wait-for","errorCode":null,"errorMessage":"Invalid --wait-for selector \"${kwargs['wait-for']}\": ${waitResult.error || 'querySelector failed'}","messagePattern":"Invalid --wait-for selector \"(.+?)\": (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"clis/web/read.js","lineNumber":429,"sourceCode":"        { name: 'stdout', type: 'boolean', default: false, help: 'Print markdown to stdout instead of saving to a file' },\n    ],\n    columns: ['title', 'author', 'publish_time', 'status', 'size', 'saved'],\n    func: async (page, kwargs, debug = false) => {\n        const url = kwargs.url;\n        const waitSeconds = kwargs.wait ?? 3;\n        const waitUntil = normalizeWaitUntil(kwargs['wait-until']);\n        const frameMode = normalizeFrameMode(kwargs.frames);\n        const shouldDiagnose = boolish(kwargs.diagnose) || debug || !!process.env.OPENCLI_VERBOSE;\n        const networkEntries = [];\n        const captureSupported = (waitUntil === 'networkidle' || shouldDiagnose)\n            ? await maybeStartNetworkCapture(page)\n            : false;\n        // Navigate to the target URL\n        await page.goto(url);\n        if (kwargs['wait-for']) {\n            const waitResult = await page.evaluate(buildWaitForSelectorAcrossFramesJs(String(kwargs['wait-for']), waitSeconds * 1000));\n            if (waitResult?.invalidSelector) {\n                throw new Error(`Invalid --wait-for selector \"${kwargs['wait-for']}\": ${waitResult.error || 'querySelector failed'}`);\n            }\n            if (!waitResult?.ok) {\n                throw new Error(`Timed out waiting for selector \"${kwargs['wait-for']}\" in main document or same-origin iframes`);\n            }\n        } else if (waitUntil !== 'networkidle') {\n            await page.wait(waitSeconds);\n        }\n        if (waitUntil === 'networkidle') {\n            if (!captureSupported) {\n                throw new Error('Network capture is unavailable, so --wait-until networkidle cannot be satisfied');\n            }\n            const idle = await waitForNetworkIdle(page, waitSeconds, networkEntries);\n            if (!idle?.ok) {\n                throw new Error(`Timed out waiting for network idle after ${waitSeconds}s`);\n            }\n        }\n        // Extract article content using browser-side heuristics\n        const data = await page.evaluate(buildRenderAwareExtractorJs({ frames: frameMode }));","sourceCodeStart":411,"sourceCodeEnd":447,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/web/read.js#L411-L447","documentation":"The web read command's --wait-for option runs buildWaitForSelectorAcrossFramesJs, which waits up to waitSeconds for a CSS selector to appear in the main document or same-origin iframes. If the result flags invalidSelector, the command throws Error with the selector, the underlying querySelector error, or a generic 'querySelector failed' fallback — meaning the --wait-for value is not a syntactically valid CSS selector, not a timeout.","triggerScenarios":"Passing --wait-for with a malformed CSS selector (unbalanced brackets/quotes, invalid pseudo-class like :contains, plain text instead of a selector, or an XPath expression) so page.evaluate returns {invalidSelector:true, error:...}.","commonSituations":"Users pass text like \"Login button\" instead of a CSS selector; use XPath syntax ('//div[@id]') which querySelector doesn't accept; typo'd pseudo-selectors unsupported by the browser engine; shell escaping mangles quotes/brackets in the selector.","solutions":["Correct the --wait-for value to a valid CSS selector, e.g. --wait-for \"#main .content\" instead of \"//div\" or prose text.","Test the selector in the browser DevTools console with document.querySelector('<selector>') to confirm it parses.","Escape quotes/brackets properly for your shell so the selector reaches the CLI intact.","Replace unsupported selectors (XPath, jQuery extensions like :contains) with standard CSS equivalents."],"exampleFix":"// before\n$ opencli web read https://example.com --wait-for \"//div[@id='app']\"\n// Error: Invalid --wait-for selector \"//div[@id='app']\": querySelector failed\n// after (valid CSS selector)\n$ opencli web read https://example.com --wait-for \"div#app\"","handlingStrategy":"validation","validationCode":"const sel = kwargs['wait-for'];\nif (sel) {\n  try { document.createDocumentFragment().querySelector(sel); }\n  catch (e) { throw new Error(`--wait-for is not a valid CSS selector: ${sel}`); }\n}","typeGuard":"function isValidCssSelector(sel) {\n  if (typeof sel !== 'string' || !sel.trim()) return false;\n  try { document.createDocumentFragment().querySelector(sel); return true; }\n  catch { return false; }\n}","tryCatchPattern":"try {\n  await readPage(url, { waitFor: sel });\n} catch (e) {\n  if (/Invalid --wait-for selector/.test(e.message)) {\n    // fix the selector to valid CSS (no XPath, no prose) and retry\n  } else throw e;\n}","preventionTips":["Validate the selector with document.querySelector in DevTools before passing it to --wait-for.","Use only standard CSS selectors — no XPath ('//div') or jQuery-only pseudo-classes like :contains.","Quote/escape the selector for your shell so brackets and quotes survive parsing.","Distinguish this from the timeout error: 'Invalid selector' means syntax, 'Timed out' means the element never appeared."],"tags":["css-selector","cli-usage","browser-automation"],"backgroundTag":"invalid-css-selector","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}