後輩くんに「E2Eテストなんか怖い😣」と言われたので「ソンナコトナイヨー☀」を体験してもらうために書く。
Table of Contents
Playwright
とりあえず、今回はこのPlaywrightってやつを使ってみよう。 まずは「なんか怖い」を払拭するためだから理由とかは脇においておこう。 でも軽快だし、クロスブラウザできるし、スクリーンショットやビデオも取れるし、いいぞ。
Playwrightをインストール
Docker使ってくぞ。まずは準備だ。
FROM node:19.4ENV WORKDIR=/playwright/
WORKDIR $WORKDIRversion: '3'
services: playwright: build: . volumes: - .:/playwrightdocker compose buildインストールは公式ドキュメント見てこう。
$ docker compose run playwright yarn create playwright
? Do you want to use TypeScript or JavaScript?> TypeScript
? Where to put your end-to-end tests?> tests
? Add a GitHub Actions workflow?> N
? Install Playwright browsers (can be done manually via 'yarn playwright install')?> n
? Install Playwright operating system dependencies (requires sudo / root - can be done manually via 'sudo yarn playwright install-deps')?> nいろいろなディレクトリやファイルが生成されているな。
- node_modules/- tests/ - example.spec.ts- tests-examples/ - demo-todo-app.spec.ts- .gitignore- docker-compose.yml- Dockerfile- package.json- playwright.config.ts- yarn.lockこんな感じになってるはず。
browsersとoperating system dependenciesはほしいので、Dockerfileに書いてこう。
FROM node:19.4ENV WORKDIR=/playwright/
WORKDIR $WORKDIR
COPY package.json $WORKDIRCOPY yarn.lock $WORKDIR
RUN yarn installRUN yarn playwright install && yarn playwright install-deps
CMD ["yarn", "playwright", "test"]Dockerfileいじったのでもう一度ビルドしておく。
docker compose build準備はこんなとこでOKかな。
テストを実行してみる
最初からtests/sample.spec.tsってサンプルテストコードがあるじゃないか。
import { test, expect } from '@playwright/test';
test('has title', async ({ page }) => { await page.goto('https://playwright.dev/');
// Expect a title "to contain" a substring. await expect(page).toHaveTitle(/Playwright/);});
test('get started link', async ({ page }) => { await page.goto('https://playwright.dev/');
// Click the get started link. await page.getByRole('link', { name: 'Get started' }).click();
// Expects the URL to contain intro. await expect(page).toHaveURL(/.*intro/);});test()ってのが2つある。これがテストケースだな。 その後に'has title'、'get started link'ってテキストが付いてるけど、テストケース名だろう。 コメントアウトで説明も書かれているけど、なんかコードからある程度わかりそうだな。
has title
page.goto()で括弧内のURLにアクセスしてそう。 page.goto('https://playwright.dev/')は https://playwright.dev/ にアクセスしてるんだろう。
expect(page).xxxで何かを検証していそうだな。 例えばexpect(page).toHaveTitle(/Playwright/)は、ページのタイトルに「Playwright」が含まれていることを検証していそうだ。 このページのタイトルは「Fast and reliable end-to-end testing for modern web apps | Playwright」みたいだからパスしそう。
get started link
page.goto('https://playwright.dev/')はhas titleのテストと同じだ。
page.getByRole('link', { name: 'Get started' }).click()は、「Get strted」ってテキストを持つリンクをクリックしているんだろう。ページトップにあるボタンだな。
expect(page).toHaveURL(/.*intro/)でURLがintroで終わるか見てるな。 実際遷移先のURLはhttps://playwright.dev/docs/introだからこれもパスしそう。
これを回してみよう。
$ docker compose up6 passedパスした。 ちなみにtests/sample.spec.tsにはtest()は2つしかないけど6つのテストがパスしてる。 そしてコンソールを眺めてると[chromium],[firefox],[webkit]の文字が見えた。 そう、すでにクロスブラウザでテストしてやがる。やりやがる。
この辺は、playwright.config.tsを眺めるとなんとなくわかる。
...export default defaultConfig({ testDir: './tests', ... projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] }, },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] }, },
{ name: 'webkit', use: { ...devices['Desktop Safari'] }, },
/* Test against mobile viewports. */ // { // name: 'Mobile Chrome', // use: { ...devices['Pixel 5'] }, // }, // { // name: 'Mobile Safari', // use: { ...devices['iPhone 12'] }, // },
/* Test against branded browsers. */ // { // name: 'Microsoft Edge', // use: { ...devices['Desktop Edge'], channel: 'msedge' }, // }, // { // name: 'Google Chrome', // use: { ..devices['Desktop Chrome'], channel: 'chrome' }, // }, ], ...})DesktopのChrome、Firefox、Saferiでテストしたようだ。 そしてコメントアウトにさらなる夢を感じる。
テストを書いてみる
自分でもテストを追加してみたい。
公式ドキュメントが丁寧。
https://playwright.dev/ にはページトップに「Playwright enables reliable end-to-end testing for modern web apps.」ってヒーロータイトルがある。これを検証してみよう。
...
test('has hero title', async ({ page }) => { await page.goto('https://playwright.dev/')
await expect(page.getByRole('heading', { name: 'Playwright enables reliable end-to-end testing for modern web apps.' })).toBeVisible()})...要素はLocatorsというもので指定するらしい(page.getByRole('header', { name: xxx })のとこ)。 toBeVisible()で存在していること(見えていること)を検証している。 詳しくは、公式ドキュメントを参照。
ちなみにclassを使ったりして指定もできる。
...
test('has hero title', async ({ page }) => { await page.goto('https://playwright.dev/')
await expect(page.locator('.hero__title')).toHaveText('Playwright enables reliable end-to-end testing for modern web apps.')})...$ docker compose up9 passedどちらの書き方にせよ、9つのテストがパスしたはず。
おぉ...なんかこんな感じでなんだってできそうだな..。
Locatorに対する操作もいろいろ..。
Assertionもいろいろ..。
これ、なんでもできるんじゃないの...??
失敗も見とく
ちゃんと失敗するのかも見ておこう。
...
test('has hero title', async ({ page }) => { await page.goto('https://playwright.dev/')
await expect(page.getByRole('heading', { name: 'Playwright enables reliable end-to-end testing for modern web apps.' })).toBeVisible() await expect(page.getByRole('heading', { name: 'Playwright enables reliable end-to-end testing for modern web apps.' })).not.toBeVisible()})....toBeVisible()の前に.notをつけた。これで「存在しない(見えない)」ことを検証することになる。 でも実際はあるから失敗するはず。
$ docker compose up3 failed[chromium] › example.spec.ts:20:5 › has hero title[firefox] › example.spec.ts:20:5 › has hero title[webkit] › example.spec.ts:20:5 › has hero title6 passed3つ失敗してる。期待通り。
ちなみにplaywrightはテストのレポートも作ってくれる。 playwright-report/index.htmlというファイルが生成されているので、ブラウザで開いて見てみよう。

うわぁ。見やすい..。

詳細まで見られるんですか..。
失敗時のオプションを少し追加
でも失敗時どうだったのか、このレポートからではわからない。 と思ったらテストオプションがめっちゃ豊富。
import { defineConfig, devices } from '@playwright/test';...export default defineConfig({ testDir: './tests', ... use: { trace: 'on-first-retry', screenshot: { mode: 'only-on-failure', fullPage: true, }, video: 'retain-on-failure', }, ...});失敗時にスクリーンショットとビデオを撮ることにしてみた。 もう一度テストを失敗させて、レポートを見ると..。


便利すぎ!
怖くないよね?
ということで、怖くなかったよね? むしろめっちゃ味方。頼れるやつやE2E。Playwrightすげぇ。 公式ドキュメント見るとページありすぎて「沼...。今はやめておこう...。」となるレベル。 でもやりたいことはコレくらいの労力で達成できますね。 それでちょっとずつ沼にハマれば、この公式ドキュメントの量は期待感しかない!
ということで、沼にはまろう!
サポートもお待ちしております!