{"record":{"id":"a557059c2af3977e","repo":"jackwener/OpenCLI","slug":"ctrip-hotel-detail-page-did-not-expose-ssr-hotel-d","errorCode":null,"errorMessage":"Ctrip hotel detail page did not expose SSR hotel data (state=${String(waitResult)})","messagePattern":"Ctrip hotel detail page did not expose SSR hotel data \\(state=(.+?)\\)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/ctrip/hotel.js","lineNumber":45,"sourceCode":"        { name: 'id', required: true, positional: true, help: 'Numeric Ctrip hotel id (use `ctrip hotel-suggest` to discover; e.g. 375539)' },\n    ],\n    columns: [\n        'hotelId', 'name', 'enName',\n        'star', 'score', 'scoreLabel', 'reviewCount', 'ratingBreakdown',\n        'facilities', 'checkInOut',\n        'cityName', 'address', 'lat', 'lon',\n        'url',\n    ],\n    func: async (page, kwargs) => {\n        const hotelId = parseHotelId(kwargs.id);\n        const url = buildHotelDetailUrl(hotelId);\n        await page.goto(url);\n        const waitResult = await page.evaluate(WAIT_FOR_HOTEL_DETAIL_JS);\n        if (waitResult === 'captcha') {\n            throw new AuthRequiredError('hotels.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 hotel detail page did not expose SSR hotel data (state=${String(waitResult)})`);\n        }\n        const detail = await page.evaluate(buildHotelDetailExtractJs());\n        if (!detail || typeof detail !== 'object') {\n            throw new CommandExecutionError('Ctrip hotel detail SSR extraction returned malformed data');\n        }\n        if (!detail.hotelId || !detail.name) {\n            throw new EmptyResultError('ctrip hotel', `No detail exposed for hotel id ${hotelId}`);\n        }\n        return [{ ...detail, url }];\n    },\n});\n","sourceCodeStart":27,"sourceCodeEnd":57,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/ctrip/hotel.js#L27-L57","documentation":"CommandExecutionError thrown when the detail page loaded but WAIT_FOR_HOTEL_DETAIL_JS returned a state other than 'content' or 'captcha', meaning the expected SSR hotel data never appeared (e.g. 'timeout', 'error', 'redirect'). The message embeds the state for diagnosis.","triggerScenarios":"WAIT_FOR_HOTEL_DETAIL_JS times out waiting for SSR data, the page redirects to a booking/login flow, the hotelId is invalid and Ctrip serves an error page, or the DOM selector for the SSR state never matches.","commonSituations":"Typo or nonexistent hotelId producing a 404-ish page; Ctrip A/B redesign removing the awaited selector; slow network causing the internal wait timeout; mobile vs desktop page variant.","solutions":["Verify the hotelId is valid by opening buildHotelDetailUrl(hotelId) in a browser and checking the state value in the message","Increase the wait timeout in WAIT_FOR_HOTEL_DETAIL_JS if on a slow connection","Update the wait/selector logic to the current Ctrip markup","Retry — transient slowness can cause the timeout state"],"exampleFix":"// before\nconst waitResult = await page.evaluate(WAIT_FOR_HOTEL_DETAIL_JS);\nif (waitResult !== 'content') throw new CommandExecutionError(`...(state=${waitResult})`);\n// after\nlet waitResult = await page.evaluate(WAIT_FOR_HOTEL_DETAIL_JS);\nif (waitResult !== 'content') {\n  await page.waitForTimeout(3000);\n  waitResult = await page.evaluate(WAIT_FOR_HOTEL_DETAIL_JS);\n}\nif (waitResult !== 'content') throw new CommandExecutionError(`...(state=${waitResult})`);","handlingStrategy":"retry","validationCode":"// validate the id format before the call\nif (!/^\\d+$/.test(String(hotelId))) throw new Error(`invalid ctrip hotel id: ${hotelId}`);","typeGuard":"const isContentState = (state) => state === 'content';","tryCatchPattern":"try {\n  return await ctrip.hotel({ id });\n} catch (e) {\n  if (e instanceof CommandExecutionError && /did not expose SSR hotel data/.test(e.message)) {\n    await sleep(2000);\n    return await ctrip.hotel({ id }); // bounded retry for timeout states\n  }\n  throw e;\n}","preventionTips":["Verify hotel ids exist by opening the detail URL in a browser","Use the state= value in the message to target the failing wait branch","Increase wait timeouts on slow networks","Update wait selectors after Ctrip front-end changes"],"tags":["scraping","ssr","timeout","dom-changed"],"backgroundTag":"ssr-extract-returned-non-array","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}