{"record":{"id":"f5597ad2a408380f","repo":"jackwener/OpenCLI","slug":"ctrip-ferry-page-did-not-render-sailing-rows-stat","errorCode":null,"errorMessage":"Ctrip ferry page did not render sailing rows (state=${String(waitResult)})","messagePattern":"Ctrip ferry page did not render sailing rows \\(state=(.+?)\\)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/ctrip/ferry.js","lineNumber":61,"sourceCode":"        'url',\n    ],\n    func: async (page, kwargs) => {\n        const fromCity = parsePlaceName('from', kwargs.from);\n        const toCity = parsePlaceName('to', kwargs.to);\n        if (fromCity === toCity) {\n            throw new ArgumentError(`--from and --to must differ (got ${fromCity})`);\n        }\n        const date = parseIsoDate('date', kwargs.date);\n        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,","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/ctrip/ferry.js#L43-L79","documentation":"CommandExecutionError thrown when the ship.ctrip.com ferry results page did not reach the 'content' state after navigation — the WAIT_FOR_FERRY_JS probe returned something other than 'captcha' or 'content' (typically timeout waiting for `.list-item-parent` sailing rows). It means the SPA failed to render sailing rows in time or at all, not an auth or empty-data situation. The message embeds the raw wait state for diagnosis.","triggerScenarios":"Calling `ctrip ferry <from> <to> --date <d>` where the getShipLineV2 XHR is slow/failed, the ?param=<json> deep link was rejected/redirected, or the wait timeout expires before `.list-item-parent` rows mount.","commonSituations":"Slow network or proxies delaying the sailings XHR past the timeout; invalid or unsupported route/date producing an error page instead of the results SPA; Ctrip front-end changes renaming the row container class; headless environment blocking the XHR.","solutions":["Rerun once — transient slowness often resolves; consider increasing the CLI's browser wait timeout","Verify the route and date are valid by opening buildFerryListUrl(from,to,date) in a real browser","If the page renders in a browser but not via the CLI, update the row selector (.list-item-parent) in WAIT_FOR_FERRY_JS in clis/ctrip/utils.js to match current markup","Check for a newer opencli version that tracks Ctrip markup changes"],"exampleFix":"// before: single attempt\nawait run(['ctrip', 'ferry', '--from', '大连', '--to', '烟台', '--date', d]);\n// after: retry render failure once with backoff\ntry {\n  await run(['ctrip', 'ferry', '--from', '大连', '--to', '烟台', '--date', d]);\n} catch (e) {\n  if (e.name === 'CommandExecutionError' && /did not render/.test(e.message)) {\n    await sleep(3000);\n    await run(['ctrip', 'ferry', '--from', '大连', '--to', '烟台', '--date', d]);\n  } else throw e;\n}","handlingStrategy":"retry","validationCode":"// Pre-validate the route deep link resolves before running the full command\nconst url = buildFerryListUrl(from, to, date);\nconst res = await fetch(url, { method: 'HEAD' });\nif (!res.ok) console.warn(`Ferry route link not healthy (${res.status}): ${url}`);","typeGuard":"function isRenderFailure(e) {\n  return e instanceof Error && e.name === 'CommandExecutionError' && /did not render sailing rows/.test(e.message);\n}","tryCatchPattern":"try {\n  return await run(['ctrip', 'ferry', '--from', f, '--to', t, '--date', d]);\n} catch (e) {\n  if (isRenderFailure(e)) {\n    await sleep(3000);\n    return run(['ctrip', 'ferry', '--from', f, '--to', t, '--date', d]); // one retry\n  }\n  throw e;\n}","preventionTips":["Increase the browser wait timeout on slow networks so getShipLineV2 XHRs can land","Confirm routes/dates are valid in a real browser before batch-running them","Keep WAIT_FOR_FERRY_JS row selectors (.list-item-parent) current with Ctrip markup","Watch the state= value in the message: recurring unknown states indicate markup changes"],"tags":["render-failure","timeout","spa","ferry","ctrip"],"backgroundTag":"page-render-timeout","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}