{"record":{"id":"8393d6a30002ac7a","repo":"jackwener/OpenCLI","slug":"uisdc-news-selector-drift-reason","errorCode":null,"errorMessage":"UISDC news selector drift: ${reason}","messagePattern":"UISDC news selector drift: (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/uisdc/news.js","lineNumber":58,"sourceCode":"          return {\n            rank: index + 1,\n            title: el.querySelector('.dubao-title')?.textContent || '',\n            summary: el.querySelector('.dubao-content')?.textContent || '',\n            url: anchor ? new URL(anchor.getAttribute('href'), location.href).href : '',\n          };\n        });\n        return { ok: true, rows };\n      })()\n    `;\n}\n\nfunction toRows(payload, limit) {\n    if (!payload || typeof payload !== 'object') {\n        throw new CommandExecutionError('UISDC news page returned an unreadable payload');\n    }\n    if (!payload.ok) {\n        const reason = typeof payload.reason === 'string' && payload.reason.trim() ? payload.reason.trim() : 'selector-drift';\n        throw new CommandExecutionError(\n            `UISDC news selector drift: ${reason}`,\n            payload.title ? `Page title: ${payload.title}` : undefined,\n        );\n    }\n    const rows = (Array.isArray(payload.rows) ? payload.rows : [])\n        .map((row, index) => ({\n            rank: index + 1,\n            title: normalizeText(row.title),\n            summary: normalizeText(row.summary),\n            url: normalizeText(row.url),\n        }))\n        .filter((row) => row.title && row.url);\n    if (rows.length === 0) {\n        throw new EmptyResultError('uisdc news', 'UISDC news page loaded, but no news rows with title and URL were extracted.');\n    }\n    return rows.slice(0, limit).map((row, index) => ({ ...row, rank: index + 1 }));\n}\n","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/uisdc/news.js#L40-L76","documentation":"When the in-page extractor reports payload.ok === false, toRows throws CommandExecutionError 'UISDC news selector drift: <reason>'. This means the page structure changed so the extraction selectors no longer matched; the reason defaults to 'selector-drift' or carries the page-reported reason string, and the page title is attached as a hint.","triggerScenarios":"UISDC redesigned its news page HTML so the extract script's selectors find no nodes and it returns {ok:false, reason}; the script's own failure path sets a custom reason string.","commonSituations":"Site redesign or A/B test changing DOM classes; lazy-loaded content not present when the script runs; region-specific markup differences.","solutions":["Read the reason in the message (and Page title hint) to identify what changed","Update the selectors inside buildExtractUisdcNewsJs in clis/uisdc/news.js to match the new DOM","Verify manually in a browser devtools which selector broke","If caused by slow loading, increase settleMs or wait for the specific node before extracting"],"exampleFix":"// before\nconst rows = [...document.querySelectorAll('.news-list li')];\n// after\nconst rows = [...document.querySelectorAll('.news-list li, .article-list__item')];","handlingStrategy":"try-catch","validationCode":"const probe = await page.$('.news-list li'); if (!probe) throw new Error('selector drift: .news-list li missing');","typeGuard":"function isOkPayload(p) { return !!p && typeof p === 'object' && p.ok === true && Array.isArray(p.rows); }","tryCatchPattern":"try { const rows = await loadUisdcNews(page, args); } catch (e) { if (/selector drift/.test(e.message)) { reportDrift(e.message, e.hint); return cachedRows; } throw e; }","preventionTips":["Pin a snapshot test of the page DOM to catch redesigns early","Write selectors resiliently (multiple candidates, semantic attributes)","Return the failing selector name as the reason from the in-page script","Monitor the site and alert on payload.ok === false"],"tags":["scraping","selector-drift","dom","command-execution-error"],"backgroundTag":"selector-drift","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}