{"record":{"id":"7cbb17c3e0c44b98","repo":"jackwener/OpenCLI","slug":"trip-com-tour-search-captured-products-but-none-ca","errorCode":null,"errorMessage":"Trip.com tour search captured products but none carried a name (the product markup may have changed)","messagePattern":"Trip\\.com tour search captured products but none carried a name \\(the product markup may have changed\\)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/trip/tour.js","lineNumber":71,"sourceCode":"        await page.goto(searchUrl);\n        const result = await page.evaluate(buildTourSearchJs(query));\n        if (!result || typeof result !== 'object') {\n            throw new CommandExecutionError('Trip.com tour search returned malformed data');\n        }\n        if (result.status === 'captcha') {\n            throw new AuthRequiredError('trip.com', 'Trip.com is asking for a verification; complete it in your browser session and retry');\n        }\n        if (result.status === 'empty') {\n            throw new EmptyResultError('trip tour', `No ${kwargs.type || 'private'} tours for \"${query}\"`);\n        }\n        if (result.status !== 'content') {\n            throw new CommandExecutionError(`Trip.com tour search did not return results (state=${String(result.status)})`);\n        }\n        // Products captured but none carry a name is drift (schema moved), not an empty search;\n        // a genuine no-match resolves as status 'empty' above off the page's \"0 routes found\".\n        const rows = Array.isArray(result.rows) ? result.rows.filter((r) => r.name) : [];\n        if (rows.length === 0) {\n            throw new CommandExecutionError('Trip.com tour search captured products but none carried a name (the product markup may have changed)');\n        }\n        return rows.slice(0, limit).map((r, i) => ({\n            rank: i + 1,\n            name: r.name,\n            type: r.type,\n            rating: r.rating,\n            reviews: r.reviews,\n            price: r.price,\n            currency: 'USD',\n            url: r.url,\n        }));\n    },\n});\n","sourceCodeStart":53,"sourceCodeEnd":85,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/trip/tour.js#L53-L85","documentation":"This CommandExecutionError distinguishes schema drift from a genuine no-match: the page reported status 'content' (results were captured), but after filtering rows for a truthy name, none remained. The product markup Trip.com uses for tour titles likely changed, so map/filter assumptions no longer hold.","triggerScenarios":"result.rows is an array whose entries all lack r.name — e.g. Trip.com renamed the title field in tour cards, or the extraction selector now grabs non-product elements.","commonSituations":"Trip.com redesigning tour card markup; extraction JS reading an old class/data attribute; region-specific page variants with different DOM structure.","solutions":["Capture a raw result.rows sample and update buildTourSearchJs to the new title field/selector.","Verify against the live page which element holds the tour name and adjust the extraction selector.","Update the CLI to a version patched for the new markup.","Compare across regions/locales — a variant page may explain the differing DOM."],"exampleFix":"// before\nname: card.querySelector('.title')?.textContent,\n// after (new markup)\nname: card.querySelector('[data-testid=\"tour-name\"]')?.textContent ?? card.querySelector('.title')?.textContent,","handlingStrategy":"type-guard","validationCode":"const rows = Array.isArray(result?.rows) ? result.rows : [];\nif (rows.length && rows.every(r => !r || typeof r.name !== 'string' || !r.name.trim())) {\n  console.warn('Tour markup drift suspected: no named rows extracted');\n}","typeGuard":"function hasName(r) { return r != null && typeof r.name === 'string' && r.name.trim().length > 0; }","tryCatchPattern":"try {\n  const rows = await tourSearch(query, type);\n} catch (e) {\n  if (/none carried a name/.test(e.message)) {\n    // treat as upstream markup change: capture diagnostics, alert maintainers\n    captureDiagnostics(e);\n  }\n  throw e;\n}","preventionTips":["Add integration tests against a stable tour page to catch markup changes early.","Write extraction selectors resilient to class-name changes (data attributes, structure).","Alert on this error — it is drift, not user error, and affects all users.","Keep the CLI/scraper updated; check upstream issues when it fires."],"tags":["schema-drift","scraping","markup-change"],"backgroundTag":"schema-drift-upstream","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}