{"record":{"id":"1478eb20e7587c5a","repo":"jackwener/OpenCLI","slug":"dianping-label-failed-message","errorCode":null,"errorMessage":"dianping ${label} failed: ${message}","messagePattern":"dianping (.+?) failed: (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/dianping/utils.js","lineNumber":94,"sourceCode":"export function normalizeShopId(rawInput) {\n    const raw = String(rawInput || '').trim();\n    if (!raw) throw new ArgumentError('shop_id must be a non-empty string');\n\n    const idMatch = raw.match(/\\/shop\\/([^?#/]+)/);\n    const shopId = idMatch ? idMatch[1] : raw;\n    if (!/^[A-Za-z0-9_-]+$/.test(shopId)) {\n        throw new ArgumentError(`'${raw}' does not look like a dianping shop id`);\n    }\n    return shopId;\n}\n\nexport function wrapDianpingStep(label, fn) {\n    return Promise.resolve()\n        .then(fn)\n        .catch((err) => {\n            if (err?.code) throw err;\n            const message = err?.message || String(err);\n            throw new CommandExecutionError(`dianping ${label} failed: ${message}`);\n        });\n}\n\n/**\n * Throw the right typed error for a dianping page that didn't render data.\n * The site short-circuits HTML when bot/login checks trip — typically\n * redirects to verify.meituan.com (Yoda icon-tap captcha) or to a login\n * page when the cookie is missing.\n */\nexport function detectAuthOrPageFailure({ text = '', url = '' }, contextHint, { emptyPatterns = [] } = {}) {\n    const signal = `${url} ${text}`;\n    if (/verify\\.meituan\\.com|verifyimg|身份核实|请依次点击|美团安全验证|Yoda/i.test(signal)) {\n        throw new AuthRequiredError(\n            'dianping.com',\n            `dianping ${contextHint} blocked by captcha — open ${url || 'www.dianping.com'} manually in this profile and solve the captcha, then retry`,\n        );\n    }\n    if (/login\\.dianping\\.com|account\\.dianping\\.com|请先登录|未登录|请登录/.test(signal)) {","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/dianping/utils.js#L76-L112","documentation":"wrapDianpingStep wraps a dianping step's async function and rethrows unexpected failures as CommandExecutionError with a 'dianping <label> failed: ...' prefix. Typed errors (any err with a .code — e.g. AuthRequiredError, EmptyResultError, ArgumentError) are passed through untouched; only code-less errors (TypeError, network crashes, selector misses) get wrapped. This normalizes raw exceptions from browser automation into a labeled, grep-able CLI error.","triggerScenarios":"Any step registered via wrapDianpingStep (cityId, result, data steps) throwing or rejecting with an error that has no .code property — e.g. a TypeError inside a page.evaluate callback, a browser/page crash, a timed-out or closed Playwright/Puppeteer target, or a plain string/undefined throw.","commonSituations":"Playwright browser closed mid-navigation, page.goto timeout, a selector helper throwing undefined because a DOM node disappeared during re-render, or a bug in adapter code throwing a literal value instead of a typed error.","solutions":["Read the wrapped inner message after 'failed: ' to find the original cause (TypeError, timeout, etc.).","Check that the browser profile/page is alive and not crashing; rerun with the browser visible to see where navigation stops.","If it is a code bug throwing a non-Error value, fix the throw site to throw a typed error from @jackwener/opencli/errors so wrapDianpingStep passes it through.","Retry the step — transient navigation/anti-bot hiccups often resolve on a second run; persistent captcha will surface as a typed AuthRequiredError instead."],"exampleFix":"// before\nthrow 'selector not found'; // wrapped as 'dianping result failed: selector not found'\n// after\nimport { CommandExecutionError } from '@jackwener/opencli/errors';\nthrow new CommandExecutionError('selector not found'); // passes through with .code","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isTypedError(err) {\n  return err instanceof Error && typeof err.code === 'string';\n}","tryCatchPattern":"try {\n  await runDianpingStep();\n} catch (err) {\n  if (err.code) {\n    // typed: AuthRequiredError / EmptyResultError / ArgumentError — handle specifically\n  } else {\n    // wrapped CommandExecutionError: inspect err.message after 'failed: ' for the raw cause\n  }\n}","preventionTips":["Always throw Error instances with .code from adapter code so wrapDianpingStep passes them through instead of wrapping.","Keep the browser page alive for the whole step; guard against navigation timeouts.","Log err.stack of the wrapped error to locate the true throw site."],"tags":["error-wrapping","browser-automation","dianping"],"backgroundTag":"unhandled-browser-step-failure","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}