Skip to main content

API 测试

介绍

¥Introduction

Playwright 可用于访问应用的 REST API。

¥Playwright can be used to get access to the REST API of your application.

有时你可能希望直接从 Java 向服务器发送请求,而无需加载页面并在其中运行 JS 代码。一些它可能派上用场的例子:

¥Sometimes you may want to send requests to the server directly from Java without loading a page and running js code in it. A few examples where it may come in handy:

  • 测试你的服务器 API。

    ¥Test your server API.

  • 在测试中访问 Web 应用之前准备服务器端状态。

    ¥Prepare server side state before visiting the web application in a test.

  • 在浏览器中运行某些操作后验证服务器端后置条件。

    ¥Validate server side post-conditions after running some actions in the browser.

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

¥All of that could be achieved via APIRequestContext methods.

编写 API 测试

¥Writing API Test

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

¥APIRequestContext can send all kinds of HTTP(S) requests over network.

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

¥The following example demonstrates how to use Playwright to test issues creation via GitHub API. The test suite will do the following:

  • 在运行测试之前创建一个新的存储库。

    ¥Create a new repository before running tests.

  • 创建一些问题并验证服务器状态。

    ¥Create a few issues and validate server state.

  • 运行测试后删除存储库。

    ¥Delete the repository after running tests.

配置

¥Configure

GitHub API 需要授权,因此我们将为所有测试配置一次令牌。同时,我们还将设置 baseURL 以简化测试。

¥GitHub API requires authorization, so we'll configure the token once for all tests. While at it, we'll also set the baseURL to simplify the tests.

package org.example;

import com.microsoft.playwright.APIRequest;
import com.microsoft.playwright.APIRequestContext;
import com.microsoft.playwright.Playwright;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.TestInstance;

import java.util.HashMap;
import java.util.Map;

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class TestGitHubAPI {
private static final String API_TOKEN = System.getenv("GITHUB_API_TOKEN");

private Playwright playwright;
private APIRequestContext request;

void createPlaywright() {
playwright = Playwright.create();
}

void createAPIRequestContext() {
Map<String, String> headers = new HashMap<>();
// We set this header per GitHub guidelines.
headers.put("Accept", "application/vnd.github.v3+json");
// Add authorization token to all requests.
// Assuming personal access token available in the environment.
headers.put("Authorization", "token " + API_TOKEN);

request = playwright.request().newContext(new APIRequest.NewContextOptions()
// All requests we send go to this API endpoint.
.setBaseURL("https://api.github.com")
.setExtraHTTPHeaders(headers));
}

@BeforeAll
void beforeAll() {
createPlaywright();
createAPIRequestContext();
}

void disposeAPIRequestContext() {
if (request != null) {
request.dispose();
request = null;
}
}

void closePlaywright() {
if (playwright != null) {
playwright.close();
playwright = null;
}
}

@AfterAll
void afterAll() {
disposeAPIRequestContext();
closePlaywright();
}
}

编写测试

¥Write tests

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

¥Now that we initialized request object we can add a few tests that will create new issues in the repository.

package org.example;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.APIRequest;
import com.microsoft.playwright.APIRequestContext;
import com.microsoft.playwright.APIResponse;
import com.microsoft.playwright.Playwright;
import com.microsoft.playwright.options.RequestOptions;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.*;

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class TestGitHubAPI {
private static final String REPO = "test-repo-2";
private static final String USER = System.getenv("GITHUB_USER");
private static final String API_TOKEN = System.getenv("GITHUB_API_TOKEN");

private Playwright playwright;
private APIRequestContext request;

// ...

@Test
void shouldCreateBugReport() {
Map<String, String> data = new HashMap<>();
data.put("title", "[Bug] report 1");
data.put("body", "Bug description");
APIResponse newIssue = request.post("/repos/" + USER + "/" + REPO + "/issues",
RequestOptions.create().setData(data));
assertTrue(newIssue.ok());

APIResponse issues = request.get("/repos/" + USER + "/" + REPO + "/issues");
assertTrue(issues.ok());
JsonArray json = new Gson().fromJson(issues.text(), JsonArray.class);
JsonObject issue = null;
for (JsonElement item : json) {
JsonObject itemObj = item.getAsJsonObject();
if (!itemObj.has("title")) {
continue;
}
if ("[Bug] report 1".equals(itemObj.get("title").getAsString())) {
issue = itemObj;
break;
}
}
assertNotNull(issue);
assertEquals("Bug description", issue.get("body").getAsString(), issue.toString());
}

@Test
void shouldCreateFeatureRequest() {
Map<String, String> data = new HashMap<>();
data.put("title", "[Feature] request 1");
data.put("body", "Feature description");
APIResponse newIssue = request.post("/repos/" + USER + "/" + REPO + "/issues",
RequestOptions.create().setData(data));
assertTrue(newIssue.ok());

APIResponse issues = request.get("/repos/" + USER + "/" + REPO + "/issues");
assertTrue(issues.ok());
JsonArray json = new Gson().fromJson(issues.text(), JsonArray.class);
JsonObject issue = null;
for (JsonElement item : json) {
JsonObject itemObj = item.getAsJsonObject();
if (!itemObj.has("title")) {
continue;
}
if ("[Feature] request 1".equals(itemObj.get("title").getAsString())) {
issue = itemObj;
break;
}
}
assertNotNull(issue);
assertEquals("Feature description", issue.get("body").getAsString(), issue.toString());
}
}

