【技术干货】UI 和 API 自动化测试神器 - Playwright

admin 2023年2月23日01:36:21评论58 views字数 20691阅读68分58秒阅读模式

【技术干货】UI 和 API 自动化测试神器 - Playwright

【技术干货】UI 和 API 自动化测试神器 - Playwright
【技术干货】UI 和 API 自动化测试神器 - Playwright
Tim
@PortalLab实验室

简介

Playwright 是微软开源的端到端(end-to-end)测试框架,可用于现代 Web 应用。Playwright 提供如下特性:

1. 任意浏览器、任意平台、一种 API

  • 跨浏览器:Playwright 支持所有现代渲染引擎,包括 Chromium、WebKit 和 Firefox。

  • 跨平台:在 Windows、Linux 和 macOS 上,进行本地或 CI 测试(无头或有头)。

  • 跨语言:可在 TypeScript、JavaScript、Python、.NET、Java 中使用 Playwright API。

  • 测试移动 Web:Android Google Chrome 和移动 Safari 的本地移动仿真。桌面和云上运行的渲染引擎相同。

2. 弹性、没有古怪的测试

  • 自动等待:Playwright 在执行操作前,将等待到元素可被操作。它还有一组丰富的检查事件。两者结合可消除对人为超时的需求 — 这是导致古怪测试的主要原因。

  • Web 优先断言:Playwright 断言是专门为动态 Web 创建的。检查将自动重试,直到满足必要的条件。

  • 追踪:配置测试重试策略,捕获执行踪迹,录像,截屏,以防止遗忘。

3. 无需折中、无限制:浏览器在不同进程中运行属于不同源的 Web 内容。Playwright 与现代浏览器架构保持一致,在进程外运行测试。这使得 Playwright 摆脱典型的进程内测试运行器限制。
  • 复合一切:横跨多个选项卡、多个源和多个用户的测试场景。为不同用户创建具有不同上下文的场景,并且在服务器上运行它们,这些都在一个测试中进行。

  • 可信事件:悬停元素,与动态控件交互,生成可信事件。Playwright 使用真正的浏览器输入管道,与真正的用户没有区别。

  • 测试 Frame、穿透 Shadow DOM:Playwright 选择器穿透 Shadow DOM,允许无缝进入 Frame。

4. 完全隔离、快速执行:
  • 浏览器上下文:Playwright 为每个测试创建一个浏览器上下文。浏览器上下文等同于全新的浏览器配置文件。它提供零开销的完全测试隔离。创建新浏览器上下文仅需几毫秒。

  • 登录一次:保存上下文的身份认证状态,并且在所有测试中重用。避免在每个测试中重复登录,还提供独立测试的完全隔离。

5. 强大的工具:
  • 代码生成:通过录制操作生成测试。将它们保存成任何语言。

  • Playwright 检查器:检查页面,生成选择器,逐步完成测试执行,查看单击点,探索执行日志。

  • 追踪查看器:捕获所有信息以调查测试失败。Playwright 追踪包含测试执行录屏、实时 DOM 快照、操作资源管理器、测试源等。

本文测试环境

  • 操作系统:macOS 12.6

  • Python:3.10.6(下文以 Python 为例,进行讲述)

  • Playwright:1.30.0

安装


创建测试环境

mkdir playwright-democd playwright-demo/python3 -m venv venv# 安装 Pytest 插件venv/bin/pip3 install pytest-playwright# 安装需要的浏览器venv/bin/playwright install

添加样例测试

在当前工作目录或子目录内部,创建 test_my_application.py 文件,其内容如下:

import refrom playwright.sync_api import Page, expect

def test_homepage_has_Playwright_in_title_and_get_started_link_linking_to_the_intro_page(page: Page): page.goto("https://playwright.dev/")
# Expect a title "to contain" a substring. expect(page).to_have_title(re.compile("Playwright"))
# create a locator get_started = page.locator("text=Get Started")
# Expect an attribute "to be strictly equal" to the value. expect(get_started).to_have_attribute("href", "/docs/intro")
# Click the get started link. get_started.click()
# Expects the URL to contain intro. expect(page).to_have_url(re.compile(".*intro"))

