jackwener/OpenCLI · error · CommandExecutionError

WeChat save-draft button was not found.

Error message

WeChat save-draft button was not found.

What it means

Thrown by saveDraft when the in-page script cannot find an enabled button whose text is exactly 保存为草稿 (Save as draft). Without that button the library cannot trigger the draft save, so it throws before polling for confirmation.

Source

Thrown at clis/weixin/create-draft.js:263

            return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length);
        }).map(function(el) {
            return (el.innerText || el.textContent || '').replace(/\\s+/g, ' ').trim();
        }).find(function(value) { return /已保存|保存成功/.test(value); }) || '';
        return { visible: !!text, text: text };
    })()`);
}

async function saveDraft(page) {
    const before = await readSaveState(page);
    const result = await evaluate(page, `(() => {
        var button = Array.from(document.querySelectorAll('span, button, a')).find(function(el) {
            return (el.textContent || '').trim() === '保存为草稿' && !el.disabled;
        });
        if (!button) return { ok: false };
        button.click();
        return { ok: true };
    })()`);
    if (!result?.ok) throw new CommandExecutionError('WeChat save-draft button was not found.');

    for (let attempt = 0; attempt < 8; attempt++) {
        await page.wait(1);
        const state = await readSaveState(page);
        if (state?.visible && (!before?.visible || state.text !== before.text)) return;
    }
    throw new CommandExecutionError('WeChat draft save was not confirmed by a fresh success status.');
}

export const createDraftCommand = cli({
    site: 'weixin',
    name: 'create-draft',
    access: 'write',
    description: '创建微信公众号图文草稿',
    domain: WEIXIN_DOMAIN,
    strategy: Strategy.COOKIE,
    browser: true,
    navigateBefore: false,

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Confirm the editor loaded fully (title textarea present) and the article has valid title/content so the save button is enabled.
  2. Open the live editor and inspect the save button's exact text/structure; update the matching logic if WeChat changed it (e.g. trim, includes, or query by class).
  3. Re-login and retry if the page was a session-expiry interstitial rather than the editor.
  4. Add a wait/poll for the button before the single click attempt to allow late rendering.

Example fix

// before
if (!result?.ok) throw new CommandExecutionError('WeChat save-draft button was not found.');
// after (in-page matching)
var els = Array.from(document.querySelectorAll('button, a, span'));
var button = els.find(function(el) {
    return (el.textContent || '').trim().includes('保存为草稿') && !el.disabled;
});
if (!button) return { ok: false };
button.scrollIntoView();
button.click();
Defensive patterns

Strategy: validation

Validate before calling

await page.wait(4);
const canSave = await evaluate(page, 'Array.from(document.querySelectorAll("button, a, span")).some(el => (el.textContent || "").trim() === "保存为草稿" && !el.disabled)');
if (canSave !== true) throw new Error('save-draft button unavailable — check editor loaded and article has title/content');

Type guard

function isSaveClickResult(v) { return typeof v === 'object' && v !== null && typeof v.ok === 'boolean'; }

Try / catch

try {
    await createDraftCommand(opts);
} catch (e) {
    if (/save-draft button was not found/i.test(e.message)) {
        console.error('save button unavailable — verify session and editor state, then retry');
    }
    throw e;
}

Prevention

When it happens

Trigger: The article editor page is not fully loaded or the session expired so the toolbar/footer actions are missing, WeChat renamed or relabeled the save button, the button is disabled (e.g. empty title/content), the button exists but has extra whitespace or nested elements so textContent !== '保存为草稿', or a modal blocks rendering.

Common situations: WeChat UI updates changing the button label or structure; running against an unloaded/interstitial page; the editor disabled the save button because required fields are missing; rendering differences in headless mode.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/208a7b5941434b5a. Report an issue: GitHub.