{"record":{"id":"347e9de9b284f29b","repo":"jackwener/OpenCLI","slug":"trip-com-hotel-dom-extraction-returned-malformed-r","errorCode":null,"errorMessage":"Trip.com hotel DOM extraction returned malformed rows","messagePattern":"Trip\\.com hotel DOM extraction returned malformed rows","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/trip/hotel-search.js","lineNumber":62,"sourceCode":"        const checkin = parseIsoDate('checkin', kwargs.checkin);\n        const checkout = parseIsoDate('checkout', kwargs.checkout);\n        if (checkin >= checkout) {\n            throw new ArgumentError(`--checkin must be before --checkout (got ${checkin} .. ${checkout})`);\n        }\n        const limit = parseListLimit(kwargs.limit);\n\n        const searchUrl = buildHotelSearchUrl(cityId, checkin, checkout);\n        await page.goto(searchUrl);\n        const waitResult = await page.evaluate(WAIT_FOR_HOTELS_JS);\n        if (waitResult === '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 (waitResult !== 'content') {\n            throw new CommandExecutionError(`Trip.com hotel page did not render hotel cards (state=${String(waitResult)})`);\n        }\n        const raw = await page.evaluate(buildHotelExtractJs());\n        if (!Array.isArray(raw)) {\n            throw new CommandExecutionError('Trip.com hotel DOM extraction returned malformed rows');\n        }\n        if (raw.length === 0) {\n            throw new EmptyResultError('trip hotel-search', `No hotels for city ${cityId} on ${checkin} .. ${checkout}`);\n        }\n        return raw.slice(0, limit).map((r, i) => ({\n            rank: i + 1,\n            name: r.name,\n            score: r.score,\n            reviewLabel: r.reviewLabel,\n            reviews: r.reviews,\n            location: r.location,\n            room: r.room,\n            price: r.price,\n            currency: r.currency,\n            url: searchUrl,\n        }));\n    },\n});","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/trip/hotel-search.js#L44-L80","documentation":"This CommandExecutionError from clis/trip/hotel-search.js is thrown when buildHotelExtractJs() returns a non-array after the page did reach the content state, meaning the extractor encountered hotel cards whose markup it could not parse. Unlike the flight command, no per-card reason is extracted here — the message is fixed. It almost always reflects a Trip.com DOM/layout change or unusual card content (e.g. ads) rather than user error.","triggerScenarios":"page.evaluate(buildHotelExtractJs()) returns null/undefined/an object instead of an array, immediately after WAIT_FOR_HOTELS_JS returned 'content'.","commonSituations":"Trip.com rolling out a new hotel card layout or A/B test; sponsored/featured hotel tiles with different DOM interleaved in results; price/availability widgets replacing standard card nodes; regional markup differences.","solutions":["Inspect the current Trip.com hotel card DOM in a browser and diff against the selectors in buildHotelExtractJs().","Update buildHotelExtractJs() to handle the new markup and to skip (rather than fail on) non-standard cards.","Add a debug dump of the non-array return value at throw time to aid future diagnosis.","Pin/verify against a known-good Trip.com layout and re-run after fixing the extractor."],"exampleFix":"// before\nconst raw = await page.evaluate(buildHotelExtractJs());\nif (!Array.isArray(raw)) {\n  throw new CommandExecutionError('Trip.com hotel DOM extraction returned malformed rows');\n}\n// after\nconst raw = await page.evaluate(buildHotelExtractJs());\nif (!Array.isArray(raw)) {\n  console.error('hotel extract returned:', JSON.stringify(raw).slice(0, 500));\n  throw new CommandExecutionError(`Trip.com hotel DOM extraction returned malformed rows: ${raw && raw.error || 'unknown'}`);\n}","handlingStrategy":"type-guard","validationCode":"// Post-hoc guard on any scraped payload you consume downstream:\nconst safeRows = Array.isArray(rows) && rows.every((r) => r && typeof r.hotelName === 'string' && typeof r.price !== 'undefined');\nif (!safeRows) console.warn('hotel payload shape unexpected; Trip.com DOM may have changed');","typeGuard":"const isExtractFailure = (e) => e instanceof CommandExecutionError && /malformed rows/.test(e.message);\nconst isHotelRowArray = (v) => Array.isArray(v) && v.every((r) => r && typeof r === 'object');","tryCatchPattern":"try {\n  const hotels = await runTripHotelSearch(args);\n} catch (e) {\n  if (isExtractFailure(e)) {\n    console.error('Trip.com hotel extractor needs updating (DOM change likely)');\n    return null; // degrade and alert\n  }\n  throw e;\n}","preventionTips":["Keep buildHotelExtractJs() tolerant of ad/sponsored tiles (skip, don't fail).","Run periodic smoke hotel searches to catch layout drift early.","Dump the non-array extractor output when this error fires to speed diagnosis.","Validate downstream row shapes before relying on results."],"tags":["scraping","dom-parsing","trip-com","hotels"],"backgroundTag":"dom-structure-changed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}