运行样例测试

在默认情况下,测试运行在 Chromium 上,可通过 CLI 选项进行配置,测试以 Headless 模式运行。测试结果和测试日志被展示在终端中。

venv/bin/pytest

编写测试

Playwright 断言(assertion)是专门为动态网页创建的。检查将自动重试,直到满足必要的条件。Playwright 自带 auto-wait,这意味着它在执行操作前,等待到元素变为可操作的(actionable)。Playwright 提供 expect 函数来编写断言。

下面是展示如何编写使用断言、定位器(locator)和选择器(selector)的测试的示例。

import refrom playwright.sync_api import Page, expect

def test_homepage_has_Playwright_in_title_and_get_started_link_linking_to_the_intro_page(page: Page): page.goto("https://playwright.dev/")
# Expect a title "to contain" a substring. expect(page).to_have_title(re.compile("Playwright"))
# create a locator get_started = page.locator("text=Get Started")
# Expect an attribute "to be strictly equal" to the value. expect(get_started).to_have_attribute("href", "/docs/intro")
# Click the get started link. get_started.click()
# Expects the URL to contain intro. expect(page).to_have_url(re.compile(".*intro"))

断言

Playwright 提供 expect (https://playwright.dev/python/docs/test-assertions)函数,它将一直等待,直到满足预期条件。

import refrom playwright.sync_api import expect
expect(page).to_have_title(re.compile("Playwright"))

定位器

定位器(Locator)是 Playwright 的自动等待和重试能力的核心部分。定位器是一种随时在网页上查找元素的方法,用于在元素上执行诸如 .click、.fill 之类的操作。可以使用 page.locator(selector, **kwargs) 方法创建自定义定位器。
from playwright.sync_api import expect
get_started = page.locator("text=Get Started")
expect(get_started).to_have_attribute("href", "/docs/installation")get_started.click()

选择器(Selector)是用于创建定位器的字符串。Playwright 支持许多不同的选择器,比如 Text、CSS、XPath 等。阅读 in-depth guide 文档,了解更多关于可用的选择器以及如何进行选择的信息。

from playwright.sync_api import expect
expect(page.locator("text=Installation")).to_be_visible()

测试隔离

Playwright Pytest 插件基于 test fixture(比如 built in page fixture)的概念,它将被传给你的测试。由于浏览器上下文,在测试之间,页面(Page)彼此隔离,这相当于开启新的浏览器行为,每个测试获得新环境,即使在一个浏览器中运行多个测试时,也是如此。
from playwright.sync_api import Page
def test_basic_test(page: Page): # ...

使用测试钩子

可以使用各种各样的 fixture 在测试之前或之后执行代码,以及在它们之间共享对象。函数(function)作用域的 fixture 具有 beforeEach/afterEach 一样的自动使用行为。模块(module)作用域的 fixture 具有 beforeAll/afterAll 一样的自动使用行为,它在所有测试之前和所有测试之后运行。
import pytestfrom playwright.sync_api import Page, expect

@pytest.fixture(scope="function", autouse=True)def before_each_after_each(page: Page): print("beforeEach") # Go to the starting url before each test. page.goto("https://playwright.dev/") yield print("afterEach")
def test_main_navigation(page: Page): # Assertions use the expect API. expect(page).to_have_url("https://playwright.dev/")

运行测试

可以运行单个测试、一组测试或全部测试。测试可以运行在一种或多种浏览器上。默认情况下,以 Headless 方式运行测试,这意味着在运行测试时,不会打开浏览器窗口,可以在终端中看到结果。通过使用 --headed 标记,可以以 headed 模式运行测试。
  • 在 Chromium 上运行测试

pytest
  • 运行单个测试文件
pytest test_login.py
  • 运行一组测试文件

pytest tests/todo-page/ tests/landing-page/
  • 使用函数名运行测试
pytest -k "test_add_a_todo_item"
  • 以有头(headed)模式运行测试
pytest --headed test_login.py
  • 在指定的浏览器上运行测试
pytest test_login.py --browser webkit
  • 在多种浏览器上运行测试
pytest test_login.py --browser webkit --browser firefox
  • 并行运行测试

pytest --numprocesses auto

【假定已安装 pytest-xdist,查看 here (https://playwright.dev/python/docs/test-runners#parallelism-running-multiple-tests-at-once)获取更多信息。】


运行测试

因为 Playwright 运行在 Python 中,所以可以使用 Debugger 调试它。Playwright 自带 Playwright Inspector,它允许你逐步通过 Playwright API 调用,查看它们的调试日志,以及探索选择器(selectors)。
PWDEBUG=1 pytest -s
【技术干货】UI 和 API 自动化测试神器 - Playwright

查看调试指南(debugging guide)了解关于 Playwright Inspector 以及使用浏览器开发者工具(Browser Developer tools)进行调试的更多信息。

测试生成器

Playwright 具有开箱即用的生成测试的能力,这是快速开始测试的好方法。它会打开两个窗口,一个是浏览器窗口,通过它你可以与希望测试的网站进行交互,另一个是 Playwright Inspector 窗口,通过它你可以录制测试、拷贝测试、清除测试以及改变测试的语言。
你将学习:
  • How to generate tests with Codegen

运行 Codegen

playwright codegen playwright.dev
运行 codegen,然后在浏览器中执行操作。Playwright 将为用户的交互生成代码。Codegen 尝试生成弹性的基于文本的选择器。

【技术干货】UI 和 API 自动化测试神器 - Playwright

当你完成与页面的交互时,按下 record 按钮停止录制,使用 copy 按钮把生成的代码拷贝到编辑器。
【技术干货】UI 和 API 自动化测试神器 - Playwright

使用 clear 按钮清除代码,重新开始录制。完成时,关闭 Playwright Inspector 窗口,或停止终端命令。

如欲了解有关生成测试的更多信息,请查看 Codegen 的详细指南。

追踪查看器(Trace Viewer)

Playwright 追踪查看器是一个 GUI 工具,它使你可以探查测试中记录的 Playwright 追踪,可以在测试的每个操作中来回移动,可视化地查看每个操作期间正在发生什么。

你将学习:

  • 如何记录追踪

  • 如何打开 HTML 报告

  • 如何打开追踪查看器


记录追踪

像下面一样使用 browser_context.tracing API 记录追踪:

browser = chromium.launch()context = browser.new_context()
# Start tracing before creating / navigating a page.context.tracing.start(screenshots=True, snapshots=True, sources=True)
page.goto("https://playwright.dev")
# Stop tracing and export it into a zip archive.context.tracing.stop(path = "trace.zip")

这将记录追踪,把它导出到名称为 trace.zip 的文件中。


打开追踪

可以使用 Playwright CLI 打开保存的追踪。
playwright show-trace trace.zip

查看追踪

通过单击每个操作或使用时间轴悬停,查看测试的追踪,以及查看操作前后的页面状态。在测试的每个步骤期间查看日志、源和网络。追踪查看器创建 DOM 快照,因此你可以与它进行交互,打开开发者工具(devtools)等。
【技术干货】UI 和 API 自动化测试神器 - Playwright
如欲了解更多信息,请查看 Trace Viewer 的详细指南。

Pytest 插件参考

Playwright 提供 Pytest 插件,来编写端到端的测试。如果想开始使用它,请参考 getting started guide


用法

使用 Pytest(https://docs.pytest.org/en/stable/) CLI 运行测试:

pytest --browser webkit --headed

如果你想自动地添加 CLI 参数,而不是指定它们,请使用 pytest.ini 文件。


CLI 参数

  • --headed:以有头模式运行测试(默认:无头)
  • --browser:用不同的浏览器 chromium、firefox、webkit 运行测试。可以指定多次(默认:所有浏览器)
  • --browser-channel:使用的 Browser channel
  • --slow-mo:使用慢动作运行测试
  • --device:要模拟的设备(Device)
  • --output:用于测试生成的制品(aritifact)的目录(默认:test-results)
  • --tracing:是否为每次测试记录追踪(trace:https://playwright.dev/python/docs/trace-viewer)。on、off 或 retain-on-failure(默认:off)
  • --video:是否为每次测试录制视频。on、off 或 retain-on-failure(默认:off)
  • --screenshot:是否在每次测试后,自动地捕获截图。on, off, or only-on-failure (默认:off)



Fixture

该插件为 pytest 配置 Playwright 特定的 fixture。为使用这些 fixture,使用 fixture 名称作为测试函数的参数。

def test_my_app_is_working(fixture_name):    # Test using fixture_name    # ...

函数作用域:这些 fixture 在测试函数请求时创建,在测试结束时销毁。

  • context:用于测试的新浏览器上下文(browser context)

  • page:用于测试的新浏览器页面(browser page)

会话作用域:这些 fixture 在测试函数请求时创建,在测试结束时销毁。

  • playwright:Playwright 实例

  • browser_type:当前浏览器的 BrowserType 实例

  • browser:Playwright 启动的 Browser 实例

  • browser_name:浏览器名称

  • browser_channel:浏览器通道(channel)

  • is_chromium、is_webkit、is_firefox:各自浏览器类型的布尔值

自定义 fixture 选项:对于 browser 和 context fixture,使用下面的 fixture 来定义自定义启动选项。

  • browser_type_launch_args:重写用于 browser_type.launch(**kwargs) 的启动参数。它应该返回字典

  • browser_context_args:重写用于 browser.new_context(**kwargs) 的选项。它应该返回字典


并行:同时运行多个测试

如果测试运行在有多个 CPU 的机器上,可以通过使用 pytest-xdist 同时运行多个测试,加快测试套件的整体执行时间。

# install dependencypip install pytest-xdist# use the --numprocesses flagpytest --numprocesses auto

根据硬件和测试的特性,可以将 numprocesses 设置为 2 到机器上 CPU 数量之间的任意值。如果设置得过高,可能产生非预期行为。

有关 pytest 选项的常用信息,请参考 Running Tests。

示例

配置 Mypy 类型以自动补全

# test_my_application.pyfrom playwright.sync_api import Page
def test_visit_admin_dashboard(page: Page): page.goto("/admin") # ...

配置慢动作

使用 --slowmo 参数以慢动作运行测试。

pytest --slowmo 100

通过浏览器跳过测试

# test_my_application.pyimport pytest
@pytest.mark.skip_browser("firefox")def test_visit_example(page): page.goto("https://example.com") # ...

在特定的浏览器上运行测试

# conftest.pyimport pytest
@pytest.mark.only_browser("chromium")def test_visit_example(page): page.goto("https://example.com") # ...

使用自定义的浏览器通道运行

pytest --browser-channel chrome
# test_my_application.pydef test_example(page):    page.goto("https://example.com")

配置 base-url

使用 base-url 参数启动 Pytest。pytest-base-url (https://github.com/pytest-dev/pytest-base-url)插件允许通过配置、CLI 参数或像 fixture 一样设置 base url。

pytest --base-url http://localhost:8080
# test_my_application.pydef test_visit_example(page):    page.goto("/admin")    # -> Will result in http://localhost:8080/admin

忽略 HTTPS 错误

# conftest.pyimport pytest
@pytest.fixture(scope="session")def browser_context_args(browser_context_args): return { **browser_context_args, "ignore_https_errors": True }

使用自定义窗口大小

# conftest.pyimport pytest
@pytest.fixture(scope="session")def browser_context_args(browser_context_args): return { **browser_context_args, "viewport": { "width": 1920, "height": 1080, } }
设备仿真
# conftest.pyimport pytest
@pytest.fixture(scope="session")def browser_context_args(browser_context_args, playwright): iphone_11 = playwright.devices['iPhone 11 Pro'] return { **browser_context_args, **iphone_11, }

或通过命令行 --device="iPhone 11 Pro"。

持久化上下文

# conftest.pyimport pytestfrom playwright.sync_api import BrowserTypefrom typing import Dict
@pytest.fixture(scope="session")def context( browser_type: BrowserType, browser_type_launch_args: Dict, browser_context_args: Dict): context = browser_type.launch_persistent_context("./foobar", **{ **browser_type_launch_args, **browser_context_args, "locale": "de-DE", }) yield context context.close()

从持久化上下文创建测试内部的所有页面。

与 unittest.TestCase 一起使用

参考下面的示例,了解如何与 unittest.TestCase 一起使用。这有一个限制,仅能指定一个浏览器,并且在指定多个浏览器时,不会生成多个浏览器的矩阵。

import pytestimport unittest
from playwright.sync_api import Page

class MyTest(unittest.TestCase): @pytest.fixture(autouse=True) def setup(self, page: Page): self.page = page
def test_foobar(self): self.page.goto("https://microsoft.com") self.page.locator("#foobar").click()        assert self.page.evaluate("1 + 1") == 2

调试

使用 pdb

在代码中使用 breakpoint() 语句停止执行,获取 pdb REPL。

def test_bing_is_working(page):    page.goto("https://bing.com")    breakpoint()    # ...

部署到 CI

请查看 guides for CI providers 获取关于将测试部署到 CI/CD 的信息。

认证

Playwright 可用于需要认证的自动化场景。

使用 Playwright 编写的测试在被称为浏览器上下文(browser contexts的、独立的、干净的环境中执行。这种隔离模型可以提升复现性,防止级联测试失败。新浏览器上下文可以加载现有的认证状态。这可以消除在每个上下文中登录的需求,加快测试执行的速度。
注意:本指南覆盖 cookie/token-based 认证(通过 app UI 登陆)。对于 HTTP 认证(HTTP authentication:https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication),请使用 browser.new_context(**kwargs):https://playwright.dev/python/docs/api/class-browser#browser-new-context。

自动化登录

Playwright API 可以与登陆表单自动化交互(automate interaction)。
下面的示例自动化登陆到 Github。执行这些步骤后,浏览器上下文将被认证。
page = context.new_page()page.goto('https://github.com/login')
# Interact with login formpage.get_by_text("Login").click()page.get_by_label("User Name").fill(USERNAME)page.get_by_label("Password").fill(PASSWORD)page.get_by_text('Submit').click()# Continue with the test

为每次测试重做登录将减慢测试的执行速度。为缓解这种情况,应该重用现有的认证状态。


重用签入状态

Playwright 提供在测试中重用签入(signed-in)状态的方式。通过该方式,可以只登陆一次,然后跳过所有测试的登陆步骤。
Web 应用使用基于 Cookie 或基于 Token 的认证,认证状态被当作 Cookie 存储,或被存储在 Local Storage 中。Playwright 提供 browserContext.storageState(options) 方法,可使用它从已认证上下文中获取存储状态,然后使用预填充状态创建新上下文。
Cookie 和 Local Storage 状态可以跨不同的浏览器使用。它们依赖应用程序的认证模型:有些应用程序可能同时需要 Cookie 和 Local Storage。
下面的代码片段从已认证上下文中获取状态,然后使用该状态创建新上下文。
# Save storage state into the file.storage = context.storage_state(path="state.json")
# Create a new context with the saved storage state.context = browser.new_context(storage_state="state.json")

Session Storage

Session Storage 很少用于存储与登陆状态相关的信息。Session Storage 特定于特定的域,页面加载时它不会持久化。Playwright 未提供持久化 Session Storage 的 API,但下面的片段可用于保存/加载 Session Storage。

import os# Get session storage and store as env variablesession_storage = page.evaluate("() => JSON.stringify(sessionStorage)")os.environ["SESSION_STORAGE"] = session_storage
# Set session storage in a new contextsession_storage = os.environ["SESSION_STORAGE"]context.add_init_script("""(storage => { if (window.location.hostname === 'example.com') { const entries = JSON.parse(storage) for (const [key, value] of Object.entries(entries)) { window.sessionStorage.setItem(key, value) } }})('""" + session_storage + "')")

多因子认证

使用多因子认证(MFA)的账户无法完全自动化,需要人工干预。持久化认证可用于部分自动化 MFA 场景。

持久化认证
注意:持久化认证不适用于 CI 环境,因为它依赖磁盘位置。用户数据目录特定于浏览器类型,不能跨浏览器类型共享。
用户数据目录可以与 browser_type.launch_persistent_context(user_data_dir, **kwargs) API 一起使用。
from playwright.sync_api import sync_playwright
with sync_playwright() as p: user_data_dir = '/path/to/directory' browser = p.chromium.launch_persistent_context(user_data_dir, headless=False) # Execute login steps manually in the browser window
生命周期
1. 在磁盘上创建用户数据目录
2. 使用用户数据目录启动持久化上下文,然后登陆 MFA 账户

3. 重用用户数据目录来运行自动化场景

事件

Playwright 允许监听发生在 Web 页面上的多种类型的事件,比如网络请求、子页面的创建、专用 Worker 等。可通过多种方式订阅这些事件,比如等待事件或添加/移除事件监听者。


等待事件

大多数时间,脚本需要等待特定的事件发生。下面是一些典型的事件等待模式。
使用 page.expect_request(url_or_predicate, **kwargs) 等待拥有指定 URL 的请求:
with page.expect_request("**/*logo*.png") as first:  page.goto("https://wikipedia.org")print(first.value.url)

等待弹出窗口:

with page.expect_popup() as popup:  page.evaluate("window.open()")popup.value.goto("https://wikipedia.org")

添加/移除事件监听者

有时,事件发生在随机时间,而不是等待它们,它们需要被处理。Playwright 支持订阅/取消订阅事件的传统语言机制:

def print_request_sent(request):  print("Request sent: " + request.url)
def print_request_finished(request): print("Request finished: " + request.url)
page.on("request", print_request_sent)page.on("requestfinished", print_request_finished)page.goto("https://wikipedia.org")
page.remove_listener("requestfinished", print_request_finished)page.goto("https://www.openstreetmap.org/")

添加一次性监听者

如果特定事件需要被处理一次,那么可以使用便捷的 API:

page.once("dialog", lambda dialog: dialog.accept("2021"))page.evaluate("prompt('Enter a number:')")

API 测试

Playwright 可用于访问应用程序的 REST API。
有时,你可能想通过 Python 直接向服务端发送请求,而非加载页面,在其中运行 js 代码。此时,Playwright 可以派上用场的一些示例如下:
  • 测试服务端 API
  • 在访问 Web 应用程序之前,准备服务端状态
  • 在浏览器中运行一些操作后,验证服务端的后置条件
这些都可以通过 APIRequestContext 方法实现。
下面的示例依赖 pytest-playwright 包,它向 Pytest test-runner 添加 Playwright fixture。
  • Writing API Test
  • Prepare server state via API calls
  • Check the server state after running user actions
  • Reuse authentication state

编写 API 测试

APIRequestContext 可以通过网络发送各种 HTTP(S) 请求。
下面的示例展示如何使用 Playwright 通过 GitHub API 测试 issue 创建。测试套件做如下事情:
  • 在运行测试之前,创建新仓库
  • 创建一些 issue 及验证服务端状态
  • 在运行测试后删除仓库
配置

Github API 需要认证,因此我们将为所有测试配置一次 token。同时,我们也将设置 baseURL 以简化测试。

import osfrom typing import Generator
import pytestfrom playwright.sync_api import Playwright, APIRequestContext
GITHUB_API_TOKEN = os.getenv("GITHUB_API_TOKEN")assert GITHUB_API_TOKEN, "GITHUB_API_TOKEN is not set"

@pytest.fixture(scope="session")def api_request_context( playwright: Playwright,) -> Generator[APIRequestContext, None, None]: headers = { # We set this header per GitHub guidelines. "Accept": "application/vnd.github.v3+json", # Add authorization token to all requests. # Assuming personal access token available in the environment. "Authorization": f"token {GITHUB_API_TOKEN}", } request_context = playwright.request.new_context( base_url="https://api.github.com", extra_http_headers=headers ) yield request_context request_context.dispose()
编写测试

现在我们已初始化请求对象,我们可以添加一些在仓库中创建新 issue 的测试。

import osfrom typing import Generator
import pytestfrom playwright.sync_api import Playwright, APIRequestContext
GITHUB_API_TOKEN = os.getenv("GITHUB_API_TOKEN")assert GITHUB_API_TOKEN, "GITHUB_API_TOKEN is not set"
GITHUB_USER = os.getenv("GITHUB_USER")assert GITHUB_USER, "GITHUB_USER is not set"
GITHUB_REPO = "test"
# ...
def test_should_create_bug_report(api_request_context: APIRequestContext) -> None: data = { "title": "[Bug] report 1", "body": "Bug description", } new_issue = api_request_context.post(f"/repos/{GITHUB_USER}/{GITHUB_REPO}/issues", data=data) assert new_issue.ok
issues = api_request_context.get(f"/repos/{GITHUB_USER}/{GITHUB_REPO}/issues") assert issues.ok issues_response = issues.json() issue = list(filter(lambda issue: issue["title"] == "[Bug] report 1", issues_response))[0] assert issue assert issue["body"] == "Bug description"
def test_should_create_feature_request(api_request_context: APIRequestContext) -> None: data = { "title": "[Feature] request 1", "body": "Feature description", } new_issue = api_request_context.post(f"/repos/{GITHUB_USER}/{GITHUB_REPO}/issues", data=data) assert new_issue.ok
issues = api_request_context.get(f"/repos/{GITHUB_USER}/{GITHUB_REPO}/issues") assert issues.ok issues_response = issues.json() issue = list(filter(lambda issue: issue["title"] == "[Feature] request 1", issues_response))[0] assert issue assert issue["body"] == "Feature description"
准备(setup)和销毁(teardown)
这些测试假定仓库存在。你可能想在运行测试之前创建新仓库,之后删除它。为此使用 session fixture。yield 之前的部分在所有测试之前执行,之后的部分在所有测试之后执行。
# ...@pytest.fixture(scope="session", autouse=True)def create_test_repository(    api_request_context: APIRequestContext,) -> Generator[None, None, None]:    # Before all    new_repo = api_request_context.post("/user/repos", data={"name": GITHUB_REPO})    assert new_repo.ok    yield    # After all    deleted_repo = api_request_context.delete(f"/repos/{GITHUB_USER}/{GITHUB_REPO}")    assert deleted_repo.ok
完成样例测试

这是 API 测试的完整样例:

from enum import autoimport osfrom typing import Generator
import pytestfrom playwright.sync_api import Playwright, Page, APIRequestContext, expect
GITHUB_API_TOKEN = os.getenv("GITHUB_API_TOKEN")assert GITHUB_API_TOKEN, "GITHUB_API_TOKEN is not set"
GITHUB_USER = os.getenv("GITHUB_USER")assert GITHUB_USER, "GITHUB_USER is not set"
GITHUB_REPO = "test"

@pytest.fixture(scope="session")def api_request_context( playwright: Playwright,) -> Generator[APIRequestContext, None, None]: headers = { # We set this header per GitHub guidelines. "Accept": "application/vnd.github.v3+json", # Add authorization token to all requests. # Assuming personal access token available in the environment. "Authorization": f"token {GITHUB_API_TOKEN}", } request_context = playwright.request.new_context( base_url="https://api.github.com", extra_http_headers=headers ) yield request_context request_context.dispose()

@pytest.fixture(scope="session", autouse=True)def create_test_repository( api_request_context: APIRequestContext,) -> Generator[None, None, None]: # Before all new_repo = api_request_context.post("/user/repos", data={"name": GITHUB_REPO}) assert new_repo.ok yield # After all deleted_repo = api_request_context.delete(f"/repos/{GITHUB_USER}/{GITHUB_REPO}") assert deleted_repo.ok

def test_should_create_bug_report(api_request_context: APIRequestContext) -> None: data = { "title": "[Bug] report 1", "body": "Bug description", } new_issue = api_request_context.post( f"/repos/{GITHUB_USER}/{GITHUB_REPO}/issues", data=data ) assert new_issue.ok
issues = api_request_context.get(f"/repos/{GITHUB_USER}/{GITHUB_REPO}/issues") assert issues.ok issues_response = issues.json() issue = list( filter(lambda issue: issue["title"] == "[Bug] report 1", issues_response) )[0] assert issue assert issue["body"] == "Bug description"

def test_should_create_feature_request(api_request_context: APIRequestContext) -> None: data = { "title": "[Feature] request 1", "body": "Feature description", } new_issue = api_request_context.post( f"/repos/{GITHUB_USER}/{GITHUB_REPO}/issues", data=data ) assert new_issue.ok
issues = api_request_context.get(f"/repos/{GITHUB_USER}/{GITHUB_REPO}/issues") assert issues.ok issues_response = issues.json() issue = list( filter(lambda issue: issue["title"] == "[Feature] request 1", issues_response) )[0] assert issue assert issue["body"] == "Feature description"

通过 API 调用准备服务端状态

下面的测试通过 API 创建新 issue,然后导航到项目中所有 issue 的列表,检查它是否出现在列表的顶部。使用 LocatorAssertions 执行该检查。

def test_last_created_issue_should_be_first_in_the_list(api_request_context: APIRequestContext, page: Page) -> None:    def create_issue(title: str) -> None:        data = {            "title": title,            "body": "Feature description",        }        new_issue = api_request_context.post(            f"/repos/{GITHUB_USER}/{GITHUB_REPO}/issues", data=data        )        assert new_issue.ok    create_issue("[Feature] request 1")    create_issue("[Feature] request 2")    page.goto(f"https://github.com/{GITHUB_USER}/{GITHUB_REPO}/issues")    first_issue = page.locator("a[data-hovercard-type='issue']").first    expect(first_issue).to_have_text("[Feature] request 2")

在运行用户操作后,检查服务器状态

下面的测试通过浏览器中的用户接口创建新 issue,然后通过 API 检查它是否被创建。

def test_last_created_issue_should_be_on_the_server(api_request_context: APIRequestContext, page: Page) -> None:    page.goto(f"https://github.com/{GITHUB_USER}/{GITHUB_REPO}/issues")    page.locator("text=New issue").click()    page.locator("[aria-label='Title']").fill("Bug report 1")    page.locator("[aria-label='Comment body']").fill("Bug description")    page.locator("text=Submit new issue").click()    issue_id = page.url.split("/")[-1]
new_issue = api_request_context.get(f"https://github.com/{GITHUB_USER}/{GITHUB_REPO}/issues/{issue_id}") assert new_issue.ok assert new_issue.json()["title"] == "[Bug] report 1" assert new_issue.json()["body"] == "Bug description"

重用认证状态

Web 应用程序使用基于 Cookie 或基于 Token 的认证,认证状态被存储为 Cookies。Playwright 提供 api_request_context.storage_state(**kwargs) 方法,它可用于从已认证上下文中获取存储状态,然后使用该状态创建新上下文。
BrowserContextAPIRequestContext 之间可交换存储状态。你可以通过 API 调用登录,然后使用已有的 Cookie 创建新上下文。下面的代码片段从已认证 APIRequestContext 获取状态,然后使用该状态创建新浏览器上下文(BrowserContext)。
request_context = playwright.request.new_context(http_credentials={"username": "test", "password": "test"})request_context.get("https://api.example.com/login")# Save storage state into a variable.state = request_context.storage_state()
# Create a new context with the saved storage state.context = browser.new_context(storage_state=state)


关于Portal Lab

星阑科技 Portal Lab 致力于前沿安全技术研究及能力工具化。主要研究方向为API 安全、应用安全、攻防对抗等领域。实验室成员研究成果曾发表于BlackHat、HITB、BlueHat、KCon、XCon等国内外知名安全会议,并多次发布开源安全工具。未来,Portal Lab将继续以开放创新的态度积极投入各类安全技术研究,持续为安全社区及企业级客户提供高质量技术输出。


往期 · 推荐



【技术干货】UI 和 API 自动化测试神器 - Playwright

【技术干货】UI 和 API 自动化测试神器 - Playwright

【技术干货】UI 和 API 自动化测试神器 - Playwright

【技术干货】UI 和 API 自动化测试神器 - Playwright

【技术干货】UI 和 API 自动化测试神器 - Playwright

原文始发于微信公众号(星阑科技):【技术干货】UI 和 API 自动化测试神器 - Playwright

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年2月23日01:36:21
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【技术干货】UI 和 API 自动化测试神器 - Playwrighthttps://cn-sec.com/archives/1564223.html

发表评论

匿名网友 填写信息