设置和拆卸

¥Setup and teardown

这些测试假设存储库存在。你可能想在运行测试之前创建一个新的测试,然后将其删除。为此使用 @BeforeAll@AfterAll 钩子。

¥These tests assume that repository exists. You probably want to create a new one before running tests and delete it afterwards. Use @BeforeAll and @AfterAll hooks for that.

public class TestGitHubAPI {
// ...

void createTestRepository() {
APIResponse newRepo = request.post("/user/repos",
RequestOptions.create().setData(Collections.singletonMap("name", REPO)));
assertTrue(newRepo.ok(), newRepo.text());
}

@BeforeAll
void beforeAll() {
createPlaywright();
createAPIRequestContext();
createTestRepository();
}

void deleteTestRepository() {
if (request != null) {
APIResponse deletedRepo = request.delete("/repos/" + USER + "/" + REPO);
assertTrue(deletedRepo.ok());
}
}
// ...

@AfterAll
void afterAll() {
deleteTestRepository();
disposeAPIRequestContext();
closePlaywright();
}
}

完整测试示例

¥Complete test example

以下是一个完整的 API 测试示例:

¥Here is the complete example of an API test:

package org.example;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.APIRequest;
import com.microsoft.playwright.APIRequestContext;
import com.microsoft.playwright.APIResponse;
import com.microsoft.playwright.Playwright;
import com.microsoft.playwright.options.RequestOptions;
import org.junit.jupiter.api.*;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.*;

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class TestGitHubAPI {
private static final String REPO = "test-repo-2";
private static final String USER = System.getenv("GITHUB_USER");
private static final String API_TOKEN = System.getenv("GITHUB_API_TOKEN");

private Playwright playwright;
private APIRequestContext request;

void createPlaywright() {
playwright = Playwright.create();
}

void createAPIRequestContext() {
Map<String, String> headers = new HashMap<>();
// We set this header per GitHub guidelines.
headers.put("Accept", "application/vnd.github.v3+json");
// Add authorization token to all requests.
// Assuming personal access token available in the environment.
headers.put("Authorization", "token " + API_TOKEN);

request = playwright.request().newContext(new APIRequest.NewContextOptions()
// All requests we send go to this API endpoint.
.setBaseURL("https://api.github.com")
.setExtraHTTPHeaders(headers));
}

void createTestRepository() {
APIResponse newRepo = request.post("/user/repos",
RequestOptions.create().setData(Collections.singletonMap("name", REPO)));
assertTrue(newRepo.ok(), newRepo.text());
}

@BeforeAll
void beforeAll() {
createPlaywright();
createAPIRequestContext();
createTestRepository();
}

void deleteTestRepository() {
if (request != null) {
APIResponse deletedRepo = request.delete("/repos/" + USER + "/" + REPO);
assertTrue(deletedRepo.ok());
}
}

void disposeAPIRequestContext() {
if (request != null) {
request.dispose();
request = null;
}
}

void closePlaywright() {
if (playwright != null) {
playwright.close();
playwright = null;
}
}

@AfterAll
void afterAll() {
deleteTestRepository();
disposeAPIRequestContext();
closePlaywright();
}

@Test
void shouldCreateBugReport() {
Map<String, String> data = new HashMap<>();
data.put("title", "[Bug] report 1");
data.put("body", "Bug description");
APIResponse newIssue = request.post("/repos/" + USER + "/" + REPO + "/issues",
RequestOptions.create().setData(data));
assertTrue(newIssue.ok());

APIResponse issues = request.get("/repos/" + USER + "/" + REPO + "/issues");
assertTrue(issues.ok());
JsonArray json = new Gson().fromJson(issues.text(), JsonArray.class);
JsonObject issue = null;
for (JsonElement item : json) {
JsonObject itemObj = item.getAsJsonObject();
if (!itemObj.has("title")) {
continue;
}
if ("[Bug] report 1".equals(itemObj.get("title").getAsString())) {
issue = itemObj;
break;
}
}
assertNotNull(issue);
assertEquals("Bug description", issue.get("body").getAsString(), issue.toString());
}

@Test
void shouldCreateFeatureRequest() {
Map<String, String> data = new HashMap<>();
data.put("title", "[Feature] request 1");
data.put("body", "Feature description");
APIResponse newIssue = request.post("/repos/" + USER + "/" + REPO + "/issues",
RequestOptions.create().setData(data));
assertTrue(newIssue.ok());

APIResponse issues = request.get("/repos/" + USER + "/" + REPO + "/issues");
assertTrue(issues.ok());
JsonArray json = new Gson().fromJson(issues.text(), JsonArray.class);
JsonObject issue = null;
for (JsonElement item : json) {
JsonObject itemObj = item.getAsJsonObject();
if (!itemObj.has("title")) {
continue;
}
if ("[Feature] request 1".equals(itemObj.get("title").getAsString())) {
issue = itemObj;
break;
}
}
assertNotNull(issue);
assertEquals("Feature description", issue.get("body").getAsString(), issue.toString());
}
}

