APIテスト
はじめに
Playwrightは、RESTアプリケーションのAPIにアクセスするために使用できます。
場合によっては、ページを読み込んだり、ページ内でJavaScriptコードを実行したりせずに、Javaから直接サーバーにリクエストを送信したいことがあります。これらが役立ついくつかの例です。
- サーバーAPIをテストする。
- テストでウェブアプリケーションにアクセスする前に、サーバー側の状態を準備する。
- ブラウザでいくつかのアクションを実行した後、サーバー側の事後条件を検証する。
これらはすべて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
フックを使用します。
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();
}
}
テスト例の完全版
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を使用して実行されます。
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");
}
}
ユーザーアクション実行後のサーバー状態の確認
以下のテストは、ブラウザのユーザーインターフェースを介して新しい課題を作成し、その後APIを介してそれが作成されたかどうかを確認します
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"));
}
}
認証状態の再利用
Webアプリケーションは、認証された状態がcookiesとして保存されるCookieベースまたはトークンベースの認証を使用します。PlaywrightはAPIRequestContext.storageState()メソッドを提供しており、認証されたコンテキストからストレージ状態を取得し、その状態を持つ新しいコンテキストを作成するために使用できます。
ストレージ状態はBrowserContextとAPIRequestContextの間で交換可能です。これを使用して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));