{"record":{"id":"249565c38b3a0d70","repo":"jackwener/OpenCLI","slug":"caption-editor-missing","errorCode":null,"errorMessage":"填写抖音草稿表单失败: caption-editor-missing","messagePattern":"填写抖音草稿表单失败: caption-editor-missing","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/douyin/draft.js","lineNumber":107,"sourceCode":"    }\n    return true;\n  }`));\n    if (!titleOk) {\n        throw new CommandExecutionError('填写抖音草稿表单失败: title-input-missing');\n    }\n    if (options.caption) {\n        const captionOk = (await page.evaluate(`() => {\n      const editor = document.querySelector('[contenteditable=\"true\"]');\n      if (!(editor instanceof HTMLElement)) return false;\n      editor.focus();\n      editor.textContent = '';\n      document.execCommand('selectAll', false);\n      document.execCommand('insertText', false, ${JSON.stringify(options.caption)});\n      editor.dispatchEvent(new Event('input', { bubbles: true }));\n      return true;\n    }`));\n        if (!captionOk) {\n            throw new CommandExecutionError('填写抖音草稿表单失败: caption-editor-missing');\n        }\n    }\n    const visibilityOk = (await page.evaluate(`() => {\n    const visibility = Array.from(document.querySelectorAll('label')).find(\n      (el) => (el.textContent || '').includes(${JSON.stringify(options.visibilityLabel)})\n    );\n    if (!(visibility instanceof HTMLElement)) return false;\n    visibility.click();\n    return true;\n  }`));\n    if (!visibilityOk) {\n        throw new CommandExecutionError('填写抖音草稿表单失败: visibility-missing');\n    }\n}\n/**\n * Switch the composer into custom-cover mode and expose the cover input with a\n * stable selector for CDP file injection.\n */","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/douyin/draft.js#L89-L125","documentation":"When a caption is provided, fillDraftComposer locates a '[contenteditable=\"true\"]' element to insert the caption text via document.execCommand('insertText'). If no contenteditable editor exists on the composer page it throws this CommandExecutionError with reason 'caption-editor-missing'.","triggerScenarios":"Passing a non-empty --caption while the composer page has no visible contenteditable editor — Douyin redesigned the caption box, the editor is inside an iframe, or the editor only mounts after the title is filled and a small delay passed.","commonSituations":"Douyin switched from contenteditable to a different editor component; caption field lazy-rendered; a modal overlay intercepting focus so the editor node is not present at query time.","solutions":["Retry the command — the editor may mount slightly later than the title fill","Check the composer DOM for the caption editor's new selector and update '[contenteditable=\"true\"]' in clis/douyin/draft.js:97","Omit --caption temporarily if the caption is optional for your workflow, then add it manually in creator center"],"exampleFix":"// before\nconst editor = document.querySelector('[contenteditable=\"true\"]');\n// after: wait/poll for lazy-mounted editor\nconst editor = document.querySelector('[contenteditable=\"true\"]')\n  || document.querySelector('.editor-content[contenteditable]');","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await opencli.douyin.draft({ video, title, caption });\n} catch (e) {\n  if (e.message.includes('caption-editor-missing')) {\n    // retry, or save draft without caption and add it manually\n  }\n}","preventionTips":["Only pass --caption when needed; caption is optional","Keep the caption selector broad (any contenteditable in the composer)","Test against the live composer after known Douyin frontend releases"],"tags":["douyin","dom-selector","contenteditable","browser-automation"],"backgroundTag":"selector-not-found","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}