请参阅实验性 JUnit 集成,了解如何自动初始化 Playwright 对象等。

¥See experimental JUnit integration to automatically initialize Playwright objects and more.

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

¥Prepare server state via API calls

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

¥The following test creates a new issue via API and then navigates to the list of all issues in the project to check that it appears at the top of the list. The check is performed using LocatorAssertions.

public class TestGitHubAPI {
@Test
void lastCreatedIssueShouldBeFirstInTheList() {
Map<String, String> data = new HashMap<>();
data.put("title", "[Feature] request 1");
data.put("body", "Feature description");
APIResponse newIssue = request.post("/repos/" + USER + "/" + REPO + "/issues",
RequestOptions.create().setData(data));
assertTrue(newIssue.ok());

page.navigate("https://github.com/" + USER + "/" + REPO + "/issues");
Locator firstIssue = page.locator("a[data-hovercard-type='issue']").first();
assertThat(firstIssue).hasText("[Feature] request 1");
}
}

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

¥Check the server state after running user actions

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

¥The following test creates a new issue via user interface in the browser and then checks via API if it was created:

public class TestGitHubAPI {
@Test
void lastCreatedIssueShouldBeOnTheServer() {
page.navigate("https://github.com/" + USER + "/" + 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();
String issueId = page.url().substring(page.url().lastIndexOf('/'));

APIResponse newIssue = request.get("https://github.com/" + USER + "/" + REPO + "/issues/" + issueId);
assertThat(newIssue).isOK();
assertTrue(newIssue.text().contains("Bug report 1"));
}
}

重用身份验证状态

¥Reuse authentication state

Web 应用使用基于 cookie 或基于令牌的身份验证,其中经过身份验证的状态存储为 cookies。Playwright 提供了 APIRequestContext.storageState() 方法,可用于从经过身份验证的上下文中检索存储状态,然后使用该状态创建新的上下文。

¥Web apps use cookie-based or token-based authentication, where authenticated state is stored as cookies. Playwright provides APIRequestContext.storageState() method that can be used to retrieve storage state from an authenticated context and then create new contexts with that state.

存储状态可以在 BrowserContextAPIRequestContext 之间互换。你可以使用它通过 API 调用登录,然后创建一个已存在 cookie 的新上下文。以下代码片段从经过身份验证的 APIRequestContext 检索状态,并使用该状态创建一个新的 BrowserContext

¥Storage state is interchangeable between BrowserContext and APIRequestContext. You can use it to log in via API calls and then create a new context with cookies already there. The following code snippet retrieves state from an authenticated APIRequestContext and creates a new BrowserContext with that state.

APIRequestContext requestContext = playwright.request().newContext(
new APIRequest.NewContextOptions().setHttpCredentials("user", "passwd"));
requestContext.get("https://api.example.com/login");
// Save storage state into a variable.
String state = requestContext.storageState();

// Create a new context with the saved storage state.
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setStorageState(state));