自动化测试用例之元素自愈:Playwright与pytest的结合使用

发布于:2024-05-07 ⋅ 阅读:(31) ⋅ 点赞:(0)

前言

在自动化测试领域,元素定位的准确性对于测试的成功至关重要。当使用Playwright结合pytest进行测试时,我们可以通过一些策略来增强测试的鲁棒性,特别是在元素定位失败时能够自动进行修复。本文将详细介绍如何实现这一过程。

环境准备

首先,确保你的环境中安装了playwrightpytest。可以通过以下命令安装:

pip install pytest
pip install playwright

自动化修复策略

在测试过程中捕获失败并尝试自动修复问题。我们将使用 Playwright 的 Locator 来定位元素,并在失败时重新定位更新元素。

BasePage类设计

BasePage类封装了页面操作和自动修复的逻辑。

base.py

# -*- coding: utf-8 -*-
# @Author  : blues_C
# @File    : base.py
# @Desc:

import re
from utils.logger import log
from playwright.sync_api import Page, expect, Locator

class BasePage:
    def __init__(self, page: Page):
        self.page = page
        
    def locator(self, selector: str) -> Locator:
        """查找并返回元素"""
        element = self.page.locator(selector)
        if not element.is_visible():
           self.auto_repair()
        return element
        
    def click(self, selector: str):
    	self.locator(selector).click()
    
    def fill(self, selector: str, input_value: str):
    	self.locator(selector).fill(input_value)
        
    def auto_repair(self, scope_selector: str = "body",
                         selector: str = "input, button, a, select, textarea, div, span, img, iframe, label, svg",
                         filename="login_element.py"):
        ### 自动修复逻辑:尝试重新定位页面上的所有相关元素,并更新我们的元素定位文件。### 
	    page_title = self.page.title()
	    scope = self.page.locator(scope_selector)
	    elements = scope.locator(selector).all()
	    log.info(f"自愈元素: {selector} (共 {len(elements)} 个)")
	
	    # 检查文件是否存在
	    if os.path.exists(filename):
	        # 读取现有文件内容,以便检查变量名
	        with open(filename, "r") as file:
	            lines = file.readlines()
	            existing_variables = set()
	
	            # 获取现有的变量名
	            for line in lines:
	                if "=" in line:
	                    variable_name = line.split("=")[0].strip()
	                    existing_variables.add(variable_name)
	
	        # 打开文件,准备写入新内容
	        with open(filename, "w") as file:
	            file.write(f"# Existing variables from {page_title}\n\n")
	
	            for element in elements:
	                element_info = f"element='{element}', "
	                start_index = element_info.find("selector=")
	                if start_index != -1:
	                    selector_str = element_info[start_index + len("selector='"):]
	                    end_index = selector_str.find("'")
	                    if end_index != -1:
	
	                        selector_content = selector_str[:end_index]
	                        # 获取各个属性
	                        text = element.inner_text()
	                        value = element.get_attribute('value')
	                        element_id = element.get_attribute('id')
	                        element_class = element.get_attribute('class')
	                        element_name = element.get_attribute('name')
	                        element_type = element.get_attribute('type')
	                        element_placeholder = element.get_attribute('placeholder')
	
	                        # 检查属性是否为 None,并记录非 None 的属性
	                        log_info = f"element='{selector_content}', "
	                        if text != '':
	                            log_info += f"text='{text}'  "
	                        if value != '' and value is not None:
	                            log_info += f"[value='{value}']  "
	                        if element_id is not None:
	                            log_info += f"#{element_id}  "
	                        if element_class is not None:
	                            log_info += f"[class='{element_class}']  "
	                        if element_name is not None:
	                            log_info += f"[name='{element_name}']  "
	                        if element_type is not None:
	                            log_info += f"[type='{element_type}']  "
	                        if element_placeholder is not None:
	                            log_info += f"[placeholder='{element_placeholder}']"
	
	                        # 打印日志信息
	                        log.info(log_info)
	                        # 如果变量名已存在,替换现有的定义行
	                        if variable_name in existing_variables:
	                            file.write(f"# {log_info} (replaced)\n")
	
	                        if element.get_attribute('name') is not None:
	                            variable_name = element.get_attribute('name')
	                            variable_type = selector_content
	                            file.write(f"{variable_name} = '{variable_type}'\n\n")
	                            existing_variables.add(variable_name)
	                        elif element.get_attribute('type') is not None:
	                            variable_name = element.get_attribute('type')
	                            variable_type = selector_content
	                            file.write(f"{variable_name} = '{variable_type}'\n\n")
	                            existing_variables.add(variable_name)
	                        else:
	                            variable_type = selector_content
	                            file.write(f"'{variable_type}'\n\n")
	                            existing_variables.add(variable_name)
	
	    else:
	        # 如果文件不存在,则创建新文件并写入内容
	        with open(filename, "w") as file:
	            file.write(f"# {page_title}\n")
	
	            for element in elements:
	                element_info = f"element='{element}', "
	                start_index = element_info.find("selector=")
	                if start_index != -1:
	                    selector_str = element_info[start_index + len("selector='"):]
	                    end_index = selector_str.find("'")
	                    if end_index != -1:
	                        selector_content = selector_str[:end_index]
	                        text = element.inner_text()
	                        value = element.get_attribute('value')
	                        element_id = element.get_attribute('id')
	                        element_class = element.get_attribute('class')
	                        element_name = element.get_attribute('name')
	                        element_type = element.get_attribute('type')
	                        element_placeholder = element.get_attribute('placeholder')
	
	                        info = f"element='{selector_content}', "
	                        if text != '':
	                            info += f"text='{text}'  "
	                        if value != '' and value is not None:
	                            info += f"[value='{value}']  "
	                        if element_id is not None:
	                            info += f"#{element_id}  "
	                        if element_class is not None:
	                            info += f"[class='{element_class}']  "
	                        if element_name is not None:
	                            info += f"[name='{element_name}']  "
	                        if element_type is not None:
	                            info += f"[type='{element_type}']  "
	                        if element_placeholder is not None:
	                            info += f"[placeholder='{element_placeholder}']"
	                        log.info(info)
	                        file.write(f"# {info}\n")
	
	                        if element.get_attribute('name') is not None:
	                            variable_name = element.get_attribute('name')
	                            variable_type = selector_content
	                            file.write(f"{variable_name} = '{variable_type}'\n\n")
	                        elif element.get_attribute('type') is not None:
	                            variable_name = element.get_attribute('type')
	                            variable_type = selector_content
	                            file.write(f"{variable_name} = '{variable_type}'\n\n")
	                        else:
	                            variable_type = selector_content
	                            file.write(f"'{variable_type}'\n\n")
    

