Closed26

[キャッチアップ] Playwright

shingo.sasakishingo.sasaki

スクラップについて

  • Playwright をざっくり理解して現場で活用できるかの検討材料とするのが狙い
  • ドキュメント読みながら手を動かしたりサンプルコード読んだりする
  • Cypress の経験があるため、主にそれとの比較の観点で読む
shingo.sasakishingo.sasaki

① トップページ

https://playwright.dev/

Playwright enables reliable end-to-end testing for modern web apps.

Puppeteer みたいなブラウザ自動化APIでなく、あくまで E2E テストのフレームワークの模様。

shingo.sasakishingo.sasaki

Any browser • Any platform • One API

Cross-browser. Playwright supports all modern rendering engines including Chromium, WebKit, and Firefox.

Chrome に限らず WebKit, FireFox も同じ API で使用できるらしい。どういう仕組なんだろ。でも今回のスコープだと Chrome で動けばOKなのであんまり考えずにいく。

Cross-platform. Test on Windows, Linux, and macOS, locally or on CI, headless or headed.

どこでも動かせるらしい。それはありがたい。想定用途としては、Mac (M1) 上と、CI (Linux) の二箇所で動けばいいかな。

Cross-language. Use the Playwright API in TypeScript, JavaScript, Python, .NET, Java.

あ、JS(TS) じゃなくても良いんだ。ほかの言語のドライバがあるのかな。今回は TypeScript を使うものとする。

しらべるとクライアントとして Ruby が使えたりもする。本来は Node で、ファーストサポートに Python とか、サードパーティで任意の言語が使えるって感じかな。

Test Mobile Web. Native mobile emulation of Google Chrome for Android and Mobile Safari. The same rendering engine works on your Desktop and in the Cloud.

モバイルデバイスのエミュレーションも可能。これもすごそうだけど今回はPR用途だけで考える。

shingo.sasakishingo.sasaki

Resilient • No flaky tests

Auto-wait. Playwright waits for elements to be actionable prior to performing actions. It also has a rich set of introspection events. The combination of the two eliminates the need for artificial timeouts - the primary cause of flaky tests.

ajax 周りに対する自動待機か。このへんの仕組みは結構ツールによって差がありそう。Cypress だと内部的に自動リトライを何度も繰り返すって感じだった気がする

Web-first assertions. Playwright assertions are created specifically for the dynamic web. Checks are automatically retried until the necessary conditions are met.

アサートに関してはまさに自動リトライ機構を持ってるようね。

Tracing. Configure test retry strategy, capture execution trace, videos, screenshots to eliminate flakes.

失敗したテストに対するフォローアップ。このへんは Cypress がたいだい同じような仕組みを持ってるので理解してる。

shingo.sasakishingo.sasaki

No trade-offs • No limits

Browsers run web content belonging to different origins in different processes. Playwright is aligned with the modern browsers architecture and runs tests out-of-process. This makes Playwright free of the typical in-process test runner limitations.

んー、ちょっと自信ないけど、テストランナーのプロセスと、ヘッドレスブラウザが動作しているプロセスが分離されていて、オリジンの数だけプロセスが作られますよって話かな。これは Cypress がテストランナーと対象サイトを同一プロセスで動かしているアーキテクチャと逆を行ってるな。

Multiple everything. Test scenarios that span multiple tabs, multiple origins and multiple users. Create scenarios with different contexts for different users and run them against your server, all in one test.

複数タブ、複数オリジン、複数セッションをサポート。これ Cypress だと苦手だから良いな。

Trusted events. Hover elements, interact with dynamic controls, produce trusted events. Playwright uses real browser input pipeline indistinguishable from the real user.

要素のホバー出来るのありがたい。Cypress 出来なかった気がする。

Test frames, pierce Shadow DOM. Playwright selectors pierce shadow DOM and allow entering frames seamlessly.

これが何を言ってるのかちょっと理解できてないけど、Cypress でも DOM の更新によって更新前の参照をした状態でのアサートに失敗するとかあった気がする。Cypress の悪口ばっかだなさっきから。

shingo.sasakishingo.sasaki

Full isolation • Fast execution

Browser contexts. Playwright creates a browser context for each test. Browser context is equivalent to a brand new browser profile. This delivers full test isolation with zero overhead. Creating a new browser context only takes a handful of milliseconds.

テストケースごとに新規プロファイルでのブラウザコンテキストを利用するため完全の独立している。パフォーマンスは気になるけど爆速らしい。

Log in once. Save the authentication state of the context and reuse it in all the tests. This bypasses repetitive log-in operations in each test, yet delivers full isolation of independent tests.

うん?これは直前のと矛盾しないのかな。コンテキストを共有するわけじゃなくて認証情報を保持して共有するってことかな。ログイン用のシナリオと、ログイン後のシナリオがある場合、実行順に依存関係が生じそうな気がするけどどうだろ。書いてみて調べればいいか。

