我如何击败 ProseMirror:以编程方式向富文本编辑器插入文本的唯一方法
Source: Dev.to
(请提供您希望翻译的具体文本内容,我将按照要求保留源链接并翻译其余部分。)
引言
如果你曾尝试在现代 Web 应用上自动填写表单,你可能会遇到这样的问题:富文本编辑器忽略你的输入。我花了好几个小时尝试让 Playwright 填写 Gumroad 上的 ProseMirror 编辑器。以下是我的经验教训。
为什么 innerHTML 和 dispatchEvent 无效
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 |
dispatchEvent | ProseMirror 检查事件是否来自受信任的来源 |
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 技巧处理表单填写部分。两者结合,基本覆盖了浏览器自动化中“大部份最后一公里”问题。
你是否也与其他富文本编辑器搏斗过?在评论中分享你的战斗经历吧。