## 目的
[[Playwright]]でプロダクトの[[E2Eテスト]]が書ける道しるべを重要な点のみにフォーカスして伝える。
> [!attention]
> プレゼンテーション資料の下書きではなく、あくまでそれを作成するためのメモ。つまり、情報量は多め
## 概要
https://playwright.dev/
- サポート
- モダンブラウザはほぼサポート
- [[クロスプラットフォーム|クロスプラットフォーム対応]]
- ヘッドレス、ヘッド対応
- モバイルエミュレータサポート
- [[Android]]は[[Google Chrome]]
- [[iOS]]は[[Safari]]
- [[フレイキーテスト]]にならない
- ウェイトを入れなくても操作可能になるまで待機してアクションできる
- ビデオやスクリーンショット撮影で実行トレースが可能
- 制限なし
- マルチタブ、マルチオリジン、マルチユーザー対応
- 実際のユーザーと区別がつかないほど忠実なイベント
- フレームをテストし、shadow domを貫通?
- Browser contexts
- 新しいブラウザプロファイルをつくる
- 数ミリ秒
- ログイン情報を保存して他のテストで使い回し可能
- Codegen
- 操作を記録してコード化する
- Playwright inspector
- テストコードに必要な情報取得
- Trace Viewer
- 様々な方法で追跡可能
## テスト用のプロジェクト作成
せっかくなので[[Turbopack]]を使ってみる。[[📜TurbopackでNext.jsプロジェクトを作ってみた]]を参照。
## [[Playwright]]インストール
https://playwright.dev/docs/intro
```console
$ npm init playwright@latest
Need to install the following packages:
create-playwright@latest
Ok to proceed? (y) y
Getting started with writing end-to-end tests with Playwright:
Initializing project in '.'
√ Where to put your end-to-end tests? · e2e
√ Add a GitHub Actions workflow? (y/N) · false
√ Install Playwright browsers (can be done manually via 'npx playwright install')? (Y/n) · true
Installing Playwright Test (npm install --save-dev @playwright/test)…
npm WARN config global `--global`, `--local` are deprecated. Use `--location=global` instead.
added 2 packages, and audited 463 packages in 6s
93 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
Downloading browsers (npx playwright install)…
npm WARN config global `--global`, `--local` are deprecated. Use `--location=global` instead.
Downloading Chromium 107.0.5304.18 (playwright build v1028) - 109.2 Mb [====================] 100% 0.0s
Chromium 107.0.5304.18 (playwright build v1028) downloaded to C:\Users\syoum\AppData\Local\ms-playwright\chromium-1028
Downloading FFMPEG playwright build v1007 - 1.4 Mb [====================] 100% 0.0s
FFMPEG playwright build v1007 downloaded to C:\Users\syoum\AppData\Local\ms-playwright\ffmpeg-1007
Downloading Firefox 105.0.1 (playwright build v1357) - 77 Mb [====================] 100% 0.0s
Firefox 105.0.1 (playwright build v1357) downloaded to C:\Users\syoum\AppData\Local\ms-playwright\firefox-1357
Downloading Webkit 16.0 (playwright build v1724) - 58.3 Mb [====================] 100% 0.0s
Webkit 16.0 (playwright build v1724) downloaded to C:\Users\syoum\AppData\Local\ms-playwright\webkit-1724
Writing playwright.config.ts.
Writing e2e\example.spec.ts.
Writing tests-examples\demo-todo-app.spec.ts.
Writing package.json.
✔ Success! Created a Playwright Test project at C:\Users\syoum\work\playwright-sample
Inside that directory, you can run several commands:
npx playwright test
Runs the end-to-end tests.
npx playwright test --project=chromium
Runs the tests only on Desktop Chrome.
npx playwright test example
Runs the tests in a specific file.
npx playwright test --debug
Runs the tests in debug mode.
npx playwright codegen
Auto generate tests with Codegen.
We suggest that you begin by typing:
npx playwright test
And check out the following files:
- .\e2e\example.spec.ts - Example end-to-end test
- .\tests-examples\demo-todo-app.spec.ts - Demo Todo App end-to-end tests
- .\playwright.config.ts - Playwright Test configuration
Visit https://playwright.dev/docs/intro for more information. ✨
Happy hacking! 🎭
```
## 動作確認
```console
$ npx playwright test
Running 3 tests using 3 workers
3 passed (6s)
To open last HTML report run:
npx playwright show-report
```
いつの間にかレポートが見られるお洒落機能がついてた。結構前からありそう。
```console
npx playwright show-report
```
## Codegen
あとでやってみよう。
```console
npx playwright codegen
```
使い捨ては便利だけど、メンテが必要なコードでは使わない方がよさそう。
## テストを書いてみる
https://playwright.dev/docs/writing-tests
```ts
test('homepage has Playwright in title and get started link linking to the intro page', async ({
page,
}) => {
await page.goto('http://localhost:3000/');
// ...
}
```
### Assertions
タイトルが含まれているか
```ts
// Turbopackがタイトルに含まれるか
await expect(page).toHaveTitle(/Turbopack/);
```
タイトルと一致するか
```ts
// Turbopackがタイトルと一致するか
await expect(page).toHaveTitle('Turbopack');
```
### [[Locator]]
[[Locator]]は対象を示すために利用されるもの。
```ts
// Grouped Layoutsというテキストの要素
const groupedLayout = page.getByText('Grouped Layouts');
// をクリック
await groupedLayout.click();
```
候補が2つ以上ある場合はエラーになる。どちらが期待されるものなのか分からないため。
```
locator.click: Error: strict mode violation: "text=Grouped Layouts" resolved to 2 elements:
1) <a href="/route-groups" class="block rounded-md px-…>Grouped Layouts</a> aka playwright.$("role=link[name="Grouped Layouts"]")
2) <div>Grouped Layouts</div> aka playwright.$("role=link[name="Grouped Layouts Organize routes without affecting URL paths"]")
```
他にもいろいろなSelectorが使えるけどそれはおいおい
### Test hooks
- `test.describe`でグループ化
- `test.beforeEach`で各テストの前処理
- `test.afterEach`で各テストの後処理
- `test.beforeAll`で全テストに対して1回だけ前処理
- `test.afterAll`で全テストに対して1回だけ後処理
## テスト実行
https://playwright.dev/docs/running-tests
```console
npx playwright test
```
### headedモード
デフォルトではheadlessモードだが、視覚的に確認したいときは`--headed`オプションでheadlessモードを無効化できる。
```console
npx playwright test --headed
```
### デバッグモード
Playwright Inspectorが起動してステップ実行できる。情報も充実していて超便利。
```console
npx playwright test --debug
```
デバッグの範囲はオプションで詳細に指定できる。
### テストレポート
デフォルトでは失敗すると開くようになっている。
## テストジェネレーター
https://playwright.dev/docs/codegen-intro
- #todo `getByRole` を使うのはなぜ?
## Trace Viewer
```console
npx playwright test --trace on
```
リトライ処理のタイミングでTraceが開始され、詳細が[[ZIP]]に保存される。ブラウザがそれを参照すると、細かな状況を追跡できる。以前はVideo撮影くらいしかできなかったが、[[ffmpeg]]の常時稼働は重いので非常に助かる。
## [[VSCode]]で動かす
https://playwright.dev/docs/getting-started-vscode
[[Playwright Test for VSCode]]を使う。
一部のコマンドを実行しようとするとエラーになる。
```
テストを実行しようとしてエラーが発生しました: TypeError: Cannot read properties of undefined (reading 'setReuseBrowser')
```
動いてほしいのでIssueを出してみた。
https://github.com/microsoft/playwright-vscode/issues/263
[[📝Playwright Test for VSCodeのRun in Debug ModeがWindowsで動かない]] にも整理。
## 色々なテスト
### Annotations
https://playwright.dev/docs/test-annotations
- `test.only`
- `test.only` がついているテストのみ実行 (複数指定可)
- `test.skip`
- `test.skip`がついているテストをスキップ
### Assertions
- 基本は[[Jest]]の`expect`と同じ
- negativeチェックは `.not` をつける
- `expect.soft`でエラーが発生しても最後までテストを実行する
- 他、色々ありすぎるので省略
- [[Locator]]の`toHaveText`は一致する文字列を探す
- [[Locator]]の`toContainText`は含む文字列を探す
### Visual comparsions
https://playwright.dev/docs/test-snapshots
[[ビジュアルリグレッションテスト]]が可能。
```ts
// ピクセル数の相違数で閾値を設定
await expect(page).toHaveScreenshot({ maxDiffPixels: 100 });
// ピクセル数の相違割合で閾値を設定
await expect(page).toHaveScreenshot({ maxDiffPixelRatio: 0.1 });
```
### components
Experimentalだけど[[React]]/[[Svelte]]/[[Vue]]のコンポーネントがテストできるとか...
## ガイド
### Network
`page.route`でレスポンスを操作できる。`page.route`の第1引数はクエリ以降の表記も必要なので注意。
```ts
import { test, expect } from '@playwright/test';
test('Test 1', async ({ page }) => {
await page.route('**/api/users?*', (route) =>
route.fulfill({
status: 200,
body: JSON.stringify({
name: 'hoge',
message: 'huga',
}),
}),
);
await page.goto('http://localhost:3000/layouts');
const dummyDataGetButton = page.getByText('ダミーデータ取得');
await dummyDataGetButton.click();
await expect(
page.locator('pre:below(:text("ダミーデータ取得"))'),
).toContainText('Michael');
});
```
## [[Svelte]]でプロジェクトを作り直す
プロダクトコードに意識をやりたくないので、一番シンプルに記述できる[[Svelte]]に切り替える。
```console
$ npm create vite@latest playwright-svelte-sample -- --template svelte-ts
Need to install the following packages:
create-vite@latest
Ok to proceed? (y) y
Scaffolding project in C:\Users\syoum\work\playwright-svelte-sample...
Done. Now run:
cd playwright-svelte-sample
npm install
npm run dev
$ cd playwright-svelte-sample
$ npm install
$ npm run dev
```
`src`配下は以下の4ファイルのみ残す。
- `App.svelte`
- `main.ts`
- `app,css`
- `vite-env.d.ts`
`App.svelte`は以下のようコードを書く。
```ts
<script lang="ts">
interface Member {
id: number;
email: string;
first_name: string;
last_name: string;
avatar?: string;
}
let count = 0;
let members: Member[] = [];
const increment = () => {
count++;
};
const decrement = () => {
count--;
};
const fetchMembers = async () => {
const res = await fetch("https://reqres.in/api/users?page=2");
members = (await res.json()).data;
};
</script>
<main>
<h1>Playwright Svelte Sample</h1>
<h2>Push "Increment/Decrement" button</h2>
<div style=" justify-content: center; gap: 10px;">
<button on:click={increment}>Increment</button>
<button on:click={decrement}>Decrement</button>
</div>
<div style="font-size: 200%; padding: 30px;">{count}</div>
<h2>Click to fetch members</h2>
<button on:click={fetchMembers}>Fetch members</button>
<div style="text-align: left">
<ul>
{#each members as member}
<li>{member.first_name} {member.last_name}</li>
{/each}
</ul>
<pre class="json">{JSON.stringify(members, null, 2)}</pre>
</div>
</main>
<style>
.json {
background-color: darkslategray;
color: whitesmoke;
padding: 10px;
}
</style>
```
### [[Playwright]]インストール
今回は[[VSCode]]から。[[Chromium]]だけで。
### テストコードを書く
#### 表示するだけのテストコード
```ts
import { test, expect } from "@playwright/test";
test("てすと", async ({ page }) => {
await page.goto("http://localhost:5173");
const header = page.locator("h1");
await expect(header).toHaveText("Playwright Svelte Sample"); はだめ
});
```
`toHaveText`を使うと`Received string`が空になる場合は `await expect(...` の `await` が抜けてる。エラーにならないので気づきにくい。
-> [[📝PlaywrightのtoHaveTextが期待通り動かない]]
#### IncrementとDecrementの確認コード
```html
<script lang="ts">
interface Member {
id: number;
email: string;
first_name: string;
last_name: string;
avatar?: string;
}
let count = 0;
let members: Member[] = [];
const increment = () => {
count++;
};
const decrement = () => {
count--;
};
const fetchMembers = async () => {
const res = await fetch("https://reqres.in/api/users?page=2");
members = (await res.json()).data;
};
</script>
<main>
<h1>Playwright Svelte Sample</h1>
<h2>Push "Increment/Decrement" button</h2>
<div style=" justify-content: center; gap: 10px;">
<button on:click={increment}>Increment</button>
<button on:click={decrement}>Decrement</button>
</div>
<div data-test-id="counter" style="font-size: 200%; padding: 30px;">
{count}
</div>
<h2>Click to fetch members</h2>
<button on:click={fetchMembers}>Fetch members</button>
<div style="text-align: left">
<ul>
{#each members as member}
<li>{member.first_name} {member.last_name}</li>
{/each}
</ul>
<pre class="json">{JSON.stringify(members, null, 2)}</pre>
</div>
</main>
<style>
.json {
background-color: darkslategray;
color: whitesmoke;
padding: 10px;
}
</style>
```