{"record":{"id":"7a8903891c1dae7c","repo":"jackwener/OpenCLI","slug":"ctrip-ferry-rows-rendered-but-parser-did-not-find","errorCode":null,"errorMessage":"Ctrip ferry rows rendered but parser did not find required sailing anchors","messagePattern":"Ctrip ferry rows rendered but parser did not find required sailing anchors","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/ctrip/ferry.js","lineNumber":70,"sourceCode":"        const limit = parseListLimit(kwargs.limit);\n\n        const searchUrl = buildFerryListUrl(fromCity, toCity, date);\n        await page.goto(searchUrl);\n        const waitResult = await page.evaluate(WAIT_FOR_FERRY_JS);\n        if (waitResult === 'captcha') {\n            throw new AuthRequiredError('ship.ctrip.com', 'Ctrip is asking for a captcha; complete it in your browser session and retry');\n        }\n        if (waitResult !== 'content') {\n            throw new CommandExecutionError(`Ctrip ferry page did not render sailing rows (state=${String(waitResult)})`);\n        }\n        const renderedCardCount = await page.evaluate(buildScrollUntilJs('.list-item-parent', limit));\n        const raw = await page.evaluate(buildFerryExtractJs());\n        if (!Array.isArray(raw)) {\n            throw new CommandExecutionError('Ctrip ferry DOM extraction returned malformed rows');\n        }\n        if (raw.length === 0) {\n            if (Number(renderedCardCount) > 0) {\n                throw new CommandExecutionError('Ctrip ferry rows rendered but parser did not find required sailing anchors');\n            }\n            throw new EmptyResultError('ctrip ferry', `No sailings for ${fromCity} to ${toCity} on ${date}`);\n        }\n        return raw.slice(0, limit).map((r, i) => ({\n            rank: i + 1,\n            shipName: r.shipName,\n            departureTime: r.departureTime,\n            fromPort: r.fromPort,\n            arrivalTime: r.arrivalTime,\n            toPort: r.toPort,\n            duration: r.duration,\n            price: r.price,\n            status: r.status,\n            url: searchUrl,\n        }));\n    },\n});\n","sourceCodeStart":52,"sourceCodeEnd":88,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/ctrip/ferry.js#L52-L88","documentation":"CommandExecutionError thrown when the ferry page rendered sailing rows (.list-item-parent cards exist — renderedCardCount > 0) but buildFerryExtractJs returned an empty array because its required sailing anchors (specific links/fields inside each row) matched nothing. The CLI deliberately distinguishes this from EmptyResultError: data is visibly on the page, so an empty parse means the extractor's selectors no longer match Ctrip's row markup. This is a parser/site-contract drift signal, not a data absence.","triggerScenarios":"Calling `ctrip ferry <from> <to> --date <d>` where scrolling confirmed non-zero .list-item-parent cards but the extraction script's required per-row anchor selectors matched zero elements — class renames, restructured row internals, or anchors attaching after extraction ran (late lazy hydration).","commonSituations":"Ctrip shipping a front-end redesign or A/B test of the sailing row; CLI version predating a markup change; regional page variants with different DOM; scroll-until resolving before inner anchors mount, then extracting too early.","solutions":["Inspect a rendered .list-item-parent row in browser DevTools and update the anchor selectors in buildFerryExtractJs (clis/ctrip/utils.js) to the current markup","Upgrade opencli in case a newer release already tracks the new ship.ctrip.com markup","Add a short settle delay or re-scroll before extraction so lazily-attached anchors exist when the extractor runs","Verify the page region/variant matches what the extractor expects (mainland CN page vs other locale variants)"],"exampleFix":"// before (utils.js): extract immediately after scroll\nconst raw = await page.evaluate(buildFerryExtractJs());\n// after: settle, then extract (also update row anchors in DevTools)\nawait page.evaluate(buildScrollUntilJs('.list-item-parent', limit));\nawait sleep(500);\nconst raw = await page.evaluate(buildFerryExtractJs());","handlingStrategy":"validation","validationCode":"// Validate that rendered rows expose the anchors the parser needs, before extracting\nconst anchorsOk = await page.evaluate(() =>\n  document.querySelectorAll('.list-item-parent a').length > 0\n);\nif (!anchorsOk) console.error('Ferry row markup changed; update buildFerryExtractJs selectors');","typeGuard":"function isParserMissError(e) {\n  return e instanceof Error && e.name === 'CommandExecutionError' && /sailing anchors/.test(e.message);\n}","tryCatchPattern":"try {\n  return await run(['ctrip', 'ferry', '--from', f, '--to', t, '--date', d]);\n} catch (e) {\n  if (isParserMissError(e)) {\n    console.error('Rows rendered but selectors missed — inspect .list-item-parent in DevTools and update buildFerryExtractJs');\n    throw e;\n  }\n  throw e;\n}","preventionTips":["Add a weekly smoke test of the ferry command to catch selector drift early","Allow a settle delay after scrolling so row anchors finish hydrating before extraction","Keep opencli updated so extractor selectors track ship.ctrip.com redesigns","Distinguish this error from EmptyResultError in scripts — this one is a parser bug, not missing data"],"tags":["parser","selector-drift","markup-change","ferry","ctrip"],"backgroundTag":"selector-drift-site-markup-changed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}