{"record":{"id":"2d99cac7923255cf","repo":"jackwener/OpenCLI","slug":"error-2d99ca","errorCode":null,"errorMessage":"等待抖音草稿编辑页超时","messagePattern":"等待抖音草稿编辑页超时","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/douyin/draft.js","lineNumber":57,"sourceCode":"        href: '',\n        ready: false,\n        bodyText: '',\n    };\n    for (let attempt = 0; attempt < COMPOSER_WAIT_ATTEMPTS; attempt += 1) {\n        lastState = (await page.evaluate(`() => ({\n      href: location.href,\n      ready: !!Array.from(document.querySelectorAll('input')).find(\n        (el) => (el.placeholder || '').includes('填写作品标题')\n      ) && !!Array.from(document.querySelectorAll('button')).find(\n        (el) => (el.textContent || '').includes('暂存离开')\n      ),\n      bodyText: document.body?.innerText || ''\n    })`));\n        if (lastState.ready)\n            return;\n        await page.wait({ time: 0.5 });\n    }\n    throw new CommandExecutionError('等待抖音草稿编辑页超时', `当前页面: ${lastState.href || 'unknown'}`);\n}\n/**\n * Fill title, caption and visibility controls on the live composer page.\n */\nasync function fillDraftComposer(page, options) {\n    const titleOk = (await page.evaluate(`() => {\n    const titleInput = Array.from(document.querySelectorAll('input')).find(\n      (el) => (el.placeholder || '').includes('填写作品标题')\n    );\n    if (!(titleInput instanceof HTMLInputElement)) return false;\n    const propKey = Object.keys(titleInput).find((key) => key.startsWith('__reactProps$'));\n    const props = propKey ? titleInput[propKey] : null;\n    if (props?.onChange) {\n      props.onChange({\n        target: { value: ${JSON.stringify(options.title)} },\n        currentTarget: { value: ${JSON.stringify(options.title)} },\n      });\n    } else {","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/douyin/draft.js#L39-L75","documentation":"waitForDraftComposer polls the Douyin creator upload page up to 120 times (0.5s apart, ~60s total) for the composer being ready — detected by an input whose placeholder contains '填写作品标题' and a button containing '暂存离开'. If the composer never appears within the timeout, it throws this CommandExecutionError with the last page URL as detail. It means the video upload did not complete or the page did not transition to the draft-editing composer.","triggerScenarios":"Calling the douyin draft CLI with a video, then the upload stalls or the page never navigates to the composer within 60 seconds: slow network upload, Douyin showing an unexpected modal/interstitial, login cookie expired redirecting to a login page, or Douyin changed composer DOM (placeholder/button text).","commonSituations":"Expired or invalid creator.douyin.com cookies; very large video on slow connection; Douyin A/B testing that renamed the title placeholder or '暂存离开' button; risk-control (验证码) interstitial after upload; upload error dialog left open.","solutions":["Check the error detail (当前页面: <href>) — if it is a login/risk-control URL, re-authenticate / refresh cookies and retry","Re-run the command; transient slow uploads may just need the retry (progress resumes from draft if partially saved)","Use a smaller/compressed video or a more stable network to finish upload within the 60s window","Manually open the upload URL to dismiss any new modals or confirm the composer still uses '填写作品标题' / '暂存离开' text; update selectors in clis/douyin/draft.js if Douyin changed them"],"exampleFix":"// before: fail hard on timeout\nthrow new CommandExecutionError('等待抖音草稿编辑页超时', `当前页面: ${lastState.href || 'unknown'}`);\n// after: surface actionable state / retry once on slow upload\nif (lastState.bodyText.includes('重新上传')) {\n  await page.setFileInput([videoPath], 'input[type=\"file\"]');\n  // restart the wait loop\n} else {\n  throw new CommandExecutionError('等待抖音草稿编辑页超时', `当前页面: ${lastState.href || 'unknown'}`);\n}","handlingStrategy":"retry","validationCode":"// pre-check auth/page reachability before invoking\ncurl -s -o /dev/null -w '%{http_code}' https://creator.douyin.com/creator-micro/content/upload \\\n  -H \"Cookie: $DOUYIN_COOKIE\" # expect 200, not a login redirect","typeGuard":null,"tryCatchPattern":"try {\n  await opencli.douyin.draft({ video, title });\n} catch (e) {\n  if (e.message.includes('等待抖音草稿编辑页超时')) {\n    // inspect e.detail for '当前页面: <href>' — re-auth if login page, else retry once\n  }\n}","preventionTips":["Keep creator.douyin.com cookies fresh before long upload runs","Compress large videos so the upload finishes within the ~60s composer wait","Watch for Douyin UI changes to '填写作品标题'/'暂存离开' and update selectors","Run during off-peak hours to avoid risk-control interstitials"],"tags":["timeout","douyin","browser-automation","upload"],"backgroundTag":"page-load-timeout","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}