化實(shí)戰(zhàn):Playwright 全自動(dòng)發(fā)稿的 9 個(gè)坑)
1. 一次讓AI替我干活的真實(shí)實(shí)驗(yàn)從想法到落地去年年底我在整理自己的技術(shù)輸出節(jié)奏時(shí)發(fā)現(xiàn)一個(gè)很尷尬的問(wèn)題我寫代碼、做項(xiàng)目、踩坑復(fù)盤的速度其實(shí)不慢但把這些東西整理成一篇能發(fā)出去的稿子再登錄社區(qū)、排版、配圖、選標(biāo)簽、點(diǎn)發(fā)布這一整套流程下來(lái)往往要吃掉我大半個(gè)下午。真正寫的時(shí)間可能只占三成剩下七成全是機(jī)械操作。于是我就冒出一個(gè)念頭能不能讓一個(gè) AI Agent 自己把這件事從頭到尾干完不是幫我寫而是幫我發(fā)——自己打開瀏覽器、自己登錄、自己填標(biāo)題正文、自己點(diǎn)發(fā)布全程我只在旁邊看著。這個(gè)實(shí)驗(yàn)最后跑通了但代價(jià)是227 次瀏覽器操作、4.3 小時(shí)的實(shí)際耗時(shí)、以及 9 個(gè)把我卡到懷疑人生的坑。這篇文章就是把這 4.3 小時(shí)里發(fā)生的所有事情攤開講清楚我用了什么工具、為什么這么選、每一步在干什么、坑是怎么踩的、又是怎么爬出來(lái)的。關(guān)鍵詞里出現(xiàn)的WorkBuddy、AI Agent、騰訊云社區(qū)、瀏覽器自動(dòng)化、Playwright這幾個(gè)詞基本就是這次實(shí)驗(yàn)的全部骨架。先說(shuō)清楚這件事適合誰(shuí)看。如果你只是想讓 AI 幫你寫一段文案那這篇對(duì)你價(jià)值不大直接用聊天窗口就夠了。但如果你想讓 AI真正去操作一個(gè)網(wǎng)頁(yè)——登錄、填表、點(diǎn)擊、翻頁(yè)、上傳、提交并且要穩(wěn)定、可復(fù)現(xiàn)、能處理各種意外彈窗和驗(yàn)證那這篇就是給你準(zhǔn)備的。它涉及的是AI Agent 與瀏覽器自動(dòng)化框架的結(jié)合是讓模型做決策、讓自動(dòng)化框架做執(zhí)行的典型架構(gòu)。哪怕你用的不是 WorkBuddy而是別的 Agent 平臺(tái)底層的思路也是通用的。我先把結(jié)論擺前面免得你看到一半才發(fā)現(xiàn)方向不對(duì)讓 AI 全自動(dòng)發(fā)一篇稿技術(shù)上完全可行但全自動(dòng)這三個(gè)字是有水分的。真正跑起來(lái)你會(huì)發(fā)現(xiàn)AI 負(fù)責(zé)的是判斷下一步該點(diǎn)哪里、該填什么而穩(wěn)定性、重試、異常兜底這些臟活累活還是得靠 Playwright 這類自動(dòng)化框架來(lái)扛。兩者分工不清就會(huì)像我一樣在某個(gè)彈窗上卡四十分鐘。下面我按實(shí)際推進(jìn)的順序把整件事拆開講。2. 為什么是 Playwright 而不是別的自動(dòng)化方案2.1 選型背后的三個(gè)硬性約束在動(dòng)手之前我對(duì)比過(guò)幾種主流方案純 Selenium、Puppeteer、Playwright以及一些基于瀏覽器插件的錄制回放工具。最后選 Playwright不是因?yàn)樗露且驗(yàn)樗鼊偤脻M足我這次實(shí)驗(yàn)的三個(gè)硬性約束。第一個(gè)約束是現(xiàn)代前端頁(yè)面的兼容性。騰訊云社區(qū)這類站點(diǎn)是典型的前端渲染應(yīng)用頁(yè)面元素大量依賴異步加載DOM 結(jié)構(gòu)在首屏之后還會(huì)持續(xù)變化。Selenium 那套基于 WebDriver 的老路子在等待策略上要寫大量顯式等待稍不留神就抓到還沒(méi)渲染完的空節(jié)點(diǎn)。Playwright 自帶的**自動(dòng)等待auto-waiting**機(jī)制會(huì)在點(diǎn)擊、填值前自動(dòng)檢查元素是否可見、是否可交互、是否穩(wěn)定這一條就省掉了我至少一半的等待代碼。第二個(gè)約束是多瀏覽器與多上下文隔離。實(shí)驗(yàn)里我需要同時(shí)保持已登錄的會(huì)話和干凈的測(cè)試會(huì)話還要能在 Chromium、Firefox、WebKit 之間切換驗(yàn)證兼容性。Playwright 的BrowserContext概念天然支持這種隔離——每個(gè) context 相當(dāng)于一個(gè)獨(dú)立的隱身窗口cookie、localStorage 互不干擾。這一點(diǎn)在我排查為什么登錄態(tài)丟了那個(gè)坑的時(shí)候起了決定性作用。第三個(gè)約束是和 AI Agent 的對(duì)接友好度。Playwright 提供了清晰的 API 邊界Agent 只需要輸出選擇器 動(dòng)作這樣的結(jié)構(gòu)化指令框架負(fù)責(zé)執(zhí)行。相比之下錄制回放工具生成的腳本可讀性差A(yù)gent 很難動(dòng)態(tài)修改而純 Selenium 的 API 又太啰嗦Agent 生成的指令容易出錯(cuò)。2.2 Playwright 在這個(gè)項(xiàng)目里到底扮演什么角色很多人會(huì)混淆一件事Playwright 不是 AI它只是手。它不會(huì)自己判斷現(xiàn)在該點(diǎn)登錄還是該點(diǎn)注冊(cè)它只會(huì)忠實(shí)地執(zhí)行你給它的指令。真正做判斷的是上層的 AI Agent——也就是 WorkBuddy 這類平臺(tái)里的決策模塊。所以整個(gè)架構(gòu)其實(shí)是兩層層級(jí)角色負(fù)責(zé)的事典型工具決策層AI Agent看當(dāng)前頁(yè)面狀態(tài)決定下一步動(dòng)作WorkBuddy / 大模型執(zhí)行層自動(dòng)化框架把動(dòng)作翻譯成真實(shí)瀏覽器操作Playwright感知層頁(yè)面快照把 DOM 轉(zhuǎn)成 Agent 能讀的文本accessibility tree / DOM 摘要我踩的第一個(gè)認(rèn)知坑就在這里一開始我天真地以為AI 自動(dòng)化就是讓模型直接操控瀏覽器結(jié)果發(fā)現(xiàn)模型根本看不見頁(yè)面。它看到的是一段被壓縮過(guò)的頁(yè)面文本描述然后基于這段描述猜下一步。頁(yè)面描述的質(zhì)量直接決定了 Agent 的判斷準(zhǔn)確率。這也是為什么后面我會(huì)花大量時(shí)間在怎么把頁(yè)面喂給 Agent這件事上。2.3 環(huán)境準(zhǔn)備里最容易被忽略的兩個(gè)細(xì)節(jié)環(huán)境搭建本身不復(fù)雜npm init playwright一把梭但有兩個(gè)細(xì)節(jié)我建議你一開始就處理好否則后面會(huì)反復(fù)返工。第一個(gè)是瀏覽器版本鎖定。Playwright 默認(rèn)會(huì)下載它自己配套的瀏覽器內(nèi)核但如果你機(jī)器上已經(jīng)裝了系統(tǒng)級(jí) Chrome很容易出現(xiàn)代碼里用的是 Playwright 內(nèi)核手動(dòng)調(diào)試時(shí)用的是系統(tǒng) Chrome兩邊行為不一致的情況。我的做法是統(tǒng)一用 Playwright 自帶的內(nèi)核并且在playwright.config.ts里顯式指定channel避免歧義。第二個(gè)是持久化用戶目錄。登錄態(tài)這種東西如果每次跑腳本都重新登錄不僅慢還容易觸發(fā)風(fēng)控。Playwright 支持launchPersistentContext把用戶數(shù)據(jù)目錄固定下來(lái)登錄一次之后 cookie 就留在本地。這個(gè)目錄的位置要提前規(guī)劃好別放在臨時(shí)目錄里否則系統(tǒng)一清理就前功盡棄。// 持久化上下文的關(guān)鍵配置 const context await chromium.launchPersistentContext(./user-data, { headless: false, // 調(diào)試階段一定要開有頭模式 viewport: { width: 1440, height: 900 }, args: [--disable-blink-featuresAutomationControlled] });提示headless: false在調(diào)試階段是剛需。Agent 判斷錯(cuò)了你能立刻看到它點(diǎn)到了哪里如果開無(wú)頭模式你只能對(duì)著日志猜效率差十倍。3. 227 次操作是怎么堆出來(lái)的任務(wù)拆解與執(zhí)行鏈路3.1 把發(fā)一篇稿拆成 Agent 能執(zhí)行的原子動(dòng)作發(fā)一篇稿聽起來(lái)是一件事但對(duì) Agent 來(lái)說(shuō)它必須被拆成一串原子動(dòng)作。我最終的拆解是這樣的打開目標(biāo)站點(diǎn)首頁(yè)判斷當(dāng)前是否已登錄檢查頭像元素是否存在未登錄則點(diǎn)擊登錄入口等待登錄完成進(jìn)入創(chuàng)作/發(fā)文頁(yè)面填寫標(biāo)題填寫正文富文本編輯器需要特殊處理選擇分類與標(biāo)簽上傳封面圖預(yù)覽并檢查點(diǎn)擊發(fā)布確認(rèn)發(fā)布結(jié)果看起來(lái)只有 11 步但實(shí)際執(zhí)行了 227 次瀏覽器操作。差距在哪在于每一步內(nèi)部都有大量的感知-判斷-執(zhí)行循環(huán)。比如第 6 步填寫正文富文本編輯器不是簡(jiǎn)單的inputAgent 需要先點(diǎn)擊編輯區(qū)、等待工具欄加載、再逐段輸入每輸入一段還要確認(rèn)內(nèi)容真的進(jìn)去了。光是正文這一項(xiàng)就消耗了 60 多次操作。3.2 感知層Agent 到底看到了什么這是整個(gè)實(shí)驗(yàn)里最值得講的部分。Agent 不是用眼睛看頁(yè)面它讀的是一份頁(yè)面結(jié)構(gòu)摘要。我最初用的是最樸素的方案——把整個(gè) DOM 的innerText抓出來(lái)喂給模型。結(jié)果非常糟糕模型經(jīng)常把導(dǎo)航欄的文字當(dāng)成正文把頁(yè)腳鏈接當(dāng)成按鈕。后來(lái)我改成了基于accessibility tree無(wú)障礙樹的摘要方案。無(wú)障礙樹的好處是它天然過(guò)濾掉了大量裝飾性元素只保留可交互、有語(yǔ)義的節(jié)點(diǎn)并且每個(gè)節(jié)點(diǎn)自帶 role按鈕、輸入框、鏈接和 name。Agent 拿到這份摘要后判斷準(zhǔn)確率肉眼可見地提升了。// 抓取頁(yè)面可交互元素的語(yǔ)義摘要 const snapshot await page.accessibility.snapshot(); // 遞歸提取 role name壓縮成 Agent 可讀的列表但這里有個(gè)坑無(wú)障礙樹對(duì)富文本編輯器的描述非常模糊。編輯器區(qū)域在無(wú)障礙樹里往往只是一個(gè)role: textbox的大節(jié)點(diǎn)Agent 根本不知道光標(biāo)在哪、當(dāng)前段落是什么格式。這就是我后面要專門講的第 4 個(gè)坑。3.3 執(zhí)行層為什么每次操作都要重新定位Playwright 有個(gè)特性叫l(wèi)ocator 的惰性求值。你寫page.locator(.submit-btn)的時(shí)候它并不會(huì)立刻去查 DOM而是在真正執(zhí)行點(diǎn)擊時(shí)才去定位。這個(gè)設(shè)計(jì)本來(lái)是為了應(yīng)對(duì)動(dòng)態(tài)頁(yè)面但在 Agent 場(chǎng)景下會(huì)帶來(lái)一個(gè)副作用如果 Agent 緩存的元素引用過(guò)期了操作就會(huì)失敗。我的應(yīng)對(duì)策略是每次操作前都重新定位絕不緩存元素句柄。Agent 輸出的指令格式統(tǒng)一成選擇器 動(dòng)作 參數(shù)執(zhí)行層每次拿到指令都重新locator一次。這樣雖然多了一點(diǎn)開銷但穩(wěn)定性提升非常明顯。227 次操作里因?yàn)樵剡^(guò)期導(dǎo)致的失敗從最初的十幾次降到了 0。3.4 4.3 小時(shí)的時(shí)間都花在哪了我把這 4.3 小時(shí)做了個(gè)粗略的分布統(tǒng)計(jì)結(jié)果挺反直覺(jué)的階段耗時(shí)占比說(shuō)明環(huán)境與登錄調(diào)試約 25%登錄態(tài)反復(fù)丟失排查了很久頁(yè)面感知方案迭代約 30%從 innerText 換到無(wú)障礙樹富文本編輯器處理約 20%最大的單點(diǎn)難題異常彈窗與重試約 15%各種意料之外的遮擋真正發(fā)文本身約 10%反而最快看到?jīng)]真正干活的時(shí)間只占一成九成都在處理怎么讓 Agent 穩(wěn)定地干活。這個(gè)比例我覺(jué)得很有代表性——任何想做瀏覽器自動(dòng)化的朋友都要有這個(gè)心理預(yù)期。4. 九個(gè)坑的完整排查鏈路4.1 坑一登錄態(tài)在第二次運(yùn)行時(shí)憑空消失現(xiàn)象第一次跑登錄成功發(fā)文順利。第二次跑腳本一啟動(dòng)就發(fā)現(xiàn)是未登錄狀態(tài)又得重新登錄。排查過(guò)程我先懷疑是 cookie 沒(méi)保存檢查了user-data目錄發(fā)現(xiàn)文件確實(shí)在。然后我打印了 context 的 cookie發(fā)現(xiàn)登錄相關(guān)的 cookie 是session類型沒(méi)有expires字段——也就是說(shuō)它是會(huì)話級(jí) cookie瀏覽器一關(guān)就沒(méi)了。而我第一次跑完腳本后進(jìn)程正常退出會(huì)話 cookie 自然被清空。根因會(huì)話級(jí) cookie 不落盤。修復(fù)改用launchPersistentContext并確保進(jìn)程不是被強(qiáng)殺而是走正常的context.close()。同時(shí)把關(guān)鍵 cookie 手動(dòng)持久化一份下次啟動(dòng)時(shí)注入。// 保存并復(fù)用登錄態(tài) const cookies await context.cookies(); fs.writeFileSync(auth.json, JSON.stringify(cookies)); // 下次啟動(dòng) await context.addCookies(JSON.parse(fs.readFileSync(auth.json, utf-8)));4.2 坑二Agent 把登錄和注冊(cè)點(diǎn)反了現(xiàn)象Agent 在首頁(yè)看到兩個(gè)相鄰的按鈕一個(gè)登錄一個(gè)注冊(cè)它點(diǎn)了注冊(cè)。排查過(guò)程我回看 Agent 的決策日志發(fā)現(xiàn)它拿到的頁(yè)面摘要里兩個(gè)按鈕的文本分別是登錄和注冊(cè)但摘要里沒(méi)有包含它們的位置信息。模型在純文本層面判斷偶爾會(huì)選錯(cuò)。根因頁(yè)面摘要丟失了空間位置信息模型只能靠文本語(yǔ)義猜。修復(fù)在摘要里給每個(gè)可交互元素加上序號(hào)和相對(duì)位置比如頂部導(dǎo)航第 3 個(gè)按鈕并且在 Agent 的提示詞里明確要求優(yōu)先選擇語(yǔ)義完全匹配的元素不確定時(shí)先輸出候選列表。4.3 坑三富文本編輯器填不進(jìn)去現(xiàn)象標(biāo)題能填正文死活填不進(jìn)去。Agent 報(bào)告已輸入但頁(yè)面上一個(gè)字都沒(méi)有。排查過(guò)程這是耗時(shí)最長(zhǎng)的一個(gè)坑。我先試了fill()無(wú)效試了type()無(wú)效試了pressSequentially()還是無(wú)效。后來(lái)我用 Playwright 的 trace 功能錄了一次操作發(fā)現(xiàn) Agent 點(diǎn)擊編輯區(qū)后焦點(diǎn)確實(shí)進(jìn)去了但輸入的內(nèi)容被編輯器的某個(gè)事件監(jiān)聽器攔截了。根因這個(gè)富文本編輯器監(jiān)聽的是beforeinput和input事件而 Playwright 的fill()是直接設(shè)置value不觸發(fā)這些事件。修復(fù)改用逐字符模擬真實(shí)鍵盤輸入并且在輸入前先click()編輯區(qū)確保焦點(diǎn)輸入后再用page.keyboard.press(Space)觸發(fā)一次事件。await editor.click(); await page.keyboard.type(content, { delay: 30 }); // 關(guān)鍵觸發(fā)編輯器的 input 事件 await page.keyboard.press(End);注意delay參數(shù)別設(shè)太小。我一開始設(shè) 0編輯器直接丟字符設(shè) 30ms 之后穩(wěn)定了。這個(gè)值因編輯器而異需要實(shí)測(cè)。4.4 坑四無(wú)障礙樹讀不到編輯器內(nèi)容現(xiàn)象Agent 想確認(rèn)正文是否填對(duì)了但無(wú)障礙樹里編輯器區(qū)域是空的。根因富文本編輯器用的是contenteditable無(wú)障礙樹對(duì)它的支持不完整。修復(fù)對(duì)編輯器區(qū)域單獨(dú)走一條感知通道——直接讀innerText而不是走無(wú)障礙樹。也就是說(shuō)感知層要分區(qū)域用不同策略不能一刀切。4.5 坑五上傳封面圖時(shí)文件選擇框卡死現(xiàn)象點(diǎn)擊上傳封面后彈出了系統(tǒng)文件選擇框腳本卡住不動(dòng)。根因系統(tǒng)級(jí)文件對(duì)話框不屬于瀏覽器 DOMPlaywright 無(wú)法直接操作。修復(fù)用page.on(filechooser)事件攔截直接setFiles()繞開系統(tǒng)對(duì)話框。page.on(filechooser, async (chooser) { await chooser.setFiles(./cover.png); }); await page.click(.upload-cover-btn);4.6 坑六標(biāo)簽選擇器的下拉框點(diǎn)不開現(xiàn)象Agent 點(diǎn)擊標(biāo)簽輸入框下拉列表不出現(xiàn)。排查過(guò)程trace 顯示點(diǎn)擊事件確實(shí)觸發(fā)了但下拉列表的 DOM 節(jié)點(diǎn)始終是display: none。后來(lái)發(fā)現(xiàn)這個(gè)下拉框是鼠標(biāo)懸停觸發(fā)的不是點(diǎn)擊觸發(fā)。修復(fù)改用hover()而不是click()。這個(gè)坑的教訓(xùn)是不要假設(shè)所有交互都是點(diǎn)擊懸停、長(zhǎng)按、右鍵都可能。4.7 坑七發(fā)布按鈕被浮層遮擋現(xiàn)象Agent 找到了發(fā)布按鈕點(diǎn)擊卻報(bào)錯(cuò)元素被遮擋。根因頁(yè)面右下角有個(gè)幫助浮層正好蓋住了發(fā)布按鈕。修復(fù)點(diǎn)擊前先檢測(cè)是否有遮擋元素有的話先關(guān)閉浮層。Playwright 的click({ trial: true })可以預(yù)演點(diǎn)擊不實(shí)際執(zhí)行用來(lái)檢測(cè)可點(diǎn)擊性非常方便。try { await page.locator(.publish-btn).click({ trial: true }); } catch { await page.locator(.help-float .close).click(); } await page.locator(.publish-btn).click();4.8 坑八Agent 陷入重試死循環(huán)現(xiàn)象某個(gè)操作失敗后Agent 不斷重試同一個(gè)動(dòng)作連續(xù)重試了 40 多次把時(shí)間全耗光了。根因Agent 的決策邏輯里沒(méi)有失敗次數(shù)上限和換策略機(jī)制。修復(fù)在 Agent 的提示詞里加入硬性規(guī)則——同一動(dòng)作連續(xù)失敗 3 次必須換一種策略或上報(bào)人工。同時(shí)在執(zhí)行層加一個(gè)計(jì)數(shù)器超過(guò)閾值直接中斷。4.9 坑九發(fā)布成功后 Agent 不知道成功了現(xiàn)象稿子其實(shí)發(fā)出去了但 Agent 還在頁(yè)面上找發(fā)布成功的提示找不到就判定失敗又點(diǎn)了一次發(fā)布。根因成功提示是個(gè) toast幾秒后就消失了Agent 感知的時(shí)候已經(jīng)錯(cuò)過(guò)。修復(fù)不要依賴瞬時(shí)提示改用結(jié)果頁(yè) URL 變化或列表頁(yè)出現(xiàn)新文章作為成功判據(jù)。這個(gè)思路很重要判斷成功要用穩(wěn)定信號(hào)不要用瞬時(shí)信號(hào)。5. 讓 Agent 穩(wěn)定干活的幾條硬經(jīng)驗(yàn)5.1 感知層要分區(qū)域定制別指望一套方案打天下前面反復(fù)提到頁(yè)面感知是 Agent 的眼睛。我最后的方案是分區(qū)感知導(dǎo)航區(qū)走無(wú)障礙樹正文區(qū)走 innerText表單區(qū)走 DOM 屬性提取彈窗區(qū)單獨(dú)監(jiān)聽。每個(gè)區(qū)域用最適合它的方式描述Agent 拿到的信息才準(zhǔn)確。這件事沒(méi)有銀彈只能一個(gè)區(qū)域一個(gè)區(qū)域地調(diào)。5.2 給 Agent 的提示詞要寫成操作規(guī)程不是任務(wù)描述我一開始給 Agent 的提示詞是幫我在騰訊云社區(qū)發(fā)一篇稿結(jié)果它各種自由發(fā)揮。后來(lái)我把它改寫成一份操作規(guī)程每一步的輸入是什么、輸出是什么、失敗怎么辦、什么情況下必須停下來(lái)問(wèn)人。改完之后Agent 的行為可預(yù)測(cè)多了。提示提示詞里一定要寫不確定時(shí)輸出候選并暫停否則 Agent 會(huì)硬著頭皮瞎點(diǎn)。5.3 重試要有策略梯度不能原地打轉(zhuǎn)重試不是簡(jiǎn)單地再來(lái)一次。我的做法是給每個(gè)動(dòng)作準(zhǔn)備三檔策略第一檔原樣重試第二檔換選擇器比如從 class 換成文本第三檔換交互方式比如從 click 換成 hover 再 click。三檔都失敗才上報(bào)人工。這樣能覆蓋絕大多數(shù)偶發(fā)問(wèn)題。5.4 全程留痕trace 是你的救命稻草Playwright 的 trace 功能一定要開。它會(huì)把每一步的截圖、DOM 快照、網(wǎng)絡(luò)請(qǐng)求全錄下來(lái)。我排查富文本那個(gè)坑全靠 trace 回放。沒(méi)有 trace我可能還在瞎猜。await context.tracing.start({ screenshots: true, snapshots: true }); // ... 執(zhí)行操作 await context.tracing.stop({ path: trace.zip });6. 這套方案還能怎么用從發(fā)稿到任何重復(fù)網(wǎng)頁(yè)操作跑通發(fā)稿這件事之后我意識(shí)到這套架構(gòu)的價(jià)值遠(yuǎn)不止發(fā)稿。任何需要登錄、需要填表、需要多步操作、且步驟相對(duì)固定的網(wǎng)頁(yè)任務(wù)都可以套這個(gè)模式。比如批量整理后臺(tái)數(shù)據(jù)、定時(shí)提交表單、跨系統(tǒng)的信息同步等等。關(guān)鍵是把任務(wù)拆成感知-決策-執(zhí)行三層然后針對(duì)每一層做穩(wěn)定性優(yōu)化。決策層交給 AI執(zhí)行層交給 Playwright感知層根據(jù)頁(yè)面特點(diǎn)定制。三層各司其職整個(gè)系統(tǒng)就穩(wěn)了。我個(gè)人在實(shí)際操作中的體會(huì)是別追求一步到位的全自動(dòng)先做到半自動(dòng) 人工兜底再逐步把人工環(huán)節(jié)替換掉。我這次實(shí)驗(yàn)里最后發(fā)布那一下其實(shí)還是我手動(dòng)確認(rèn)的——不是技術(shù)上做不到而是我覺(jué)得發(fā)布這種不可逆操作留個(gè)人工確認(rèn)更穩(wěn)妥。等技術(shù)再成熟一點(diǎn)再考慮全自動(dòng)也不遲。最后分享一個(gè)小技巧如果你也想試這套方案先從最簡(jiǎn)單的單頁(yè)表單開始練手別一上來(lái)就挑戰(zhàn)富文本編輯器。把感知層、執(zhí)行層、重試機(jī)制都調(diào)順了再上復(fù)雜頁(yè)面能少踩一半的坑。