{"record":{"id":"ed7c21e3224fd14d","repo":"jackwener/OpenCLI","slug":"trip-com-things-to-do-page-did-not-render-product","errorCode":null,"errorMessage":"Trip.com things-to-do page did not render product cards (state=${String(waitResult)})","messagePattern":"Trip\\.com things-to-do page did not render product cards \\(state=(.+?)\\)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/trip/attraction.js","lineNumber":54,"sourceCode":"        'rating', 'reviews', 'booked',\n        'price', 'currency',\n        'url',\n    ],\n    func: async (page, kwargs) => {\n        const query = parseKeyword('query', kwargs.query);\n        const limit = parseListLimit(kwargs.limit);\n\n        const searchUrl = buildAttractionSearchUrl(query);\n        await page.goto(searchUrl);\n        const waitResult = await page.evaluate(WAIT_FOR_ATTRACTIONS_JS);\n        if (waitResult === 'captcha') {\n            throw new AuthRequiredError('trip.com', 'Trip.com is asking for a verification; complete it in your browser session and retry');\n        }\n        if (waitResult === 'empty') {\n            throw new EmptyResultError('trip attraction', `No attractions for \"${query}\"`);\n        }\n        if (waitResult !== 'content') {\n            throw new CommandExecutionError(`Trip.com things-to-do page did not render product cards (state=${String(waitResult)})`);\n        }\n        const raw = await page.evaluate(buildAttractionExtractJs());\n        if (!Array.isArray(raw)) {\n            throw new CommandExecutionError('Trip.com attraction DOM extraction returned malformed rows');\n        }\n        if (raw.length === 0) {\n            throw new CommandExecutionError('Trip.com attraction cards rendered but parser did not find required detail-link anchors');\n        }\n        return raw.slice(0, limit).map((r, i) => ({\n            rank: i + 1,\n            name: r.name,\n            rating: r.rating,\n            reviews: r.reviews,\n            booked: r.booked,\n            price: r.price,\n            currency: 'USD',\n            url: r.url,\n        }));","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/trip/attraction.js#L36-L72","documentation":"The `trip attraction` command throws this CommandExecutionError when the in-page wait script neither confirmed content ('content'), emptiness ('empty'), nor a challenge ('captcha') — the page ended in some other state, so product cards never rendered. The state value is embedded in the message for debugging. It usually means Trip.com's SPA never finished hydrating or changed its loading markup.","triggerScenarios":"page.evaluate(WAIT_FOR_ATTRACTIONS_JS) returns any value other than 'content'/'empty'/'captcha' — e.g. a timeout sentinel or null — after page.goto on the attraction search URL, such as slow client-side rendering exceeding the wait script's internal deadline.","commonSituations":"Slow network / throttled connection where hydration outlasts the wait window; Trip.com A/B tests changing the things-to-do page structure so the wait selector never matches; transient server errors returning a stub page; heavy CPU contention on the machine running the headless browser.","solutions":["Read the `state=` value in the message: if it indicates a timeout, simply retry — slow renders are often transient.","Increase browser navigation/wait timeouts in the shared page setup and rerun.","Check Trip.com in a normal browser to see whether the things-to-do page layout changed; if so the library's wait script needs updating.","Improve network conditions or run closer to Trip.com's region to speed up client-side hydration."],"exampleFix":"// before\nawait runCli(['trip', 'attraction', query]);\n// after: retry once on render-state failure\ntry {\n  await runCli(['trip', 'attraction', query]);\n} catch (e) {\n  if (e.name === 'CommandExecutionError' && /did not render product cards/.test(e.message)) {\n    await sleep(3000);\n    return runCli(['trip', 'attraction', query]);\n  }\n  throw e;\n}","handlingStrategy":"retry","validationCode":"null","typeGuard":"null","tryCatchPattern":"async function withRetry(fn, attempts = 2) {\n  for (let i = 0; i < attempts; i++) {\n    try {\n      return await fn();\n    } catch (e) {\n      const transient = /did not render product cards \\(state=/.test(e.message);\n      if (transient && i < attempts - 1) { await sleep(3000); continue; }\n      throw e;\n    }\n  }\n}","preventionTips":["Run on a stable, fast network so SPA hydration finishes within the wait window.","Keep the library updated — Trip.com layout changes can invalidate wait selectors.","Retry with backoff on render-state failures rather than failing immediately.","Log the `state=` value from the message to distinguish timeouts from layout changes."],"tags":["render-timeout","spa-hydration","scraping","trip-com"],"backgroundTag":"page-render-timeout","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}