我如何击败 ProseMirror:以编程方式向富文本编辑器插入文本的唯一方法

发布: (2026年3月14日 GMT+8 15:23)
4 分钟阅读
原文: Dev.to

Source: Dev.to

(请提供您希望翻译的具体文本内容,我将按照要求保留源链接并翻译其余部分。)

引言

如果你曾尝试在现代 Web 应用上自动填写表单,你可能会遇到这样的问题:富文本编辑器忽略你的输入。我花了好几个小时尝试让 Playwright 填写 Gumroad 上的 ProseMirror 编辑器。以下是我的经验教训。

为什么 innerHTMLdispatchEvent 无效

editor.innerHTML = '
My description
';
editor.dispatchEvent(new Event('input', { bubbles: true }));

ProseMirror 维护自己的内部文档模型。当你修改 innerHTML 时,ProseMirror 并不知道这件事,所以下一次渲染会丢弃你的更改。即使派发 input 事件也无济于事——ProseMirror 会忽略它未发起的 DOM 变更。

Playwright 在 contenteditable 上的 fill()

await page.locator('[contenteditable]').fill('text')

fill() 旨在用于 <input><textarea> 元素;它在 contenteditable 元素上不起作用。

用键盘输入

await editor.click()
await page.keyboard.type('My text')

这实际上会在编辑器中输入文本,ProseMirror 能捕获到,但对于长文本来说(逐字符)速度非常慢,而且在输入过程中可能会失去焦点。

使用 document.execCommand('insertText')

execCommand('insertText') 是浏览器原生的指令,ProseMirror(以及 TipTap、Slate、Quill 和大多数富文本编辑器)都会监听。当浏览器处理此指令时,会触发与真实按键相同的内部事件流水线——编辑器的事务系统捕获它,更新文档模型,并正确渲染。

为什么它有效

方法发生的情况
innerHTML绕过编辑器 entirely
dispatchEventProseMirror 检查事件是否来自受信任的来源
keyboard.type()有效,但速度慢(character‑by‑character)
execCommand('insertText')触发编辑器内部事务流水线

execCommand 在技术上已被弃用,但所有浏览器仍然支持它,并且目前是向 contenteditable 编辑器以编程方式输入文本的唯一可靠方式。

经实战考验的填充 ProseMirror/TipTap 编辑器函数

async def fill_prosemirror(page, text):
    # Find the editor (skip small contenteditable elements)
    editors = page.locator('[contenteditable="true"]')
    count = await editors.count()
    editor = None
    for i in range(count):
        el = editors.nth(i)
        box = await el.bounding_box()
        if box and box['height'] > 80:  # Skip URL slugs, etc.
            editor = el
            break

    if not editor:
        return False

    # Focus
    await editor.click(force=True)
    await page.wait_for_timeout(300)

    # Clear existing content
    await page.keyboard.press('Meta+a')  # Cmd+A on Mac
    await page.keyboard.press('Backspace')
    await page.wait_for_timeout(200)

    # Insert line by line
    lines = text.split('\n')
    for i, line in enumerate(lines):
        if line.strip():
            await page.evaluate(
                '(t) => document.execCommand("insertText", false, t)',
                line
            )

如果编辑器包含工具栏,通常需要使用 execCommand

触发保存按钮

富文本编辑器经常拦截普通点击。使用直接的 JS click 绕过 Playwright 的可操作性检查:

await page.evaluate(`() => {
    const buttons = document.querySelectorAll('button');
    for (const btn of buttons) {
        if (btn.textContent.includes('Save')) {
            btn.click();
            return true;
        }
    }
    return false;
}`);

补充工具:SessionKeeper

我构建了 SessionKeeper 来处理网页自动化中的身份验证部分(CAPTCHA、登录墙)。上面的 ProseMirror 技巧处理表单填写部分。两者结合,基本覆盖了浏览器自动化中“大部份最后一公里”问题。


你是否也与其他富文本编辑器搏斗过?在评论中分享你的战斗经历吧。

0 浏览
Back to Blog

相关文章

阅读更多 »

关于 JavaScript 的简介

介绍 在今天的课堂上,我学习了 JavaScript 的简短介绍,所以我将在这篇博客中分享一些关于 JavaScript 的事实。什么是 JavaScript?JavaScr...

现代 JavaScript:理解 ES6 类

封面图片:Modern JavaScript:Understanding ES6 Classes https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%...

你的设计系统存在耦合问题

介绍 我直截了当地写作,我珍惜你的时间——少废话,多价值。挑选一个流行的组件库,找到 Button 组件。你会看到:结构……