jackwener/OpenCLI · error · CommandExecutionError

Ctrip ferry rows rendered but parser did not find required…

Error message

Ctrip ferry rows rendered but parser did not find required sailing anchors

What it means

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.

Solutions

  1. 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
  2. Upgrade opencli in case a newer release already tracks the new ship.ctrip.com markup
  3. Add a short settle delay or re-scroll before extraction so lazily-attached anchors exist when the extractor runs
  4. Verify the page region/variant matches what the extractor expects (mainland CN page vs other locale variants)

Example fix

// before (utils.js): extract immediately after scroll
const raw = await page.evaluate(buildFerryExtractJs());
// after: settle, then extract (also update row anchors in DevTools)
await page.evaluate(buildScrollUntilJs('.list-item-parent', limit));
await sleep(500);
const raw = await page.evaluate(buildFerryExtractJs());
Defensive patterns

Strategy: validation

Validate before calling

// Validate that rendered rows expose the anchors the parser needs, before extracting
const anchorsOk = await page.evaluate(() =>
  document.querySelectorAll('.list-item-parent a').length > 0
);
if (!anchorsOk) console.error('Ferry row markup changed; update buildFerryExtractJs selectors');

Type guard

function isParserMissError(e) {
  return e instanceof Error && e.name === 'CommandExecutionError' && /sailing anchors/.test(e.message);
}

Try / catch

try {
  return await run(['ctrip', 'ferry', '--from', f, '--to', t, '--date', d]);
} catch (e) {
  if (isParserMissError(e)) {
    console.error('Rows rendered but selectors missed — inspect .list-item-parent in DevTools and update buildFerryExtractJs');
    throw e;
  }
  throw e;
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/7a8903891c1dae7c. Report an issue: GitHub.

Appendix: source

Thrown at clis/ctrip/ferry.js:70

        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,
            toPort: r.toPort,
            duration: r.duration,
            price: r.price,
            status: r.status,
            url: searchUrl,
        }));
    },
});

View on GitHub (pinned to 49907e53dc)