{"record":{"id":"9ddca788eb88f751","repo":"jackwener/OpenCLI","slug":"trip-com-flight-dom-extraction-returned-malformed-9ddca7","errorCode":null,"errorMessage":"Trip.com flight DOM extraction returned malformed rows${reason}","messagePattern":"Trip\\.com flight DOM extraction returned malformed rows(.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/trip/flight.js","lineNumber":68,"sourceCode":"        const date = parseIsoDate('date', kwargs.date);\n        const limit = parseListLimit(kwargs.limit);\n\n        const searchUrl = buildFlightSearchUrl(fromCode, toCode, date);\n        await page.goto(searchUrl);\n        const waitResult = await page.evaluate(WAIT_FOR_FLIGHTS_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 flight page did not render flight cards (state=${String(waitResult)})`);\n        }\n        const raw = await page.evaluate(buildFlightExtractJs());\n        if (!Array.isArray(raw)) {\n            const reason = raw && typeof raw === 'object' && typeof raw.error === 'string'\n                && /^malformed flight card \\d+: [a-z /]+$/.test(raw.error)\n                ? `: ${raw.error}`\n                : '';\n            throw new CommandExecutionError(`Trip.com flight DOM extraction returned malformed rows${reason}`);\n        }\n        if (raw.length === 0) {\n            throw new EmptyResultError('trip flight', `No flights for ${fromCode} to ${toCode} on ${date}`);\n        }\n        return raw.slice(0, limit).map((r, i) => ({\n            rank: i + 1,\n            airline: r.airline,\n            departureTime: r.departureTime,\n            departureAirport: r.departureAirport,\n            arrivalTime: r.arrivalTime,\n            arrivalAirport: r.arrivalAirport,\n            duration: r.duration,\n            stops: r.stops,\n            price: r.price,\n            currency: r.currency,\n            url: searchUrl,\n        }));\n    },","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/trip/flight.js#L50-L86","documentation":"This CommandExecutionError from clis/trip/flight.js is thrown when buildFlightExtractJs() returns a non-array, meaning the in-page extractor found flight-card-like elements but failed to parse at least one row into a clean object. When the returned object carries an error string matching /^malformed flight card \\d+: [a-z /]+$/, that per-card reason is appended to the message. It signals a DOM structure change or unexpected card content, not an empty page.","triggerScenarios":"page.evaluate(buildFlightExtractJs()) returns null, an object like { error: 'malformed flight card 3: missing airline' }, or any non-array because one or more flight cards lacked expected fields/shape.","commonSituations":"Trip.com A/B-testing a new flight card layout; ads or sponsored cards injected into the results list with different markup; partially rendered cards missing airline/time nodes when the extractor runs; regional variants of the site with different DOM.","solutions":["Read the appended reason (if any) to see which card and field was malformed.","Open the page in a browser and inspect the flight card DOM for layout changes.","Update buildFlightExtractJs() field selectors to match the current markup.","Add defensive field extraction (defaulting missing fields) so a single odd card does not abort the whole extraction, then retry."],"exampleFix":"// before\nthrow new CommandExecutionError(`...malformed rows${reason}`);\n// after\nif (!Array.isArray(raw)) {\n  console.warn('extraction returned', raw);\n  raw = []; // or skip malformed cards inside buildFlightExtractJs\n}\nthrow new CommandExecutionError(`...malformed rows${reason}`); // only when unrecoverable","handlingStrategy":"type-guard","validationCode":"// Cannot pre-validate server DOM; verify library/site assumptions after upgrade:\nconst smoke = await runTripFlight({ from: 'SFO', to: 'LAX', date: nextFriday(), limit: 1 });\nif (!Array.isArray(smoke)) throw new Error('extractor contract broken');","typeGuard":"const isExtractFailure = (e) => e instanceof CommandExecutionError && /malformed rows/.test(e.message);\nconst isArrayPayload = (v) => Array.isArray(v);","tryCatchPattern":"try {\n  const flights = await runTripFlight(args);\n} catch (e) {\n  if (isExtractFailure(e)) {\n    console.error(`Trip.com DOM may have changed: ${e.message}`);\n    return null; // degrade gracefully, alert maintainers\n  }\n  throw e;\n}","preventionTips":["Include the extractor's raw.error reason in monitoring alerts.","Make in-page extractors skip malformed cards instead of failing whole extraction.","Re-run a smoke search after Trip.com releases UI changes.","Keep extraction selectors centralized and easily updatable."],"tags":["scraping","dom-parsing","trip-com","flights"],"backgroundTag":"dom-structure-changed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}