{"record":{"id":"83143a67dff58f51","repo":"jackwener/OpenCLI","slug":"trip-com-train-dom-extraction-returned-malformed-r","errorCode":null,"errorMessage":"Trip.com train DOM extraction returned malformed rows","messagePattern":"Trip\\.com train DOM extraction returned malformed rows","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/trip/train.js","lineNumber":60,"sourceCode":"    ],\n    func: async (page, kwargs) => {\n        const from = parseKeyword('from', kwargs.from);\n        const to = parseKeyword('to', kwargs.to);\n        const country = parseKeyword('country', kwargs.country);\n        const limit = parseListLimit(kwargs.limit);\n\n        const searchUrl = buildTrainRouteUrl(country, from, to);\n        await page.goto(searchUrl);\n        const waitResult = await page.evaluate(WAIT_FOR_TRAINS_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 train timetable did not render (state=${String(waitResult)}); check the city names and --country`);\n        }\n        const raw = await page.evaluate(buildTrainExtractJs());\n        if (!Array.isArray(raw)) {\n            throw new CommandExecutionError('Trip.com train DOM extraction returned malformed rows');\n        }\n        if (raw.length === 0) {\n            throw new EmptyResultError('trip train', `No timetable for ${from} to ${to} (${country})`);\n        }\n        return raw.slice(0, limit).map((r, i) => ({\n            rank: i + 1,\n            departureTime: r.departureTime,\n            fromStation: r.fromStation,\n            arrivalTime: r.arrivalTime,\n            toStation: r.toStation,\n            duration: r.duration,\n            changes: r.changes,\n            url: searchUrl,\n        }));\n    },\n});\n","sourceCodeStart":42,"sourceCodeEnd":77,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/trip/train.js#L42-L77","documentation":"CommandExecutionError thrown when page.evaluate(buildTrainExtractJs()) returns a non-array. The wait probe said 'content' (results rendered), but the extraction script came back in an unexpected shape, meaning the DOM structure changed or the script was sabotaged by page code.","triggerScenarios":"Trip.com renders train rows in a new DOM structure so buildTrainExtractJs() returns null/an object instead of an array; a page-side script overwrites the expected global; or the evaluate result fails to serialize as an array.","commonSituations":"Trip.com frontend redesign/A-B test changing row markup; extraction script pinned to old class names; headless browser returning undefined because of a serialization issue in the evaluate payload.","solutions":["Update buildTrainExtractJs() selectors to match the current Trip.com train-card markup","Log the raw evaluate result to see the actual shape returned","Pin or bypass A/B test variants (cookies/URL params) so the stable layout renders","Add a defensive Array.isArray check with diagnostics upstream of this throw"],"exampleFix":"// before\nconst raw = await page.evaluate(buildTrainExtractJs());\nif (!Array.isArray(raw)) throw new CommandExecutionError('malformed rows');\n// after\nconst raw = await page.evaluate(buildTrainExtractJs());\nif (!Array.isArray(raw)) {\n  console.error('extract returned:', raw);\n  throw new CommandExecutionError('malformed rows: ' + JSON.stringify(raw).slice(0, 200));\n}","handlingStrategy":"type-guard","validationCode":"// cannot pre-validate; guard the result instead\nconst raw = await page.evaluate(buildTrainExtractJs());\nif (raw == null) throw new Error('extract returned null — selector drift likely');","typeGuard":"function isTrainRows(v) {\n  return Array.isArray(v) && v.every(r =>\n    r && typeof r.departureTime === 'string' && typeof r.fromStation === 'string');\n}","tryCatchPattern":"try {\n  const rows = await getTripTrains(args);\n} catch (e) {\n  if (e instanceof CommandExecutionError && e.message.includes('malformed rows')) {\n    console.error('Trip.com DOM changed; capture page HTML for selector update');\n  }\n  throw e;\n}","preventionTips":["Pin test snapshots of the extractor output in CI","Monitor Trip.com markup changes; alert on this error","Log the raw evaluate result alongside the throw","Keep extraction selectors centralized for quick patching"],"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"}