{"record":{"id":"ac11fcdaa3ce2324","repo":"jackwener/OpenCLI","slug":"trip-com-transfer-dom-extraction-returned-malforme","errorCode":null,"errorMessage":"Trip.com transfer DOM extraction returned malformed rows","messagePattern":"Trip\\.com transfer DOM extraction returned malformed rows","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/trip/transfer.js","lineNumber":67,"sourceCode":"        const listUrl = buildTransferListUrl(city, airport);\n        await page.goto(listUrl);\n        const waitResult = await page.evaluate(WAIT_FOR_TRANSFERS_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 === 'empty') {\n            throw new EmptyResultError('trip transfer', `No airport transfers for ${city} (${airport})`);\n        }\n        if (waitResult !== 'content') {\n            throw new CommandExecutionError(`Trip.com transfer listing did not render (state=${String(waitResult)}); check the city and airport code`);\n        }\n        const landedPath = await page.evaluate('location.pathname');\n        if (!/\\/airport-transfers\\/[^/]+\\/airport-[^/]+/i.test(String(landedPath))) {\n            throw new CommandExecutionError(`Trip.com bounced ${city} / ${airport} to the transfer landing; check the city name matches the airport IATA code`);\n        }\n        const raw = await page.evaluate(buildTransferExtractJs());\n        if (!Array.isArray(raw)) {\n            throw new CommandExecutionError('Trip.com transfer DOM extraction returned malformed rows');\n        }\n        if (raw.length === 0) {\n            throw new CommandExecutionError('Trip.com transfer cards rendered but parser did not find required price anchors');\n        }\n        return raw.slice(0, limit).map((r, i) => ({\n            rank: i + 1,\n            type: r.type,\n            passengers: r.passengers,\n            luggage: r.luggage,\n            price: r.price,\n            currency: r.currency,\n            url: listUrl,\n        }));\n    },\n});\n","sourceCodeStart":49,"sourceCodeEnd":83,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/trip/transfer.js#L49-L83","documentation":"CommandExecutionError thrown when the transfer page loaded and passed all checks, but page.evaluate(buildTransferExtractJs()) returned a non-array. The rendered page's DOM no longer matches the extraction script's expectations, so it produced null or an object instead of a rows array.","triggerScenarios":"Trip.com redesign of transfer card markup so the extractor's selectors match nothing and it returns null; a page script clobbering the extractor's globals; serialization failure of the evaluate payload.","commonSituations":"Trip.com A/B tests or frontend deploys changing transfer card structure; extractor pinned to old class names; headless browser rendering a fallback/legacy layout.","solutions":["Update buildTransferExtractJs() to match current transfer card DOM","Log the raw evaluate output to diagnose the actual returned shape","Disable/bypass A/B variants so the stable layout renders","Retry in case a partial render produced a degenerate DOM"],"exampleFix":"// before\nconst raw = await page.evaluate(buildTransferExtractJs());\nif (!Array.isArray(raw)) throw new CommandExecutionError('malformed rows');\n// after\nconst raw = await page.evaluate(buildTransferExtractJs());\nif (!Array.isArray(raw)) {\n  await page.screenshot({ path: 'transfer-debug.png' });\n  throw new CommandExecutionError('malformed rows, screenshot saved');\n}","handlingStrategy":"type-guard","validationCode":"// guard the raw result shape\nconst raw = await page.evaluate(buildTransferExtractJs());\nif (raw === null) throw new Error('extractor matched nothing — DOM drift');","typeGuard":"function isTransferRows(v) {\n  return Array.isArray(v) && v.every(r => r && typeof r.type === 'string' && 'passengers' in r);\n}","tryCatchPattern":"try {\n  const rows = await getTransfers(args);\n} catch (e) {\n  if (e.message.includes('malformed rows')) {\n    await page.screenshot({ path: 'transfer-dom-drift.png' }).catch(() => {});\n  }\n  throw e;\n}","preventionTips":["Snapshot extractor output in CI to catch drift early","Keep selectors in one place for fast updates","Log raw evaluate output on failure","Watch for Trip.com frontend deploys/A-B tests"],"tags":["scraping","dom-parsing","trip-com","schema-drift"],"backgroundTag":"dom-extraction-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}