一、背景
從 Web 誕生之日起,UI 自動(dòng)化就成了測(cè)試的難點(diǎn),到現(xiàn)在近 30 年,一直沒(méi)有有效的手段解決Web UI測(cè)試的問(wèn)題,盡管發(fā)展了很多的 webdriver 驅(qū)動(dòng),圖片 diff 驅(qū)動(dòng)的工具,但是這些工具的投入產(chǎn)出比一直被質(zhì)疑,自動(dòng)化率越多維護(hù)成本越高,大部分都做著就放棄了,還有一部分在做與不做間糾結(jié)。
本文結(jié)合一些開(kāi)源的項(xiàng)目探索使用GPT 自動(dòng)做 UI 測(cè)試的可能性。
二、方案選型
當(dāng)前UI 的主要問(wèn)題:一個(gè)是通過(guò) Webdriver 控制瀏覽器執(zhí)行,這些工具都需要先查找到對(duì)應(yīng)元素的 Elements,無(wú)論是錄制的還是自己編寫(xiě)的在面對(duì) UI 變化,元素變化時(shí)都需要耗費(fèi)很大的精力去重新識(shí)別,解析 Dom 查找,這個(gè)工作乏味且沒(méi)有效率;另一種是通過(guò)圖像進(jìn)行點(diǎn)擊,比如 Sikuli 這種工具,主要的問(wèn)題也是復(fù)用性較差,換個(gè)分辨率的機(jī)器或者圖片發(fā)生少的改動(dòng)就不可用。
使用 GPT 做 UI 測(cè)試嘗試了兩種方案:
第一種將 Html 元素投喂給 GPT,主要方法獲取 Html代碼,對(duì) Html 做初步縮減處理,再做向量化,然后喂給 GPT4 自動(dòng)生成 Webdriver 驅(qū)動(dòng)腳本,效果一般,而且因?yàn)?Html 比較大,Token 的消耗很大。
第二種思路是讓 GPT 像人一樣思考和測(cè)試,比如一個(gè)人打開(kāi)一個(gè)網(wǎng)頁(yè)后,他通過(guò)眼睛看到的頁(yè)面文字或圖標(biāo),然后用手完成點(diǎn)擊和輸入的操作,最后通過(guò)頁(yè)面的彈窗或者文字來(lái)識(shí)別是否有錯(cuò)誤,這幾個(gè)動(dòng)作通過(guò)大腦統(tǒng)一協(xié)調(diào)。
這里主要介紹第二種.
三、新方案實(shí)踐
1.新方案簡(jiǎn)介
新的方案主要結(jié)合 Playwright,SoM視覺(jué)標(biāo)記,GPT4Vison,GPT4,AutoGen來(lái)實(shí)現(xiàn)。主要的原理
通過(guò) Playwright進(jìn)行瀏覽器操作,包括頁(yè)面圖像的獲取、瀏覽器的各種操作,相當(dāng)于‘‘手’’;
進(jìn)行SoM 視覺(jué)數(shù)據(jù)標(biāo)記,因?yàn)?GPT4Vison 在進(jìn)行頁(yè)面原始識(shí)別時(shí)并不是很準(zhǔn)確,參考微軟的論文可以通過(guò)視覺(jué)標(biāo)記的手段來(lái)輔助 GPT4V 識(shí)別,相當(dāng)于“眼睛”。
通過(guò)GPT4+AutoGen 將這些步驟串起來(lái)實(shí)現(xiàn)協(xié)調(diào)控制,相當(dāng)于“大腦”和“神經(jīng)中樞”。
2.主要架構(gòu)
?
3.實(shí)現(xiàn)步驟
1. 使用 Playwright 注入 JS
browser = playwright.chromium.launch(channel="chrome",headless=False) context = browser.new_context() page = context.new_page() page.goto("http://oa.jd.com/") inject_js ="./pagemark.js" withopen(inject_js,'r')asfile: content =file.read() page.evaluate(f"{content}")
2. SoM 視覺(jué)提示標(biāo)記
如前文提到的 GPT4V 并不能有效的識(shí)別 Web 的元素,所以在使用 GPT4V 之前進(jìn)行圖像標(biāo)記,圖像標(biāo)記現(xiàn)在有兩種方式,一種是通過(guò) AI 識(shí)別圖片進(jìn)行標(biāo)記,這種主要利用在對(duì)靜態(tài)圖片圖像的識(shí)別,對(duì)于 Web 頁(yè)面的標(biāo)記,我們可以采用注入 JS 修改頁(yè)面元素的方式來(lái)標(biāo)記。這里通過(guò)在瀏覽器中注入 pagemark.js,利用 Playwright 執(zhí)行 js 函數(shù)來(lái)完成頁(yè)面的標(biāo)記,該 JS 能夠完成標(biāo)準(zhǔn)的coco annotation的標(biāo)注。
// DOM Labelerlet labels =[];functionunmarkPage(){ for(const label of labels){ document.body.removeChild(label); } labels =[]; } functionmarkPage(){ unmarkPage(); var bodyRect = document.body.getBoundingClientRect(); var items =Array.prototype.slice.call( document.querySelectorAll('*') ).map(function(element){ var vw = Math.max(document.documentElement.clientWidth ||0, window.innerWidth ||0); var vh = Math.max(document.documentElement.clientHeight ||0, window.innerHeight ||0); var rects =[...element.getClientRects()].filter(bb=>{ var center_x = bb.left + bb.width /2; var center_y = bb.top + bb.height /2; var elAtCenter = document.elementFromPoint(center_x, center_y); return elAtCenter === element || element.contains(elAtCenter) }).map(bb=>{ const rect ={ left: Math.max(0, bb.left), top: Math.max(0, bb.top), right: Math.min(vw, bb.right), bottom: Math.min(vh, bb.bottom) }; return{ ...rect, width: rect.right - rect.left, height: rect.bottom - rect.top } }); var area = rects.reduce((acc, rect)=> acc + rect.width * rect.height,0); return{ element: element, include: (element.tagName ==="INPUT"|| element.tagName ==="TEXTAREA"|| element.tagName ==="SELECT")|| (element.tagName ==="BUTTON"|| element.tagName ==="A"||(element.onclick !=null)|| window.getComputedStyle(element).cursor =="pointer")|| (element.tagName ==="IFRAME"|| element.tagName ==="VIDEO") , area, rects, text: element.textContent.trim().replace(/s{2,}/g,' ') }; }).filter(item=> item.include &&(item.area >=20) ); // Only keep inner clickable items items = items.filter(x=>!items.some(y=> x.element.contains(y.element)&&!(x == y))) // Function to generate random colors functiongetRandomColor(){ var letters ='0123456789ABCDEF'; var color ='#'; for(var i =0; i 6; i++){ color += letters[Math.floor(Math.random()*16)]; } return color; } // Lets create a floating border on top of these elements that will always be visible items.forEach(function(item, index){ item.rects.forEach((bbox)=?>{ newElement = document.createElement("div"); var borderColor =getRandomColor(); newElement.style.outline =`2px dashed ${borderColor}`; newElement.style.position ="fixed"; newElement.style.left = bbox.left +"px"; newElement.style.top = bbox.top +"px"; newElement.style.width = bbox.width +"px"; newElement.style.height = bbox.height +"px"; newElement.style.pointerEvents ="none"; newElement.style.boxSizing ="border-box"; newElement.style.zIndex =2147483647; // newElement.style.background = `${borderColor}80`; // Add floating label at the corner var label = document.createElement("span"); label.textContent = index; label.style.position ="absolute"; label.style.top ="-19px"; label.style.left ="0px"; label.style.background = borderColor; label.style.color ="white"; label.style.padding ="2px 4px"; label.style.fontSize ="12px"; label.style.borderRadius ="2px"; newElement.appendChild(label); document.body.appendChild(newElement); labels.push(newElement); console.log(index) }); }) }
以某系統(tǒng)為例,標(biāo)注后的效果如下圖:
將標(biāo)記元素的位置編號(hào)保存到本地coco.json
{ "1":{"x":"10","y":"40","w":"8","h":"10","center_x":"30","center_y":"13"}, "2":{"x":"20","y":"40","w":"16","h":"10","center_x":"30","center_y":"21"}, "10":{"x":"40","y":"80","w":"16","h":"10","center_x":"80","center_y":"21"}}
其中 center_x, center_y 為元素的中心點(diǎn)坐標(biāo)
3.GPT4V 識(shí)別
Prompt
我上傳了一張網(wǎng)頁(yè)截圖,頁(yè)面元素已經(jīng)使用COCO Annotator進(jìn)行了數(shù)字標(biāo)記。 請(qǐng)分析這張圖片,并識(shí)別出網(wǎng)頁(yè)的用途以及各個(gè)標(biāo)記元素的功能?;谀愕姆治觯?qǐng)生成一個(gè)規(guī)劃路徑來(lái)完成以下任務(wù): - 輸入ERP搜索。 - 檢查列表是否為空 請(qǐng)使用圖片中的數(shù)字標(biāo)記來(lái)描述完成任務(wù)的具體步驟。
GPT4V Response
這張圖片顯示的是一個(gè)具有多個(gè)數(shù)字標(biāo)記的網(wǎng)頁(yè)截圖。根據(jù)這些標(biāo)記,我們可以推測(cè)出網(wǎng)頁(yè)的用途以及各個(gè)元素的功能。 為了完成您提出的任務(wù),我們可以按照以下步驟操作: nn1. 使用標(biāo)記為10的搜索框輸入ERP搜索詞。這個(gè)搜索框的標(biāo)簽顯示為“搜索框輸入erp”,表明它用于輸入搜索詞。 nn2. 點(diǎn)擊標(biāo)記為17的搜索按鈕。這個(gè)按鈕的標(biāo)簽是“搜索”,表明它用于提交搜索請(qǐng)求。 nn3. 檢查列表是否為空。這個(gè)步驟可能需要查看標(biāo)記為19的區(qū)域,這個(gè)區(qū)域被標(biāo)記為“列表內(nèi)容”,如果這個(gè)區(qū)域沒(méi)有顯示任何內(nèi)容或者有提示信息顯示列表為空,則說(shuō)明搜索結(jié)果為空。 nn以 上步驟基于圖片中的數(shù)字標(biāo)記和元素標(biāo)簽來(lái)描述完成任務(wù)的具體操作。"
提取元素坐標(biāo)
利用正則表達(dá)式從GPT4V 的 response 中提取查詢到的元素的 bbox id= 10,17,19, 結(jié)合在 SoM 標(biāo)記中記錄的 json 文件,找到編號(hào) 10 的元素坐標(biāo)"10":{"x":"40","y":"80","w":"16","h","10","center_x":"80","center_y":"21"}
class GPT4VRecognize def get_location(self, query): coco_json='./coco.json' withopen(coco_json,'r') as file: content =file.read() matches=re.findall(r'd+',gpt4v_response_content) num=-1 iflen(matches)>0: num=matches[0] data = json.loads(json_str) center_x = data[num]["center_x"] center_y = data[num]["center_y"] return center_x,center_y
4.Playwright操作頁(yè)面
Playwright是一個(gè)非常強(qiáng)大的操作瀏覽器的工具,這里因?yàn)榍懊嬉呀?jīng)通過(guò) GPT4V 識(shí)別了圖片,所以我們主要通過(guò) 坐標(biāo) 的方式來(lái)控制元素,不再需要解析Dom 來(lái)控制,主要的包括,點(diǎn)擊,雙擊,鼠標(biāo)拖動(dòng),輸入,截圖等:
class Actions: page=None __init__(self,url): global page browser = playwright.chromium.launch(channel="chrome",headless=False) context = browser.new_context() page = context.new_page() page.goto("http://oa.jd.com/") def mouse_move(self,x,y): page.mouse.move(self,x,y) def screenshot(self): page.screenshot() def mouse_click(self,x,y): page.mouse.click(self,x,y) def input_text(self,x,y,text): page.mouse.click(self,x,y) page.keyboard.type(text) def mouse_db_click(self,x,y): def select_option(self,x,y,option): ......
5.使用 AutoGen編排
AutoGen是一個(gè)代理工具,它能夠代理多個(gè) LLM在不同會(huì)話間切換,能夠自動(dòng)的調(diào)用預(yù)定的函數(shù),并協(xié)調(diào)這些 LLM 完成任務(wù)。
在上面的程序中,實(shí)現(xiàn)了:眼睛:通過(guò) GPT4V 來(lái)識(shí)別元素;手:通過(guò) Playwright 來(lái)做各種操作;后面需要通過(guò)一個(gè)大腦來(lái)協(xié)調(diào)手、眼完成任務(wù),這里通過(guò) GPT4+AutoGen 來(lái)實(shí)現(xiàn)大腦的協(xié)同調(diào)度。
config_list = config_list_from_json(env_or_file="OAI_CONFIG_LIST") assistant= autogen.AssistantAgent( name="assistant", system_message= """ You are the orchestrator responsible for completing a task involving the UI window. Utilize the provided functions to take a screenshot after each action. Remember to only use the functions you have been given and focus on the task at hand. """, llm_config={"config_list": config_list}, ) user_proxy = autogen.UserProxyAgent( name="brain_proxy", human_input_mode="NEVER", code_execution_config={"work_dir":"coding"}, max_consecutive_auto_reply=10, llm_config={"config_list": config_list},) action = Actions(url) gpt4v=GPT4VRecognize() user_proxy.register_function( function_map={ "get_location": gpt4v.get_location, "mouse_move":action.mouse_move, "screenshot":action.screenshot, "mouse_click":action.mouse_click, "mouse_dbclick":action.mouse_dbclick, "select_option":action.select_option }) def run(message_ask): user_proxy.initiate_chat(assistant,message=message_ask) if __name__ =="__main__": run("通過(guò) 輸入erp 'wwww30' 搜索結(jié)果,并檢查是否返回空列表")
四、問(wèn)題和后續(xù)
1.當(dāng)前的主要問(wèn)題
本文主要拋磚引玉,通過(guò)一種完全按人類思維進(jìn)行 UI測(cè)試的方式,在實(shí)驗(yàn)過(guò)程中還有很多問(wèn)題待解決。
1. GPT 在中文語(yǔ)境下識(shí)別的不太友好,在實(shí)驗(yàn)過(guò)程中對(duì)中文的 prompt 理解有誤,需要不斷的優(yōu)化 prompt,尤其是頁(yè)面中的中文元素。
2. AutoGen 對(duì)于處理預(yù)定義的動(dòng)作也會(huì)有問(wèn)題,需要不斷調(diào)優(yōu)。
3. GPT4V 真的很貴。
2.未來(lái)的想法
1. 將每次向 GPT4V請(qǐng)求的圖像識(shí)別做本地化處理,結(jié)合現(xiàn)有的一些測(cè)試方法,從而減少 Token,縮短執(zhí)行耗時(shí)。
2. 面向業(yè)務(wù)的 GPT需要不斷訓(xùn)練,將系統(tǒng)使用手冊(cè)和一有的 PRD 文檔投喂給 GPT,增強(qiáng) gpt 的系統(tǒng)識(shí)別和測(cè)試能力。
五、參考
1.Set-of-Mark Prompting Unleashes Extraordinary Visual Grounding in GPT-4V?
2.Microsoft AutoGen?
3.GPT-4V-ACT?
審核編輯 黃宇
-
測(cè)試
+關(guān)注
關(guān)注
8文章
5706瀏覽量
128883 -
AI
+關(guān)注
關(guān)注
88文章
35164瀏覽量
280014 -
GPT
+關(guān)注
關(guān)注
0文章
368瀏覽量
16096
發(fā)布評(píng)論請(qǐng)先 登錄
評(píng)論