在编写UI自动化测试用例的时候,通常会采用每个测试用例前打开新页面重新进行登录,以减少用例间的影响,比如一个测试用例执行失败会影响到下一个测试用例的执行,或者下一个用例的开始依赖于上一个用例的结束页面。但是这种方式会使得测试用例的执行时间大幅度上升,尤其是在测试用例划分的颗粒度比较小的时候;加入一个项目中有2000个测试用例,登录操作耗时2秒,那么光耗费在登录上面的时间就有4000秒,达到一个多小时了,严重影响测试执行效率,再假如项目中使用了验证码限制登录的情况,那么就更复杂了。所以,如果可以将登录状态保持住,开始测试用例的时候打开新页面,跳过登录直接进入到待测系统中,就可以大幅度提高测试执行效率。通常,每种UI自动化测试工具都会有类似的功能,这里以Playwright为例来介绍如何实现。
在Playwright中提供了现成的方法,通过 context.storage_state(path='<文件路径>')
,可以将当前浏览器上下文的全部状态保存下来,在创建浏览器上下文时,添加 storage_state
参数即可读取保存的文件,从而完全恢复之前的浏览器状态。示例代码如下
# 首次登陆系统
from playwright.sync_api import sync_playwright
with sync_playwright() as playwright:
browser = playwright.chromium.launch()
context = browser.new_context()
page = context.new_page()
# 登陆系统
page.goto('' )
page.fill('' , '' )
page.fill('' , '' )
page.click('' )
# 判断是否登陆成功
assert 'Welcome' in page.title()
# 保存状态文件
context.storage_state(path='login_data.json')
# 使用已保存的状态文件跳过登录状态直接访问系统
with sync_playwright() as playwright:
browser = playwright.chromium.launch()
# 创建浏览器上下文时加载状态文件
context = browser.new_context(storage_state='login_data.json')
page = context.new_page()
# 直接访问登录后的URL
page.goto('' )
# 判断是否访问到登录后的页面
assert 'Welcome' in page.title()
通过以上代码验证了这种实现方式的可用性,还是很好用的。结合Pytest测试框架,可以通过fixture的形式提供一个已登录的page对象,可以直接在测试用例中使用,实现方式如下:
@pytest.fixture()
def logged_page():
ss_file = 'login_data.json'
with sync_playwright() as playwright:
browser = playwright.chromium.launch()
# 判断是否存在状态文件,有的话就加载
if os.path.isfile(ss_file):
context = browser.new_context(storage_state=ss_file)
else:
context = browser.new_context()
page = context.new_page()
# 直接跳转至登录后页面,前提是未登录用户访问待测系统会自动跳转至登录页面,如果你的系统逻辑不一样,需要修改
page.goto('' )
# 通过title判断是否成功进入系统,如果没有需要进行登录,一般在第一次访问系统,或登录信息过期等原因会触发
if 'Welcome' not in page.title():
page.fill('' , '' )
page.fill('' , '' )
page.click('' )
yield page
# 测试执行结束后保存状态文件,前提是测试用例中不能退出系统,安全起见加上异常处理
try:
context.storage_state(path=ss_file)
except Exception as e:
print(e)
如前,结合Pytest已经可以实现一个很好用的fixture,在很多场景下已经够用了,不过我在项目中结合Clent-Page Object模式进行了另一种实现,核心代码如下:
class Client(ABC):
playwright = None
browser = None
def __init__(self, url: str, *, storage_state_name: str = None):
self.url = url
self.context = None
self.main_page = None
self.storage_state_file_path = None
if storage_state_name is not None:
self.storage_state_file_path = os.path.join('storage_state', f'{storage_state_name}.json')
@abstractmethod
def register_page(self):
pass
@abstractmethod
def login(self, **kwargs):
pass
@abstractmethod
def is_logged(self):
pass
def start(self) -> None:
Client.playwright = sync_playwright().start()
Client.browser = Client.playwright.chromium.launch(**BROWSER_CONFIG)
if self.storage_state_file_path is not None and os.path.isfile(self.storage_state_file_path):
self.context = Client.browser.new_context(**CONTEXT_CONFIG, storage_state=self.storage_state_file_path)
else:
self.context = Client.browser.new_context(**CONTEXT_CONFIG)
self.main_page = self.context.new_page()
self.main_page.goto(self.url)
self.register_page()
def close(self) -> None:
if self.storage_state_file_path is not None:
if not os.path.exists('storage_state'):
os.mkdir('storage_state')
self.context.storage_state(path=self.storage_state_file_path)
self.main_page.close()
self.context.close()
def logged_user(client, url, *, scope = 'function', storage_state_name = None, **kwargs):
@pytest.fixture(scope=scope)
def user_fixture():
user = client(url, storage_state_name=storage_state_name)
user.start()
logger.info(f'用户登录: {kwargs}')
if not user.is_logged():
user.login(**kwargs)
yield user
user.close()
继承Client类,使用 logger_user
函数来生成不同终端用户的fixture,以实现继承Pytest。