動(dòng)Web自動(dòng)化測(cè)試:自動(dòng)生成Selenium與Playwright腳本)
每次產(chǎn)品提一嘴需求UI 改幾個(gè)樣式測(cè)試同學(xué)就要陪著熬夜改腳本——干過 Web 自動(dòng)化測(cè)試的人應(yīng)該都懂這種痛。過去大半年我一直在折騰一套內(nèi)部代號(hào)叫 Mirage Flow 的工具核心思路一句話把頁(yè)面改寫成一段可讀的 UI 描述它自動(dòng)生成對(duì)應(yīng)的 Selenium 或 Playwright 自動(dòng)化測(cè)試腳本。聽起來像是“測(cè)試代碼不用手寫了”實(shí)操下來大部分常見頁(yè)面確實(shí)可以直接出能跑通的代碼。這篇我會(huì)把 Mirage Flow 從設(shè)計(jì)思路、描述格式、映射規(guī)則到雙框架生成代碼的實(shí)現(xiàn)細(xì)節(jié)完整拆開講也會(huì)把我在內(nèi)部分享時(shí)幫別人避開的坑一并列出來。適合正在搭自動(dòng)化測(cè)試體系、被腳本維護(hù)逼到頭大的測(cè)試開發(fā)也適合想了解 UI 描述驅(qū)動(dòng)測(cè)試代碼生成這個(gè)思路的前端和 QA 同學(xué)。1. 自動(dòng)化測(cè)試腳本維護(hù)的痛和 UI 描述這個(gè)解法1.1 腳本為什么越寫越脆弱剛接觸自動(dòng)化測(cè)試那會(huì)兒我也和大多數(shù)人一樣拿到一個(gè)頁(yè)面就按 F12 復(fù)制 XPath拼出一段“能跑就行”的腳本。等用例攢到幾百條問題就全冒出來了。最頭疼的是定位器太脆弱。開發(fā)把按鈕的 CSS 類名從btn-login改成btn-primary腳本直接紅燈某個(gè)輸入框的 placeholder 文案從“請(qǐng)輸入手機(jī)號(hào)”變成“請(qǐng)輸入手機(jī)號(hào)碼”整條用例就崩。類似的問題還有頁(yè)面加了異步加載腳本還在等固定 sleep接口超時(shí)報(bào)錯(cuò)測(cè)試用例跟著誤報(bào)開發(fā)重構(gòu)了 DOM 層級(jí)一層層寫死的 XPath 全部失效。我曾經(jīng)統(tǒng)計(jì)過一個(gè)小版本迭代里腳本維護(hù)時(shí)間能占到總工作量的 60% 以上真正寫新功能的時(shí)間反而沒多少。更深一層的問題是協(xié)作成本。業(yè)務(wù)同事、新來的同學(xué)想看懂自動(dòng)化腳本發(fā)現(xiàn)全是driver.find_element(By.XPATH, //div[3]/div[1]/div[2]/button)這種天書。他們提一句“登錄按鈕現(xiàn)在換個(gè)位置了”你都得在全量用例里搜一遍有多少地方寫死了這個(gè)按鈕的路徑。時(shí)間一長(zhǎng)自動(dòng)化測(cè)試變成了少數(shù)人的私產(chǎn)別人既不敢動(dòng)也不敢提。這就有點(diǎn)像裝修時(shí)把所有開關(guān)位置都記在腦子里地圖和圖紙都不畫——家里一旦調(diào)整家具布局你只能摸黑挨個(gè)試。UI 描述驅(qū)動(dòng)生成腳本的思路本質(zhì)上就是先畫一張“平面圖”讓每個(gè)控件有自己穩(wěn)定的身份再讓施工隊(duì)按圖去干活。頁(yè)面改了改圖比扒墻皮快得多。1.2 為什么要把“頁(yè)面描述”和“測(cè)試代碼”拆開Mirage Flow 最早想解決的問題其實(shí)很簡(jiǎn)單能不能讓測(cè)試代碼不直接依賴某一條具體的 XPath而是依賴“這里有一個(gè)密碼輸入框”這類語義信息因?yàn)楫a(chǎn)品、UI、測(cè)試在評(píng)審時(shí)討論的從來不是#id_023而是“登錄頁(yè)有個(gè)密碼框”。于是我們把人要管的層次和機(jī)器要管的層次分開。測(cè)試人員維護(hù)一份 UI 描述描述里寫清楚頁(yè)面有什么元素、元素叫什么、有哪些動(dòng)作和斷言生成器拿到描述之后再根據(jù)目標(biāo)框架的能力去選擇定位策略、生成等待代碼、拼裝腳本骨架。也就是說描述只回答“這個(gè)頁(yè)面長(zhǎng)什么樣、我要做什么”代碼負(fù)責(zé)回答“具體怎么定位、怎么等待、怎么斷言”。這個(gè)拆分帶來的好處在很多真實(shí)場(chǎng)景里都被驗(yàn)證過頁(yè)面改版時(shí)我們通常只要更新描述文件里的字段重新生成腳本不用把所有用例翻一遍新增一個(gè)同類頁(yè)面時(shí)復(fù)制一份描述改幾個(gè)字段整個(gè)用例就有了業(yè)務(wù)同學(xué)參與評(píng)審描述文件比讓他們看WebDriverWait代碼接地氣得多同一個(gè)描述可以同時(shí)生成 Selenium 和 Playwright 兩套代碼切換技術(shù)棧時(shí)不必推倒重來。當(dāng)然這套方案也不是萬能的。頁(yè)面結(jié)構(gòu)極其復(fù)雜、控件完全沒有穩(wěn)定屬性、交互邏輯非常特殊的時(shí)候純靠描述生成反而比手寫更繞。所以 Mirage Flow 的定位從來不是“取代手寫測(cè)試代碼”而是把那些重復(fù)的、模式化的 Web 操作從手寫變成生成讓人把精力留在真正需要判斷的地方。2. Mirage Flow 的核心設(shè)計(jì)思路與方案選型2.1 整體架構(gòu)到底分了哪幾層Mirage Flow 整體拆成四層這也是我后來給團(tuán)隊(duì)做分享時(shí)最常用的分層方式。第一層是 UI 描述層。我們選用 YAML 作為描述文件的載體原因后面細(xì)說。這層只關(guān)心“頁(yè)面有哪些元素、元素類型是什么、有哪些動(dòng)作和斷言”完全不涉及任何定位器和框架 API。第二層是解析與校驗(yàn)層。生成器會(huì)讀取描述文件先做 Schema 校驗(yàn)字段缺失、類型錯(cuò)誤、引用不存在的元素都在這一層提前暴露。校驗(yàn)通過后把 YAML 歸一化成內(nèi)部統(tǒng)一的 Python 字典結(jié)構(gòu)后續(xù)模板只要消費(fèi)這一份結(jié)構(gòu)即可。第三層是代碼生成層。這里用了 Jinja2 模板引擎加一組針對(duì)元素類型的映射策略。生成器根據(jù)目標(biāo)框架Selenium 或 Playwright選擇不同的模板集模板里只負(fù)責(zé)拼裝代碼結(jié)構(gòu)具體的定位器計(jì)算和等待策略由策略模塊提供。第四層是執(zhí)行與反饋層。生成出來的腳本可以直接交給 pytest 執(zhí)行失敗時(shí)會(huì)截圖、抓取頁(yè)面源碼、輸出日志并和 Allure 報(bào)告做集成。執(zhí)行反饋又反過來告訴我們哪些元素描述偏向不穩(wěn)定可以在下次生成時(shí)主動(dòng)提示。為什么要把這四層拆得這么干凈因?yàn)樵趯?shí)際迭代中四層的變化頻率完全不同。描述格式相對(duì)穩(wěn)定映射策略會(huì)隨項(xiàng)目演進(jìn)不斷補(bǔ)充模板要跟隨兩個(gè)框架的 API 版本更新執(zhí)行層則要根據(jù)團(tuán)隊(duì)基礎(chǔ)設(shè)施調(diào)整。如果把這四層揉在一個(gè)腳本里任何一層小改動(dòng)都會(huì)引發(fā)連鎖問題后來者根本不敢動(dòng)。2.2 為什么同時(shí)支持 Selenium 和 Playwright而不是二選一立項(xiàng)討論時(shí)團(tuán)隊(duì)內(nèi)部也吵過一輪Selenium 生態(tài)成熟、團(tuán)隊(duì)多少都會(huì)一點(diǎn)Playwright 自動(dòng)等待和調(diào)試體驗(yàn)好、是趨勢(shì)。最后結(jié)論是都要支持但理由不是站隊(duì)而是現(xiàn)實(shí)需求。我先用一張表把兩個(gè)框架在我們項(xiàng)目里的對(duì)比列出來維度SeleniumPlaywright生態(tài)成熟度高歷史資料和社區(qū)案例多相對(duì)較新團(tuán)隊(duì)學(xué)習(xí)成本稍高自動(dòng)等待需要手動(dòng)寫顯式等待或 Polling內(nèi)置 Actionability 檢查默認(rèn)行為友好多 Tab / 多窗口需要切換窗口句柄代碼略繁瑣提供多頁(yè)面上下文API 更直觀iframe / Shadow DOMiframe 要顯式 switchShadow DOM 要額外處理frame_locator 與 Shadow DOM 支持更順滑網(wǎng)絡(luò)攔截與 Mock需要額外工具原生支持 route 攔截遺留系統(tǒng)兼容對(duì)老瀏覽器和復(fù)雜環(huán)境適應(yīng)性更好對(duì)現(xiàn)代 Chromium/Firefox/WebKit 更友好存量腳本遷移成本低很多團(tuán)隊(duì)已有大量 Selenium 用例高我們真實(shí)的業(yè)務(wù)里存量項(xiàng)目一大半是 Selenium 腳本這些腳本背后還有一堆自定義工具函數(shù)、測(cè)試數(shù)據(jù)準(zhǔn)備邏輯、內(nèi)部框架封裝。如果要求全部遷移到 Playwright光遷移和驗(yàn)證成本就夠嗆。而新立項(xiàng)的項(xiàng)目我們希望直接用 Playwright享受自動(dòng)等待和多瀏覽器支持。所以 Mirage Flow 的設(shè)計(jì)原則是描述層保持中立生成層通過適配器輸出兩種代碼。對(duì)使用的人來說同一個(gè)描述文件既能生成舊項(xiàng)目的 Selenium 回歸腳本也能生成新項(xiàng)目的 Playwright 用例。你不需要想清楚“未來用什么框架”先把頁(yè)面描述沉淀下來框架遷移只是一個(gè)重新生成的動(dòng)作。2.3 從 UI 描述到定位器的映射策略描述層里寫的是語義信息生成代碼時(shí)必須翻譯成真正的定位器。這一塊是 Mirage Flow 最核心的難點(diǎn)也是最需要反復(fù)打磨的部分。我們給元素定位設(shè)計(jì)了一個(gè)優(yōu)先級(jí)策略id優(yōu)先其次是>page: 登錄頁(yè) url: /login elements: - name: 用戶名輸入框 type: input placeholder: 請(qǐng)輸入用戶名 - name: 密碼輸入框 type: input placeholder: 請(qǐng)輸入密碼 secure: true - name: 登錄按鈕 type: button text: 登錄 actions: - name: 輸入用戶名 element: 用戶名輸入框 action: fill data: ${username} - name: 輸入密碼 element: 密碼輸入框 action: fill data: ${password} - name: 點(diǎn)擊登錄 element: 登錄按鈕 action: click assertions: - name: 校驗(yàn)登錄成功 element: 歡迎信息 type: visible這個(gè)文件的優(yōu)點(diǎn)在于沒有一行代碼但任何懂業(yè)務(wù)的人都能看懂我們?cè)跍y(cè)什么。elements定義頁(yè)面元素actions定義操作序列assertions定義結(jié)果校驗(yàn)。描述文件里最需要注意的是命名一致性。你在elements里給元素起的name是全局唯一標(biāo)識(shí)后面的actions和assertions都通過name引用它。我見過不少同事把同一個(gè)輸入框在元素定義里叫“手機(jī)號(hào)輸入框”在動(dòng)作里寫成“輸入手機(jī)號(hào)”結(jié)果生成器匹配不上報(bào)錯(cuò)信息還提示得不夠直接。后來我們?cè)?Schema 校驗(yàn)里強(qiáng)行要求引用任何元素前必須先保證該元素確實(shí)存在于elements列表否則解析直接失敗。3.2 元素類型識(shí)別與定位器生成的核心實(shí)現(xiàn)比較傻的做法是在模板里寫滿if type button這種硬編碼但一旦控件類型變多模板會(huì)變成一個(gè)巨大的分支地獄。Mirage Flow 里我們抽了一個(gè)locator_strategy.py每種元素類型對(duì)應(yīng)一個(gè)策略類由策略類負(fù)責(zé)計(jì)算定位表達(dá)式。以生成 CSS 選擇器為例核心邏輯大約是這樣def build_css_locator(element: dict) - str: # 優(yōu)先級(jí)id >from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC driver webdriver.Chrome() driver.maximize_window() try: driver.get(http://localhost:8080/login) username_input WebDriverWait(driver, 10).until( EC.presence_of_element_located( (By.CSS_SELECTOR, [placeholder*請(qǐng)輸入用戶名]) ) ) username_input.send_keys(test01) password_input WebDriverWait(driver, 10).until( EC.presence_of_element_located( (By.CSS_SELECTOR, [placeholder*請(qǐng)輸入密碼]) ) ) password_input.send_keys(abc123) login_button WebDriverWait(driver, 10).until( EC.element_to_be_clickable( (By.XPATH, //button[contains(text(),登錄)]) ) ) login_button.click() WebDriverWait(driver, 10).until( EC.visibility_of_element_located( (By.XPATH, //*[text()歡迎回來]) ) ) finally: driver.quit()生成代碼時(shí)我特別要求把WebDriverWait的顯式等待寫進(jìn)去而不是讓框架替我們偷懶用time.sleep。原因很簡(jiǎn)單Selenium 沒有內(nèi)建 Actionability 檢查presence_of_element_located只代表元素出現(xiàn)了不代表能被點(diǎn)擊。如果生成器偷懶生成的腳本八成在真實(shí)環(huán)境里跑不穩(wěn)。所以對(duì)可點(diǎn)擊元素我們一律生成element_to_be_clickable對(duì)需要輸入的控件至少生成presence_of_element_located保證元素被渲染出來。生成器還處理了一些“看起來不起眼但很要命”的細(xì)節(jié)finally里必須driver.quit()防止并發(fā)跑用例時(shí)進(jìn)程不釋放關(guān)鍵操作后如果描述里開啟了截圖自動(dòng)插入截圖代碼最后把異常信息帶出方便 pytest 捕獲。3.4 生成 Playwright 代碼時(shí)差異點(diǎn)比想象中大剛開始我以為 Playwright 代碼就是把find_element換成locator而已實(shí)際上手才發(fā)現(xiàn)兩個(gè)框架的生成邏輯差異遠(yuǎn)比 API 名稱差異大。Playwright 最大的不同是自帶 Actionability 檢查click()方法會(huì)自動(dòng)等待元素可見、穩(wěn)定、可點(diǎn)擊。這帶來一個(gè)直接后果生成代碼里不需要堆WebDriverWait。但我們必須理解它的等待語義否則生成的代碼可能用錯(cuò)等待方式。一個(gè)典型的 Playwright 生成結(jié)果from playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.launch() page browser.new_page() page.goto(http://localhost:8080/login) page.fill([placeholder*請(qǐng)輸入用戶名], test01) page.fill([placeholder*請(qǐng)輸入密碼], abc123) page.click(button:has-text(登錄)) page.wait_for_selector(text歡迎回來) browser.close()兩套代碼放在一起看差異就非常直觀Selenium 版本需要手動(dòng)等待、手動(dòng)選擇顯式條件生成器要替用戶做很多決策Playwright 版本自帶等待生成器只需要保證定位器合理代碼看起來簡(jiǎn)潔得多Playwright 的多 Tab 和 iframe 處理方式不同生成器生成的代碼結(jié)構(gòu)也不同。我們?cè)?jīng)給一個(gè)涉及內(nèi)部系統(tǒng) iframe 的頁(yè)面生成 Playwright 代碼生成器直接輸出frame_locator(iframe[titlemain]).locator(...)這比 Selenium 里switch_to.frame再定位的代碼可讀性好太多。雙框架生成讓我學(xué)到一個(gè)教訓(xùn)不要試圖在一套抽象里掩蓋框架的差異因?yàn)檫@樣生成的代碼必然是你妥協(xié)后的“最小公分母”。Mirage Flow 的做法是讓描述層完全相同但生成模板按框架能力各自發(fā)揮。Playwright 模板可以用它的自動(dòng)等待優(yōu)勢(shì)Selenium 模板就老老實(shí)實(shí)生成顯式等待兩者生成的代碼都符合各自社區(qū)的最佳實(shí)踐。3.5 數(shù)據(jù)驅(qū)動(dòng)和斷言怎么生成到腳本里自動(dòng)化測(cè)試不可能只有一組數(shù)據(jù)。登錄頁(yè)至少要測(cè)正確密碼和錯(cuò)誤密碼搜索結(jié)果頁(yè)要測(cè)不同關(guān)鍵詞。所以描述文件里的${username}、${password}這類變量必須能透?jìng)鹘o測(cè)試框架。我們最終選擇讓生成器輸出 pytest 參數(shù)化入口import pytest from yourgen_module.login_page import run_login pytest.mark.parametrize(username,password,expect_success, [ (test01, abc123, True), (test02, wrong, False), ]) def test_login(username, password, expect_success): run_login(usernameusername, passwordpassword, expect_successexpect_success)這樣設(shè)計(jì)的好處是數(shù)據(jù)和業(yè)務(wù)邏輯完全分離。測(cè)試數(shù)據(jù)可以放在 JSON/Excel 里再由一個(gè)很小的加載邏輯喂給參數(shù)化裝飾器業(yè)務(wù)腳本則由生成器維護(hù)描述文件更新后重新生成即可。斷言部分也是一樣。描述文件里寫了assertions生成器會(huì)翻譯成對(duì)應(yīng)框架的斷言代碼。我們支持了最常見的幾類元素可見、元素文本等于、元素?cái)?shù)量等于、元素屬性包含、當(dāng)前 URL 包含。每類斷言在不同框架里寫法不同所以這塊也是雙模板的重點(diǎn)。不要小看斷言描述的重要性它其實(shí)是把測(cè)試意圖沉淀下來比只記錄操作路徑更有價(jià)值。4. 常見問題與排查技巧實(shí)錄4.1 定位器生成出來卻還是不穩(wěn)定問題出在哪Mirage Flow 上線第一個(gè)月被問得最多的就是“為什么我根據(jù)描述生成了選擇器跑兩次還是掛了”。我排查了一圈發(fā)現(xiàn)絕大多數(shù)情況不是生成器的問題而是頁(yè)面里存在多個(gè)滿足條件的節(jié)點(diǎn)。舉例來說頁(yè)面里有三個(gè)按鈕文本都包含“提交”描述里寫type: button, text: 提交生成器用:has-text(提交)或contains(text(),提交)定位時(shí)匹配到多個(gè)節(jié)點(diǎn)。Selenium 會(huì)直接拋ElementNotInteractableException或StaleElementReferenceExceptionPlaywright 的strict mode會(huì)直接報(bào)錯(cuò)提示“l(fā)ocator resolved to 3 elements”。這個(gè)我后來在描述規(guī)范里加了一條規(guī)則如果元素文本模糊請(qǐng)務(wù)必補(bǔ)充位置信息比如index: 0或者寫一個(gè)更精確的父級(jí)描述。另一個(gè)常見坑是頁(yè)面里存在不可見元素。比如開發(fā)用隱藏的input做狀態(tài)存儲(chǔ)placeholder 恰好又和真實(shí)輸入框類似。這時(shí)候顯式等待能查到元素但send_keys卻報(bào)錯(cuò)。我們?cè)谏?Selenium 代碼時(shí)特意對(duì)fill動(dòng)作增加visibility_of_element_located而不是presence_of_element_located能減少一部分誤報(bào)。排查這類問題我推薦一個(gè)土辦法把生成的 locator 直接粘到瀏覽器 DevTools 的 Console 里執(zhí)行document.querySelectorAll(...)數(shù)數(shù)匹配了幾個(gè)節(jié)點(diǎn)。凡是能立刻看出匹配數(shù)量不對(duì)基本都能在生成階段發(fā)現(xiàn)問題不用反復(fù)跑用例試錯(cuò)。4.2 iframe 和 Shadow DOM描述層怎么表達(dá)現(xiàn)在的系統(tǒng)里嵌套 iframe、Shadow DOM 已經(jīng)很常見。早期描述文件里沒有這個(gè)概念生成的腳本遇到內(nèi)部系統(tǒng)頁(yè)面就必掛。后來我們?cè)谠孛枋隼镌黾恿藘蓚€(gè)可選字段- name: 內(nèi)部操作按鈕 type: button text: 確定 frame: iframe[titlemain] shadow: true生成器遇到frame字段時(shí)Selenium 側(cè)會(huì)生成切 frame 再定位的代碼Playwright 側(cè)則生成frame_locator(...).locator(...)。遇到shadow字段時(shí)處理方式又不一樣Playwright 默認(rèn)可以穿透 open shadow root而 Selenium 需要先取shadowRoot再往里查。這些差異統(tǒng)統(tǒng)被封裝在模板層對(duì)描述作者來說只是多寫兩個(gè)字段。這里有個(gè)值得強(qiáng)調(diào)的點(diǎn)如果不確定元素到底在不在 iframe 里可以先跑一次帶--debug的生成模式工具會(huì)輸出候選定位器在頁(yè)面上的命中情況。比起在腳本里加斷點(diǎn)這個(gè)階段排查成本低得多。4.3 等待策略固定 sleep 是最毒的用法我見過太多現(xiàn)有自動(dòng)化項(xiàng)目里全是time.sleep(5)。剛開始腳本能跑等頁(yè)面變慢或者接口變慢sleep 不夠就掛為了解決掛有人把 sleep 加到 10 秒、15 秒于是整個(gè)用例集跑到要 40 分鐘。這種代碼不能算自動(dòng)化測(cè)試只能叫定時(shí)器表演。Mirage Flow 生成的代碼里我們默認(rèn)不輸出任何固定 sleep。Selenium 側(cè)用WebDriverWait配合具體條件Playwright 側(cè)直接依賴自動(dòng)等待能力。唯一允許出現(xiàn)page.wait_for_timeout(500)的場(chǎng)景是那些頁(yè)面里有動(dòng)畫過渡、且動(dòng)畫結(jié)束后元素才可交互的罕見情況而且我們會(huì)在描述里顯式標(biāo)注delay: 500讓使用者意識(shí)到這里有一個(gè)不經(jīng)意的等待。如果用例跑下來還是不穩(wěn)定第一步永遠(yuǎn)不是加 sleep而是打開瀏覽器的 Network 面板看請(qǐng)求到底卡在哪。是后端 5 秒才返回還是前端 JS 渲染慢了定位到原因后或者調(diào)整接口 Mock或者改用更合理的等待條件。把“等待“這件事交給廣播式輪詢而不是固定假設(shè)頁(yè)面一定在 N 秒內(nèi)加載完穩(wěn)定性會(huì)完全不一樣。4.4 和 CI 集成時(shí)最容易踩的三個(gè)坑生成出來的腳本最終要跑在流水線里和本地跑完全是兩種體驗(yàn)。最先踩的坑是測(cè)試環(huán)境地址寫死在描述文件里。我們?cè)诿枋鑫募镉?{base_url}這種變量占位CI 里通過環(huán)境變量注入這樣同一套描述可以隨便切換 dev / staging / prod 環(huán)境。第二個(gè)坑是失敗證據(jù)收集。CI 里腳本掛了人不在現(xiàn)場(chǎng)連截圖都看不到。我們?cè)?pytest 的conftest.py里加了 fixture每個(gè)用例失敗時(shí)自動(dòng)截圖并附加到 Allure 報(bào)告同時(shí)抓取當(dāng)前頁(yè)面 URL 和頁(yè)面源碼。這一步對(duì)排查定位器失效特別有用因?yàn)楹芏鄷r(shí)候光看報(bào)錯(cuò)信息根本不知道頁(yè)面當(dāng)時(shí)長(zhǎng)什么樣。第三個(gè)坑是瀏覽器并發(fā)和資源占用。早期我們?cè)?CI 里同時(shí)起十個(gè) Chrome 進(jìn)程跑不同用例經(jīng)常把節(jié)點(diǎn)內(nèi)存打滿出現(xiàn)一堆莫名其妙的白屏和超時(shí)。后來做了兩件事一是控制并行度二是給每個(gè)任務(wù)固定臨時(shí)目錄避免多個(gè)瀏覽器搶同一份用戶數(shù)據(jù)目錄。看起來很基礎(chǔ)但確實(shí)救了我們很多次。5. 一些實(shí)際體會(huì)和擴(kuò)展方向Mirage Flow 做到現(xiàn)在最有價(jià)值的不是它生成了多少行代碼而是逼著我們把“頁(yè)面怎么測(cè)”這件事用結(jié)構(gòu)化方式沉淀下來。以前討論一條用例是否合理大家得先翻代碼現(xiàn)在直接看描述文件一條條核對(duì)元素和斷言效率高很多。我個(gè)人的經(jīng)驗(yàn)是先覆蓋高頻控件不要妄想一步到位。按鈕、輸入框、下拉、單選、文本斷言這幾類能覆蓋日常 80% 的頁(yè)面交互。表格、彈窗、拖拽、復(fù)雜動(dòng)畫這類特殊場(chǎng)景寧可讓它落地在描述里留手工擴(kuò)展口也不要讓模板無限變復(fù)雜。模板越花哨后期維護(hù)越痛苦。后續(xù)我打算做兩件事。一是把描述文件做得更“寬松”允許自然語言描述加參數(shù)配置混合使用比如“點(diǎn)擊搜索按鈕”結(jié)合type: button一起解析讓非測(cè)試同學(xué)也能寫描述。二是把定位器穩(wěn)定性統(tǒng)計(jì)接回來每次執(zhí)行后記錄哪些 locator 匹配多個(gè)節(jié)點(diǎn)、哪些頁(yè)面元素經(jīng)常變化讓生成器根據(jù)歷史執(zhí)行數(shù)據(jù)調(diào)整策略優(yōu)先級(jí)。這些都是基于現(xiàn)有描述層之上的增量不用推翻架構(gòu)這也是當(dāng)初分層設(shè)計(jì)最值得的一筆投資。如果讓我重新來一遍我會(huì)在立項(xiàng)時(shí)就把描述文件的 Schema 定得更嚴(yán)一些字段窮舉清楚而不是邊用邊加。現(xiàn)在雖然做了向后兼容處理但早期那些“摸著石頭過河”留下的模糊字段依然會(huì)在自動(dòng)生成時(shí)造成語義歧義。自動(dòng)化工具最怕的不是功能少而是規(guī)則不明確。把規(guī)則寫清楚把例外留給人判斷這套思路放到任何自動(dòng)化項(xiàng)目里都適用。