shingo.sasakishingo.sasaki

まだ試してはいないけど、どうやら context.storageState(); みたいな仕組みでブラウザコンテキストをまるごと保存して、それを他のシナリオで再読み込みできるみたい。ので認証情報に限らず、クッキーまるごととかできるっぽい。

shingo.sasakishingo.sasaki

Powerful Tooling

Codegen. Generate tests by recording your actions. Save them into any language.

あー、ブラウザ手動操作してテストコードに起こすやつね。これはファーストテイクとしてはかなりアリ。流石にそのまま使うことはできないと思ってるけど。

Playwright inspector. Inspect page, generate selectors, step through the test execution, see click points, explore execution logs.

GUI のデバッグツールがあるらしい。そんなに使う機会あるかな?

Trace Viewer. Capture all the information to investigate the test failure. Playwright trace contains test execution screencast, live DOM snapshots, action explorer, test source, and many more.

失敗したテストのフォローアップ用のツールがある。DOM スナップショット良さそう。状況再現できるってことだよね。

shingo.sasakishingo.sasaki

② Installation

手元で某サービスをテストするコードを書きながら理解を深めていきたいので、インストール作業から始めてく。

https://playwright.dev/docs/intro

yarn create playwright

TS 使う。GitHub Actions なし。各種ブラウザインストール。

簡易的なプロジェクトが作られる。

こんなサンプルコードが作られてる。

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 page to have a heading with the name of Installation.
  await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
});

とりあえず実行する

$ npx playwright test

Running 6 tests using 6 workers
  6 passed (7.5s)

To open last HTML report run:

  npx playwright show-report

どうやら3ブラウザ x 2シナリオでサンプルテスト6種が実行されたらしい。
ブラウザについては設定ファイルのこのへんのことっぽいが、今回は Chrome 1個あればいいかな。

  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },

    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },

    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },
  ],

UI モードで起動する。

npx playwright test --ui

想像してたより遥かにカッコいいし多機能だ。この感じは一見すると Cypress だけど、プロセスはしっかり別れてるんだね。

これブラウザが中で動いてるんじゃないんだ。ブラウザは別プロセスで動いていて、その結果の DOM スナップショットをツール内で描画してるだけだな?実際にブラウザが自動で動いてる様は確認できないのかな。

shingo.sasakishingo.sasaki

明日以降、具体的なテストシナリオを書きながらツールに慣れていくよ。

shingo.sasakishingo.sasaki

③ Wrighting tests

https://playwright.dev/docs/writing-tests

Introduction

Playwright は要素がインタラクション可能であるかを自動でチェック・待機してくれる。
https://playwright.dev/docs/actionability

First test

テスト対象ページにアクセスし、ログイン画面の見出しをアサートする

import { test, expect } from "@playwright/test";

const LOGIN_URL = "https://hogehoge/login";

test("ログイン画面を表示できる", async ({ page }) => {
  await page.goto(LOGIN_URL);
  await expect(page.getByText("Log In")).toBeVisible();
});
  • goto はテストの基本となるページ遷移メソッド
  • getByText はロケータの一種で、要素の取得に使用する
  • expect は Jest みたいなテストアサーション
shingo.sasakishingo.sasaki

ロケータはやっぱりユーザーが視認する情報と、role みたいなアクセシビリティ属性を使用するのが推奨されてるみたい。code generator 使って自動生成した場合もそれが優先されるから、それをもとに手直しでOK
https://playwright.dev/docs/locators

shingo.sasakishingo.sasaki

アサーションで意識しなきゃならないのは、自動リトライするかどうかだな。
https://playwright.dev/docs/test-assertions

await expect(locator).toBeAttached() みたいな要素やページに基づくアサートは自動リトライ対象。

expect(value).toBeTruthy() みたいな単体テストで見かけるようなヤツは自動リトライしない。

These assertions allow to test any conditions, but do not auto-retry. Most of the time, web pages show information asynchronously, and using non-retrying assertions can lead to a flaky test.

ってことなので推奨されて無さそう。それはそう。

shingo.sasakishingo.sasaki

ログインできるぐらいのシナリオを作ってみる。

test("ログインできる", async ({ page }) => {
  await page.goto(LOGIN_URL);
  await page.getByPlaceholder("Enter group ID").fill(TENANT_NAME);
  await page.getByRole("button", { name: "Next" }).click();
  await expect(page.getByText(TENANT_NAME)).toBeVisible();
  await page
    .getByPlaceholder("Enter email address or account name")
    .fill(USER_NAME);
  await page.getByPlaceholder("Enter password").fill(PASSWORD);
  await page.getByRole("button", { name: "Log In" }).click();
  await expect(page.getByText("HogeHoge")).toBeVisible();
});
  • click fill は要素に対するアクション
  • ほかにも check hover focus setInputFiles selectOption あたりは使いそう