pytest fixture的使用

conftest.py

import pytest
from playwright.sync_api import sync_playwright

@pytest.fixture(scope='session')
def browser():
    with sync_playwright() as p:
        browser = p.chromium.launch()
        yield browser
        browser.close()

测试用例编写

test_cases.py

import pytest
from common.base import BasePage
from playwright.sync_api import sync_playwright

def test_auto_repair(browser):
    page = browser.new_page()
    page.goto('https://example.com')

    try:
        # 运行测试步骤
        BasePage(page).fill(login_element.username, 'username')
        BasePage(page).fill(login_element.password, 'password')
        BasePage(page).click(login_element.login)

    except Exception as e:
        pytest.fail(f'Test failed: {e}')
        BasePage(page).fill(login_element.username, 'username')
        BasePage(page).fill(login_element.password, 'password')
        BasePage(page).click(login_element.login)

元素定位文件

login_element.py 示例:

# 登录到 standard
# element='form >> input,button >> nth=0', #username  [class='form-control']  [name='username']  [type='text']  
username = 'form >> input,button >> nth=0'

# element='form >> input,button >> nth=1', #password  [class='form-control']  [name='password']  [type='password']  
password = 'form >> input,button >> nth=1'

# element='form >> input,button >> nth=2', #id-hidden-input  [name='credentialId']  [type='hidden']  
credentialId = 'form >> input,button >> nth=2'

# element='form >> input,button >> nth=3', [value='登录']  #kc-login  [class='btn btn-primary btn-block btn-lg']  [name='login']  [type='submit']  
login = 'form >> input,button >> nth=3'

总结

本文展示了如何使用Playwright的Locator结合pytest的自动化测试框架来实现元素的自动定位和修复。通过封装页面操作和自动修复逻辑到BasePage类中,我们可以提高测试的稳定性和可维护性。同时,使用pytest的fixture来管理浏览器的生命周期,使得测试更加简洁。

通过上述方法,我们能够确保即使在面对复杂的页面元素变化时,我们的自动化测试也能够适应并成功执行,从而提高测试的覆盖率和准确性。