Skip to main content

API 測試

簡介

Playwright 可以用來存取你的應用程式的 REST API。

有時候你可能想直接從 Java 發送請求到伺服器,而不需要載入頁面並在其中執行 js 程式碼。以下是一些可能派上用場的範例:

  • 測試你的伺服器 API。
  • 在測試中訪問 web 應用程式之前準備伺服器端狀態。
  • 在瀏覽器中執行一些操作後驗證伺服器端後置條件。

所有這些都可以通過 APIRequestContext 方法實現。

撰寫 API 測試

APIRequestContext 可以透過網路發送各種 HTTP(S) 請求。

以下範例展示如何使用 Playwright 測試通過 GitHub API 建立問題。測試套件將執行以下操作:

  • 建立一個新的儲存庫後執行測試。
  • 建立一些問題並驗證伺服器狀態。
  • 執行測試後刪除儲存庫。

設定

GitHub API 需要授權,所以我們將為所有測試設定一次 token。在此同時,我們也會設定 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 integration 以自動初始化 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 apps 使用基於 cookie 或基於 token 的身份驗證,已驗證的狀態存儲為 cookies。Playwright 提供 APIRequestContext.storageState() 方法,可用於從已驗證的上下文中檢索存儲狀態,然後使用該狀態建立新的上下文。

儲存狀態在 BrowserContextAPIRequestContext 之間是可互換的。你可以使用它通過 API 呼叫登入,然後建立一個已經包含 cookies 的新 context。以下程式碼片段從已驗證的 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));