Playwright 使用指南,Playwright 入门介绍 请参考另一篇博客
此博客为官方文档译文 希望读者可以快速了解 Playwriht 可以用来做什么,怎么用。有些专业名词可能翻译不准确哈
文章目录
- Playwrigh 使用指南-1
- 1 Auto-waiting 自动等待
- 2 API testing API 测试
- 2.1 Writing API Test 编写 API测试
- 2.1.1 Configure 配置
- 2.1.2 Write tests 编写测试
- 2.1.3 Setup and teardown 安装和拆卸
- 2.1.4 Complete test example 完整的测试示例
- 2.2 Prepare server state via API calls 通过API调用准备服务器状态
- 2.3 Check the server state after running user actions 运行用户操作后检查服务器状态
- 2.4 Reuse authentication state 重用的认证状态
- 3. Assertions 断言
Playwrigh 使用指南-1
1 Auto-waiting 自动等待
Playwright 在执行操作之前对元素执行一系列可操作性检查,以确保这些操作按预期运行。它会自动等待所有相关检查通过,然后才执行请求的操作。如果所需的检查未在给定范围内通过timeout
,则操作失败并显示TimeoutError
.
例如,对于[page.click(selector, **kwargs)],Playwright 将确保:
- 元素Attached 附加到 DOM
- 元素Visible 可见
- 元素是Stable 稳定的,就像没有动画或完成动画一样
- 元素 Receives Events 接收事件,因为没有被其他元素遮挡
- 元素已 Enabled 启用
以下是为每个操作执行的可操作性检查的完整列表:
Action | Attached | Visible | Stable | Receives Events | Enabled | Editable |
check | Yes | Yes | Yes | Yes | Yes | - |
click | Yes | Yes | Yes | Yes | Yes | - |
dblclick | Yes | Yes | Yes | Yes | Yes | - |
setChecked | Yes | Yes | Yes | Yes | Yes | - |
tap | Yes | Yes | Yes | Yes | Yes | - |
uncheck | Yes | Yes | Yes | Yes | Yes | - |
hover | Yes | Yes | Yes | Yes | - | - |
scrollIntoViewIfNeeded | Yes | - | Yes | - | - | - |
screenshot | Yes | Yes | Yes | - | - | - |
fill | Yes | Yes | - | - | Yes | Yes |
selectText | Yes | Yes | - | - | - | - |
dispatchEvent | Yes | - | - | - | - | - |
focus | Yes | - | - | - | - | - |
getAttribute | Yes | - | - | - | - | - |
innerText | Yes | - | - | - | - | - |
innerHTML | Yes | - | - | - | - | - |
press | Yes | - | - | - | - | - |
setInputFiles | Yes | - | - | - | - | - |
selectOption | Yes | Yes | - | - | Yes | - |
textContent | Yes | - | - | - | - | - |
type | Yes | - | - | - | - | - |
Forcing actions 强制行为
page.click(selector, **kwargs)
等一些操作支持force
禁用非必要可操作性检查的选项,例如将真值传递force
给page.click(selector, **kwargs)
方法不会检查目标元素是否实际接收到点击事件.
Attached
当元素连接到 Document 或 ShadowRoot时,它被认为是附加的。
Visible
当元素具有非空边界框并且没有visibility:hidden
计算样式时,元素被认为是可见的。请注意,大小为零或 with 的元素display:none
不被视为可见。
Stable
当元素在至少两个连续的动画帧中保持相同的边界框时,元素被认为是稳定的。
Enabled
元素被视为已启用,除非它是<button>
、或具有属性。<select>``<input>``<textarea>``disabled
Editable
当元素被启用并且没有readonly
设置属性时,它被认为是可编辑的。
Receives Events 接收事件
- 当元素在动作点是指针事件的命中目标时,被认为接收指针事件。例如,当点击 点时
(10;10)
,Playwright 会检查是否有其他元素(通常是叠加层)会捕获点击点(10;10)
。
例如,考虑一个场景,Sign Up
无论何时调用page.click(selector, **kwargs)
,Playwright 都会点击按钮:
- 页面正在检查用户名是否唯一且
Sign Up
按钮已禁用; - 在与服务器核对后,禁用的
Sign Up
按钮将替换为另一个现在启用的按钮。
2 API testing API 测试
Playwright 可用于访问应用程序的 REST API。
有时您可能希望直接从 Python 向服务器发送请求,而无需加载页面并在其中运行 js 代码。它可能会派上用场的几个例子:
- 测试您的服务器 API。
- 在测试中访问 Web 应用程序之前准备服务器端状态。
- 在浏览器中运行一些操作后验证服务器端的后置条件。
所有这些都可以通过APIRequestContext方法来实现。
以下示例依赖于pytest-playwright
将 Playwright 固定装置添加到 Pytest 测试运行器的包。
- 编写 API 测试
- 通过 API 调用准备服务器状态
- 运行用户操作后检查服务器状态
- 重用认证状态
2.1 Writing API Test 编写 API测试
APIRequestContext可以通过网络发送各种 HTTP(S) 请求。
以下示例演示了如何使用 Playwright 通过GitHub API测试问题创建。测试套件将执行以下操作:
- 在运行测试之前创建一个新的存储库。
- 创建一些问题并验证服务器状态。
- 运行测试后删除存储库。
2.1.1 Configure 配置
GitHub API 需要授权,因此我们将为所有测试配置一次令牌。在此期间,我们还将设置baseURL
以简化测试。
import os
from typing import Generator
import pytest
from 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()
2.1.2 Write tests 编写测试
现在我们初始化了请求对象,我们可以添加一些测试,这些测试将在存储库中创建新问题。
import os
from typing import Generator
import pytest
from 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"
2.1.3 Setup and teardown 安装和拆卸
这些测试假定存储库存在。您可能希望在运行测试之前创建一个新的,然后再将其删除。为此使用会话夹具。之前的部分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
2.1.4 Complete test example 完整的测试示例
以下是 API 测试的完整示例:
from enum import auto
import os
from typing import Generator
import pytest
from 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"
2.2 Prepare server state via API calls 通过API调用准备服务器状态
以下测试通过 API 创建一个新问题,然后导航到项目中所有问题的列表以检查它是否出现在列表顶部。使用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")
2.3 Check the server state after running user actions 运行用户操作后检查服务器状态
以下测试通过浏览器中的用户界面创建一个新问题,然后通过 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"
2.4 Reuse authentication state 重用的认证状态
Web 应用程序使用基于 cookie 或基于令牌的身份验证,其中经过身份验证的状态存储为cookie。Playwright 提供了api_request_context.storage_state(**kwargs)方法,该方法可用于从经过身份验证的上下文中检索存储状态,然后使用该状态创建新的上下文。
存储状态在BrowserContext和APIRequestContext之间是可互换的。您可以使用它通过 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)
3. Assertions 断言
Playwright 为您提供了 Web-First Assertions 以及方便的方法来创建断言,这些断言将等待并重试,直到满足预期的条件。
考虑以下示例:
同步
from playwright.sync_api import Page, expect
def test_status_becomes_submitted(page: Page) -> None:
# ..
page.locator("#submit-button").click()
expect(page.locator(".status")).to_have_text("Submitted")
异步
from playwright.async_api import Page, expect
async def test_status_becomes_submitted(page: Page) -> None:
# ..
await page.locator("#submit-button").click()
await expect(page.locator(".status")).to_have_text("Submitted")
Playwright 将使用选择器重新测试节点,.status
直到获取的节点具有"Submitted"
文本。它将重新获取节点并一遍又一遍地检查它,直到满足条件或达到超时。您可以将此超时作为选项传递。
默认情况下,断言超时设置为 5 秒。
详细使用请参考官方文档