自动化测试工具playwright中文文档-------10.API 测试

介绍

Playwright 可以用来访问你的应用程序的 REST API。

有时,你可能希望直接从 Python 向服务器发送请求,而无需加载页面并在其中运行 JavaScript 代码。这在以下情况下可能非常有用:

  1. 测试你的服务器 API。
  2. 在测试中访问 Web 应用程序之前准备服务器端状态。
  3. 在浏览器中执行某些操作后验证服务器端的后置条件。

所有这些都可以通过  APIRequestContext  方法来实现。

以下示例依赖于 pytest-playwright包,该包将 Playwright 夹具添加到 Pytest 测试运行器中。、

编写 API 测试

APIRequestContext 可以通过网络发送各种 HTTP(S) 请求。

以下示例演示了如何使用 Playwright(通过 GitHub API 测试问题创建。测试套件将执行以下操作:

  1. 在运行测试之前创建一个新的仓库。
  2. 创建几个问题并验证服务器状态。
  3. 在运行测试后删除仓库。

配置

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()

-> Generator[APIRequestContext, None, None] 指定了一个函数或方法的返回类型是一个生成器(Generator),这个生成器产生(yields)的对象类型是 APIRequestContext

Generator[T, SendType, ReturnType] 是一个特殊的类型注解,用于表示生成器函数的类型。这里:

  • T 是生成器产生(yields)的值的类型。
  • SendType 是可以通过 send() 方法发送到生成器的值的类型(在 Python 3.3+ 中引入)。如果生成器不需要接收通过 send() 发送的值,则通常为 None
  • ReturnType 是生成器正常完成时返回的值的类型(通过 return 语句)。如果生成器没有显式地返回任何值(即到达末尾或抛出异常),则默认为 None

编写测试

现在我们初始化了请求对象,我们可以添加一些将在存储库中创建新问题的测试。

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"

设置和清理

这些测试假设仓库已经存在。你可能希望在运行测试之前创建一个新仓库,并在测试完成后删除它。为此,可以使用一个会话范围的 fixture。在 yield 之前的部分代表所有测试之前的操作,而 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 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"

通过API调用来准备服务器状态

以下测试通过API创建了一个新的issue,然后导航到项目中的所有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:  
        """  
        通过API创建一个新的issue。  
  
        :param title: issue的标题  
        """  
        data = {  
            "title": title,  
            "body": "Feature description",  # issue的正文描述  
        }  
        # 使用API请求上下文向GitHub API发送POST请求来创建issue  
        new_issue = api_request_context.post(  
            f"/repos/{GITHUB_USER}/{GITHUB_REPO}/issues", data=data  
        )  
        # 断言确保请求成功  
        assert new_issue.ok  
  
    # 创建两个新的issue  
    create_issue("[Feature] request 1")  
    create_issue("[Feature] request 2")  
  
    # 使用Playwright的Page对象导航到GitHub上的issue列表页面  
    page.goto(f"https://github.com/{GITHUB_USER}/{GITHUB_REPO}/issues")  
  
    # 使用locator找到第一个issue的链接(这里假设GitHub的DOM结构中有相应的data属性)  
    first_issue = page.locator("a[data-hovercard-type='issue']").first  
  
    # 使用expect断言来检查第一个issue的标题是否为"[Feature] request 2"  
    # 这意味着最后创建的issue应该出现在列表的顶部  
    expect(first_issue).to_have_text("[Feature] request 2")

执行用户操作后检查服务器状态

以下测试通过浏览器用户界面创建一个新的issue,然后通过API检查该issue是否已被创建:

def test_last_created_issue_should_be_on_the_server(api_request_context: APIRequestContext, page: Page) -> None:  
    # 导航到GitHub仓库的issue页面  
    page.goto(f"https://github.com/{GITHUB_USER}/{GITHUB_REPO}/issues")  
    # 点击“New issue”按钮  
    page.locator("text=New issue").click()  
    # 填写issue的标题  
    page.locator("[aria-label='Title']").fill("Bug report 1")  
    # 填写issue的描述  
    page.locator("[aria-label='Comment body']").fill("Bug description")  
    # 点击“Submit new issue”按钮提交issue  
    page.locator("text=Submit new issue").click()  
      
    # 从提交后页面的URL中提取issue的ID  
    issue_id = page.url.split("/")[-1]  
  
    # 使用API请求上下文向GitHub API发送GET请求,检查新创建的issue  
    new_issue = api_request_context.get(f"https://github.com/{GITHUB_USER}/{GITHUB_REPO}/issues/{issue_id}")  
    # 断言确保请求成功  
    assert new_issue.ok  
    # 断言从API返回的issue的标题是否与预期一致  
    assert new_issue.json()["title"] == "Bug report 1"  
    # 断言从API返回的issue的描述是否与预期一致  
    # 注意:由于实际GitHub API返回的body字段可能包含HTML格式化或其他元数据,这里的断言可能需要调整以匹配实际返回的格式  
    assert new_issue.json()["body"] == "Bug description"

重用认证状态

Web应用程序使用基于cookie或基于令牌的认证方式,其中认证状态作为 cookies存储在浏览器中。Playwright 提供了 api_request_context.storage_state() 方法,该方法可用于从已认证的上下文中检索存储状态,并使用该状态创建新的上下文。

存储状态可以在 BrowserContext 和 APIRequestContext之间互换。你可以使用它先通过API调用来登录,然后创建一个包含已有cookie的新上下文。以下代码片段展示了如何从已认证的 APIRequestContext 中检索状态,并使用该状态创建一个新的  BrowserContext

# 假设你已经创建了一个Playwright实例并命名为playwright  
# 创建一个新的APIRequestContext,并带上HTTP凭证进行登录  
request_context = playwright.request.new_context(http_credentials={"username": "test", "password": "test"})  
# 注意:这里的.get()方法通常不会处理登录流程中的重定向或登录表单,这里只是为了示例  
# 在实际应用中,你可能需要发送POST请求到登录API,并处理可能的重定向  
request_context.get("https://api.example.com/login")  
  
# 保存存储状态到一个变量中  
state = request_context.storage_state()  
  
# 使用保存的存储状态创建一个新的BrowserContext  
context = browser.new_context(storage_state=state)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值