跳至主要内容

API 测试

简介

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

有时您可能希望直接从 Java 发送请求到服务器,而无需加载页面并在其中运行 js 代码。以下是一些可能派上用场的示例

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

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

编写 API 测试

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

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

  • 在运行测试之前创建一个新的存储库。
  • 创建一些问题并验证服务器状态。
  • 运行测试后删除存储库。

配置

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

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

编写测试

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

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

设置和拆卸

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

  // ...

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

完整的测试示例

以下是 API 测试的完整示例

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 对象等。

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

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

@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");
}

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

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

@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"));
}

重用身份验证状态

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

存储状态可在 BrowserContextAPIRequestContext 之间互换。您可以使用它通过 API 调用登录,然后创建一个新的上下文,其中已包含 Cookie。以下代码片段从已认证的 APIRequestContext 中检索状态,并使用该状态创建一个新的 BrowserContext

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