shingo.sasakishingo.sasaki

④ Running tests

https://playwright.dev/docs/running-tests

テスト実行の色んなコマンドの紹介。

ブラウザ実際に立ち上げて走らせるコマンドあった。細かいデバッグせずにざっと1種のテストだけ走らせるってときはこれで良さそう。

$ npx playwright test --headed

デバッグモードでステップ実行するのも面白い

$ npx playwright test --debug

テスト結果のレポートをHTMLで生成できる

$ npx playwright show-report
shingo.sasakishingo.sasaki

⑤ Test generator

https://playwright.dev/docs/codegen-intro

$ npx playwright codegen demo.playwright.dev/todomvc

みたいなコマンド実行するとブラウザが起動して対象ページにアクセスして、そのブラウザ上での操作がリアルタイムでコード化される。

使ってみたけど、記録用に使用されるブラウザが日本語で、実行時に使用されるほうが英語だったから再現できなかった。言語の指定はサッとできるんだろうけど。

shingo.sasakishingo.sasaki

英語にするのはこのオプション付けるだけだった。 --lang=en

同様にタイムゾーンや位置情報も変更可能。

shingo.sasakishingo.sasaki

しかもセッション情報の保存あるいは読み込みまでできるぞ…。

$ npx playwright codegen github.com/microsoft/playwright --save-storage=auth.json
$ npx playwright codegen --load-storage=auth.json github.com/microsoft/playwright
shingo.sasakishingo.sasaki

⑥ Trace viewer

https://playwright.dev/docs/trace-viewer-intro

トレースは実行ステップごとのDOMのスナップショットを閲覧してデバッグできる仕組み。

デフォルトだとテスト失敗時にリトライしてその時にのみトレースを記録する。めっちゃコスパ良さそう。

import { defineConfig } from '@playwright/test';
export default defineConfig({
  retries: process.env.CI ? 2 : 0, // set to 2 when running on CI
  // ...
  use: {
    trace: 'on-first-retry', // record traces on first retry of each test
  },
});

trace オプションで明示的にトレース作成もできる。

$ npx playwright test --trace on

トレースはレポートから確認可能。

npx playwright show-report
shingo.sasakishingo.sasaki

⑦ UI Mode

https://playwright.dev/docs/test-ui-mode

これまでもちょいちょい使ってた IDE みたいなやつ。トレース確認もできるし、ステップ実行もできるし、Playwright の機能を詰め込みまくった感がある。

  • テスト実行
  • ウォッチモード
  • ファイルエクスプローラー
  • トレース確認
  • VRT
  • ソースコード表示
  • などなど
shingo.sasakishingo.sasaki

⑧ CI GitHub Actions

https://playwright.dev/docs/ci-intro

GitHub Actions に限らず任意の CI 内でテストを走らせられるよという話。それはそう。

Azure にテストレポートをアップロードするとかニッチな案内出てきたなと思ったけど、Playwright も GitHub も Azure もマイクロソフトか…。

shingo.sasakishingo.sasaki

公式ドキュメントの基本的なページはこのぐらい。
リファレンス系は困ったら読めば良いとして、しばらく色んなテスト書いてみようかな。

shingo.sasakishingo.sasaki

セッションの状態を保存する

ログイン後のセッションを保存して再利用する。

state.json にその時点のクッキーとかローカルストレージとか全部をシリアライズしたやつが書き込まれる。

await page.context().storageState({ path: "state.json" });

これを別のテストで読み込むと再現できる。

test("ログインした状態でページを開ける", async () => {
  const browser = await chromium.launch();
  const state = JSON.parse(require("fs").readFileSync("state.json", "utf8"));
  const context = await browser.newContext({ storageState: state });
  const page = await context.newPage();

  await page.goto(URL);
});

コンテキストの作り方が若干分かりづらかったので整理する

  • browser: 起動するブラウザのインスタンス
  • context: セッションとなるブラウザコンテキスト
  • page: ブラウザのタブやウィンドウ

browser > context > page の階層関係で、それぞれ子を複数持てる感じかな。

shingo.sasakishingo.sasaki

画像をアップロードする

setInputFiles が使える。
https://playwright.dev/docs/api/class-elementhandle#element-handle-set-input-files

ファイルパスはカレントディレクトリからの相対パスとなるので、画像ファイルを用意しておく。

  const fileInput = await page.$("input[type=file].media-file-input");
  await fileInput?.setInputFiles("fixtures/sample.jpg");

ほんとは getByRole とかアクセシビリティ属性で取得できればよかったんだけど、そういうのがなかったので $ を使ってCSSセレクタで取り出してる。

shingo.sasakishingo.sasaki

Basic 認証を突破する

Basic 認証情報を入力する手段はないっぽいので、素直にURLに埋め込む。

await page.goto('http://username:password@example.com');
このスクラップは2023/10/18にクローズされました