{"record":{"id":"4062f15af525c840","repo":"jackwener/OpenCLI","slug":"trip-com-tour-search-returned-malformed-data","errorCode":null,"errorMessage":"Trip.com tour search returned malformed data","messagePattern":"Trip\\.com tour search returned malformed data","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/trip/tour.js","lineNumber":56,"sourceCode":"        { name: 'limit', type: 'int', default: 20, help: 'Number of tours (1-50)' },\n    ],\n    columns: [\n        'rank',\n        'name', 'type',\n        'rating', 'reviews',\n        'price', 'currency',\n        'url',\n    ],\n    func: async (page, kwargs) => {\n        const query = parseKeyword('query', kwargs.query);\n        const tourType = parseTourType(kwargs.type);\n        const limit = parseListLimit(kwargs.limit);\n\n        const searchUrl = buildTourSearchUrl(query, tourType);\n        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,","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/trip/tour.js#L38-L74","documentation":"This CommandExecutionError is thrown when the in-page tour-search script returns null or a non-object, meaning the browser automation could not extract the expected structured result from the Trip.com tours page. It indicates the page did not run/complete the embedded extraction JS as designed — a page-level failure, not an empty search.","triggerScenarios":"page.evaluate(buildTourSearchJs(query)) returning undefined/null or a primitive — e.g. the extraction script threw inside the page, the page navigated away/redirected, or the DOM the script expects never rendered.","commonSituations":"Slow network so the page never reached the expected state; Trip.com redirecting to a different regional domain or consent page; the extraction script's entry-point selector disappearing after a site update.","solutions":["Retry the search — transient page-load failures often resolve on a second attempt.","Increase any page load/timeout budgets so the tours page fully renders before evaluation.","Manually open the built searchUrl in a browser to see what the page actually shows (redirect, consent wall, error page).","Update buildTourSearchJs if the site changed structure or entry behavior."],"exampleFix":"// before\nconst result = await page.goto(searchUrl); // no wait\nconst r = await page.evaluate(buildTourSearchJs(query));\n// after\nawait page.goto(searchUrl, { waitUntil: 'networkidle' });\nconst r = await page.evaluate(buildTourSearchJs(query));","handlingStrategy":"retry","validationCode":"// preflight: ensure the browser session is alive and the URL is reachable\nconst resp = await fetch(searchUrl, { method: 'HEAD' }).catch(() => null);\nif (!resp || !resp.ok) throw new Error('Trip.com tours page unreachable before automation');","typeGuard":"function isSearchResult(r) { return r !== null && typeof r === 'object' && !Array.isArray(r); }","tryCatchPattern":"try {\n  const rows = await tourSearch(query, type);\n} catch (e) {\n  if (/malformed data/.test(e.message)) {\n    await sleep(2000);\n    return tourSearch(query, type); // single retry\n  }\n  throw e;\n}","preventionTips":["Use waitUntil: 'networkidle' (or equivalent) before evaluating page scripts.","Keep the browser session healthy; avoid navigating mid-extraction.","Add a bounded retry with backoff around browser-automation searches.","Watch for regional redirects/consent pages and handle them before extraction."],"tags":["scraping","browser-automation","trip-com"],"backgroundTag":"page-render-failure","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}