{"record":{"id":"9f6693aec8440559","repo":"jackwener/OpenCLI","slug":"trip-com-deals-dom-extraction-returned-malformed-r","errorCode":null,"errorMessage":"Trip.com deals DOM extraction returned malformed rows","messagePattern":"Trip\\.com deals DOM extraction returned malformed rows","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/trip/deals.js","lineNumber":46,"sourceCode":"        'rank',\n        'title', 'offer',\n        'discount',\n        'url',\n    ],\n    func: async (page, kwargs) => {\n        const limit = parseListLimit(kwargs.limit);\n\n        await page.goto(buildDealsUrl());\n        const waitResult = await page.evaluate(WAIT_FOR_DEALS_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 deals page did not render deal tiles (state=${String(waitResult)})`);\n        }\n        const raw = await page.evaluate(buildDealsExtractJs());\n        if (!Array.isArray(raw)) {\n            throw new CommandExecutionError('Trip.com deals DOM extraction returned malformed rows');\n        }\n        // The Top Deals hub is a permanent curated page, so once the wait confirms it\n        // rendered, zero parsed tiles means the tile markup drifted, not an empty hub.\n        if (raw.length === 0) {\n            throw new CommandExecutionError('Trip.com deals hub rendered but no promotion tiles parsed (the tile markup may have changed)');\n        }\n        return raw.slice(0, limit).map((r, i) => ({\n            rank: i + 1,\n            title: r.title,\n            offer: r.offer,\n            discount: r.discount,\n            url: r.url,\n        }));\n    },\n});\n","sourceCodeStart":28,"sourceCodeEnd":62,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/trip/deals.js#L28-L62","documentation":"CommandExecutionError thrown when the deals page's in-page extractor (buildDealsExtractJs) returns a value that is not an array — the extraction script itself failed or returned an unexpected shape rather than a list of tiles. This distinguishes a broken extractor/serialization from a page with zero deals.","triggerScenarios":"page.evaluate(buildDealsExtractJs()) resolves to null/undefined/an object instead of an array — e.g. the script threw internally and a wrapper returned an error object, or the page context was replaced by a navigation before evaluation finished.","commonSituations":"Page navigated mid-evaluation (redirect after goto); extractor script version mismatch with page globals; evaluate returning an error object from a caught in-page exception; Trip.com serving an SPA shell that aborts the script.","solutions":["Rerun the command — a transient redirect/navigation often explains a one-off non-array result","Confirm the browser session lands on the real Top Deals page (no redirect loop)","Check that the extractor version matches the CLI version (reinstall/update the package)","Debug buildDealsExtractJs to return a structured error object so the failure mode is diagnosable"],"exampleFix":"// before\nconst raw = await page.evaluate(buildDealsExtractJs());\nif (!Array.isArray(raw)) {\n    throw new CommandExecutionError('Trip.com deals DOM extraction returned malformed rows');\n}\n// after\nconst raw = await page.evaluate(buildDealsExtractJs());\nif (!Array.isArray(raw)) {\n    throw new CommandExecutionError(\n        `Trip.com deals DOM extraction returned malformed rows (got ${typeof raw})`);\n}","handlingStrategy":"type-guard","validationCode":"// Verify the page context is stable before evaluating the extractor\nconst url = page.url();\nif (!url.includes('deals')) console.warn('Page navigated away from deals hub; extraction may return non-array');","typeGuard":"function isArrayRows(v) {\n    return Array.isArray(v);\n}","tryCatchPattern":"try {\n    const deals = await runTripDeals();\n} catch (e) {\n    if (e instanceof CommandExecutionError && /malformed rows/.test(e.message)) {\n        // non-array extract: retry once; if persistent, log typeof result for diagnosis\n    } else { throw e; }\n}","preventionTips":["Guard against mid-evaluation navigation (wait for network idle before extract)","Have the in-page extractor return structured errors instead of bare failure","Pin CLI/extractor versions so evaluate serialization matches expectations"],"tags":["scraping","serialization","dom-parsing","trip-com"],"backgroundTag":"unexpected-extract-shape","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}