先週に同僚と会話していたときに気になっていた Experimental Test Proxy について。
mugi さんの素振りメモ や この機能が実装された際の PR のコードを読んで、ようやく何が行われているかを理解した。
まず、README にある以下のコードがどこをスタブしているかであるが、
import { test, expect } from "next/experimental/testmode/playwright";
test("/product/shoe", async ({ page, next }) => {
next.onFetch((request) => {
if (request.url === "http://my-db/product/shoe") {
return new Response(
JSON.stringify({
title: "A shoe",
}),
{
headers: {
"Content-Type": "application/json",
},
}
);
}
return "abort";
});
await page.goto("/product/shoe");
await expect(page.locator("body")).toHaveText(/Shoe/);
});
スタブしているのは下記の図でいうところの req B
の方であって、 req A
の Browser - Next server ではない。Request URL の例に my-db
と入ってるのはこれを強調する意図なんだと思う。
flowchart LR
ua[Browser]
bff[Next server]
backend[Backend service]
ua -- "req A"--> bff -- "req B" --> backend
Next Server からの Backend に対する Request を Playwright spec からスタブできるのは普通に考えるとかなり奇妙だが、以下のようにしてこの仕組みを実現している。
- Playwright Worker でテストのライフサイクルに合わせて Node.js の HTTP Server を上げ下げする処理を挟み込む. 起点はここ
- Playwright Page Fixture で Browser - Next server に
Next-Test-Proxy-Port
Header に 1.で上げた Server の Port 番号を付与するようにする. この辺 - Next Server 側は
--experimental-test-proxy
が付与されたときに以下の処理が追加される- Next Server 自身の Request ハンドラで Experimental Test 用の ALS を起動しておく
fetch
をインターセプトする. ここの interceptTestApis という関数が実体.- インターセプトされた
fetch
は本来の Backend Service ではなく、リクエストのNext-Test-Proxy-Port
Header Port にプロキシする
- インターセプトされた
このようにして Next server からの fetch が Playwright Worker 上の HTTP Server に流れ込むようになる。
flowchart LR
ua[Browser]
proxy[Proxy Server\ne.g. MSW handlers]
bff[Next server]
subgraph Playwright
direction BT
subgraph Worker
direction LR
proxy
end
Worker -- "Control via CDP" --> ua
end
ua -- "req A" --> bff
subgraph Next
direction TB
bff --> fetch
end
fetch -- "req intercepted via test mode" --> proxy
Experimental Text Mode では Request ハンドラとして MSW も選択できることになっているが(ただし v1 系)、Node.js + MSW で想像する msw/node
などは一切でてこない。