jackwener/OpenCLI · error · CommandExecutionError
Ctrip ferry page did not render sailing rows (state=${String
Error message
Ctrip ferry page did not render sailing rows (state=${String(waitResult)}) What it means
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.
Source
Thrown at clis/ctrip/ferry.js:61
'url',
],
func: async (page, kwargs) => {
const fromCity = parsePlaceName('from', kwargs.from);
const toCity = parsePlaceName('to', kwargs.to);
if (fromCity === toCity) {
throw new ArgumentError(`--from and --to must differ (got ${fromCity})`);
}
const date = parseIsoDate('date', kwargs.date);
const limit = parseListLimit(kwargs.limit);
const searchUrl = buildFerryListUrl(fromCity, toCity, date);
await page.goto(searchUrl);
const waitResult = await page.evaluate(WAIT_FOR_FERRY_JS);
if (waitResult === 'captcha') {
throw new AuthRequiredError('ship.ctrip.com', 'Ctrip is asking for a captcha; complete it in your browser session and retry');
}
if (waitResult !== 'content') {
throw new CommandExecutionError(`Ctrip ferry page did not render sailing rows (state=${String(waitResult)})`);
}
const renderedCardCount = await page.evaluate(buildScrollUntilJs('.list-item-parent', limit));
const raw = await page.evaluate(buildFerryExtractJs());
if (!Array.isArray(raw)) {
throw new CommandExecutionError('Ctrip ferry DOM extraction returned malformed rows');
}
if (raw.length === 0) {
if (Number(renderedCardCount) > 0) {
throw new CommandExecutionError('Ctrip ferry rows rendered but parser did not find required sailing anchors');
}
throw new EmptyResultError('ctrip ferry', `No sailings for ${fromCity} to ${toCity} on ${date}`);
}
return raw.slice(0, limit).map((r, i) => ({
rank: i + 1,
shipName: r.shipName,
departureTime: r.departureTime,
fromPort: r.fromPort,
arrivalTime: r.arrivalTime,View on GitHub (pinned to 49907e53dc)
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
Example fix
// before: single attempt
await run(['ctrip', 'ferry', '--from', '大连', '--to', '烟台', '--date', d]);
// after: retry render failure once with backoff
try {
await run(['ctrip', 'ferry', '--from', '大连', '--to', '烟台', '--date', d]);
} catch (e) {
if (e.name === 'CommandExecutionError' && /did not render/.test(e.message)) {
await sleep(3000);
await run(['ctrip', 'ferry', '--from', '大连', '--to', '烟台', '--date', d]);
} else throw e;
} Defensive patterns
Strategy: retry
Validate before calling
// Pre-validate the route deep link resolves before running the full command
const url = buildFerryListUrl(from, to, date);
const res = await fetch(url, { method: 'HEAD' });
if (!res.ok) console.warn(`Ferry route link not healthy (${res.status}): ${url}`); Type guard
function isRenderFailure(e) {
return e instanceof Error && e.name === 'CommandExecutionError' && /did not render sailing rows/.test(e.message);
} Try / catch
try {
return await run(['ctrip', 'ferry', '--from', f, '--to', t, '--date', d]);
} catch (e) {
if (isRenderFailure(e)) {
await sleep(3000);
return run(['ctrip', 'ferry', '--from', f, '--to', t, '--date', d]); // one retry
}
throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Ctrip cruise port page did not render (state=${String(portWa
- Ctrip place page did not render attraction links for city id
- Ctrip bus page did not render schedule rows (state=${String(
- Ctrip cruise page did not render (state=${String(indexWait)}
- Ctrip ferry DOM extraction returned malformed rows
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/f5597ad2a408380f.
Report an issue: GitHub.