当前UI自动化测试脚本的稳定性受多种因素影响,导致执行失败率较高,主要失败原因包括:
1.元素定位失效:前端属性(ID/Class/XPath)动态变更,导致脚本无法识别元素
2.页面结构重构:DOM层级或组件库升级引发原有定位策略失效
3.环境问题:网络延迟、浏览器版本差异等造成执行中断
4.业务逻辑变更:需求调整导致流程变化,原有用例不再适用
所以引入AI辅助的自愈机制,希望通过AI 实现智能定位修复,通过AI识别页面结构变化,自动生成备选选择器,自动调整定位策略,以适应前端变更,从而减少人工干预,降低维护成本降低,提高脚本稳定性。
根据UI自动化执行失败的原因,制定不同的修复策略,具体如下
本文主要分享通过调用AI api 根据错误原因,推荐心得元素定位器列表,逐一验证新的元素定位器,验证通过则修改脚本。
调用方式:
importrequestsAPI_KEY="your_api_key"url="https://api.deepseek.com/v1/chat/completions"headers={"Authorization":f"Bearer{API_KEY}","Content-Type":"application/json"}payload={"model":"deepseek-chat","messages":[{"role":"user","content":"请解释Transformer的注意力机制"}],"temperature":0.7,"max_tokens":1000}response=requests.post(url,json=payload,headers=headers)print(response.json())Deepseek API 返回数据类型:支持json数据返回
AI_API_KEY=Bearersk-keyxxxx#需要申请API_URL=https://api.deepseek.com/v1/chat/completions
2. 自愈脚本self_healing_locator.py
1. 构建promot模版
2. 调用AI API输出推荐选择器列表
3. 遍历校验候选选择器有效性,验证通过则修改脚本;
importreimportrequestsfromplaywright.sync_apiimportPagefromcommon.rwConfigimportrwConfigfromcommon.logimportlogger# 获取配置文件信息AI_API_KEY = rwConfig().getConfigOption(option='AI_API_KEY', section="common")API_URL = rwConfig().getConfigOption(option='API_URL', section="common")classHealer:def__init__(self, page: Page):self.page = pagedefdeepseek(self, prompt:str) ->list:"""调用AI API 生成定位器"""headers = {"Authorization": AI_API_KEY,"Content-Type":"application/json"}# 调用deepseek APIpayload = {"model":"deepseek-chat","messages": [{"role":"user","content": prompt}],"temperature":0.7,"max_tokens":1000}try:response = requests.post(API_URL, headers=headers, json=payload)logger.info(f"API调用成功:{response.json()['choices'][0]['message']['content']}")selector_list =eval(response.json()['choices'][0]['message']['content'])logger.info(f"AI推荐定位器列表:{selector_list}")# 返回推荐列表returnselector_listexceptExceptionase:logger.info(f"API调用失败:{str(e)}")return[]deflocator_prompt(self, pass_selector:str,failed_selector:str) ->str:"""构建元素定位提示词模板"""# 获取正确元素区域的页面内容dom_snippet =self.get_html_around_element(pass_selector)# logger.info(f"截断dom内容: {dom_snippet}")returnf"""请为Playwright测试生成3个可靠的元素定位器,要求:1. 代替定位失败的选择器:{failed_selector}2. 基于当前页面片段(可能被截断):{dom_snippet}输出content要求:- 按稳定性优先级排序,剔除需要开发人员参与的定位方式- 使用CSS或XPath格式- 输出为Python列表格式,如:["1", "2"],剔除其他文案,仅保留list内容"""defheal_element(self,pass_selector:str,failed_selector:str, test_file:str) ->bool:"""执行自愈流程"""i =0prompt =self.locator_prompt(pass_selector,failed_selector)logger.info(f"生成定位器提示词:{prompt}")# 调用AI API 生成定位器elements =self.deepseek(prompt)# 验证选择器有效性forselectorinelements:logger.info(f"第【{i+1}】次验证选择器有效性:{selector}")ifself._try_selector(selector):# 更新测试脚本文件logger.info(f"更新测试脚本文件:{test_file}失败元素:{failed_selector}新定位器:{selector}")self.update_file(test_file, failed_selector, selector)returnTrueelse:i +=1ifi >3:logger.info(f"AI推荐的选择器均 验证失败")returnFalselogger.info(f"未找到有效定位器:{failed_selector}")returnFalsedefget_html(self, selector, chars_before=2000, chars_after=10000):# 获失败元素局部页面的HTMLfull_html = self.page.content()# 获取元素的outerHTML和位置element_html = self.page.locator(selector).evaluate('el => el.outerHTML')element_index = full_html.find(element_html)ifelement_index == -1:logger.info("关键元素 未找到")returnNone# 计算截取范围start =max(0, element_index - chars_before)end =min(len(full_html), element_index +len(element_html) + chars_after)# 截取页面片段surrounding_html = full_html[start:end]returnsurrounding_htmldeftry_selector(self, selector:str) ->bool:"""验证选择器有效性"""try:self.page.wait_for_selector(selector, timeout=3000)logger.info(f"选择器 有效:{selector}")returnTrueexcept:logger.info(f"选择器 无效:{selector}")returnFalsedefupdate_file(self, file_path:str, old:str, new:str):"""更新测试脚本文件"""withopen(file_path,"r")asf:content = f.read()updated = re.sub(rf'([\'"])({re.escape(old)})([\'"])',lambdam:f"{m.group(1)}{new}{m.group(3)}",content)logger.info(f"更新后的内容:{updated}")withopen(file_path,"w")asf:f.write(updated)
3. 测试demo
importpytestfromplaywright.sync_apiimportPage, expectfromcommon.self_healing_locatorimportSaucedemoHealerfromcommon.rwConfigimportrwConfigfromconftestimport*URL ="https://xxxxxx/sso"headless=bool(int(rwConfig().getConfigOption(option='headless')))@pytest.fixture(scope="module")defpage():withsync_playwright()asplaywright:browser = playwright.chromium.launch(headless=headless)#保存登录信息# context = browser.new_context(storage_state="./data/state.json")context = browser.new_context(page=context.new_page()page.goto(URL)yieldpagebrowser.close()@pytest.fixturedefhealer(page):yieldHealer(page)deftest_checkout_flow(page, healer: Healer):try:page.goto(URL)visible = page.get_by_role("heading", name="手机密码登录").is_visible()ifnotvisible:# 切换到手机号密码登录页面time.sleep(1)page.locator("xxwc-togger-sign div").nth(1).click()# 输入手机号page.locator("#phone").fill("xxxxxx")# 输入密码page.locator("#pwd").fill("xxxxxx")# 点击登录按钮page.locator(".xxx-button-login").click()exceptExceptionase:if"#pwd"instr(e):# 触发自愈流程:传入正确元素、失败元素ifhealer.heal_element("#phone","#pwd", __file__):pytest.fail("元素定位已自动修复,请重新运行测试")else:pytest.fail("元素定位修复失败 END")raisee
当前阶段遗留问题:
1. 部分页面的html内容获取失败,导致推荐选择器获取失败
2.自愈脚本如何快速运用到全局case中
3.在pytest中,使用的是--reruns或其他重试机制(如pytest-rerunfailures),所以重试时不会自动加载修改后的脚本,因为:
Python 在首次导入模块时会将其编译为字节码(.pyc),并缓存在__pycache__目录中。
重试时,pytest仍会使用内存中已加载的模块(即第一次执行时的脚本版本),不会重新读取磁盘上的修改
| 欢迎光临 链载Ai (https://www.lianzai.com/) | Powered by Discuz! X3.5 |