Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

PlaywrightによるE2Eテスト入門 / Introduction to E2E Tes...

PlaywrightによるE2Eテスト入門 / Introduction to E2E Testing with Playwright

Hiroki Takeda

August 31, 2024
Tweet

More Decks by Hiroki Takeda

Other Decks in Technology

Transcript

  1. 2 / 68 whoami 武田 大輝(Takeda Hiroki) Future Architect, Inc.

    Technology Innovation Group Software Architect & Tech Lead X: @rhumie_ / Qiita: @rhumie 技育祭2023秋登壇しました https://www.youtube.com/watch?v=bnV4riSqVYQ
  2. 3 / 68 本講義の流れ 1. 導入編 ソフトウェアテストとは何かを学ぶ E2Eテストとは何かを学ぶ Playwrightとは何かを学ぶ 2.

    基礎編 Playwrightを動かしてみる PlaywrightによるE2Eテストの書き方を学ぶ 3. 応用編 E2Eテストの戦略を学ぶ
  3. 9 / 68 テストにはどのような種類があるのか 工程による分類 例. 単体テスト, 結合テスト, システムテスト, ユーザ受入テスト

    範囲による分類 例. 単体テスト, 結合テスト, E2Eテスト 目的による分類 例. 機能テスト, 非機能テスト(例. 性能, セキュリティ, ユーザビリティ), リグレッションテスト, 探索的テスト 手法による分類 例. ブラックボックステスト, ホワイトボックステスト 実行方法による分類 例. 動的テスト, 静的テスト テストはいろいろな軸で分類され, それぞれを効果的に組み合わせたテスト戦略が重要となる
  4. 10 / 68 テスト戦略モデルを知る(1/3) テストピラミッド cf. Succeeding with Agile: Software

    Development Using Scrum (Mike Cohn, 2009) 粒度の異なるテストのトレードオフを理解した上で, 全体としてどう最適化を行うか
  5. 11 / 68 テスト戦略モデルを知る(2/3) アイスクリームコーン(アンチパターン) cf. Introducing the software testing

    ice-cream cone (anti-pattern) 粒度の異なるテストのトレードオフを理解した上で, 全体としてどう最適化を行うか
  6. 15 / 68 E2Eテストは自動化すべきなのか 投資対効果(自動化による効果 / 自動化のための投資)を意識して自動化することが望ましい 頻繁にE2Eテストを繰り返す場合は, 投資対効果が大きい E2Eテストツールの進化に伴い、テストの実行速度や実装コストは昔(Selenium時代)よりも改善されている

    テストピラミッドに則った戦略(ユニットテストや統合テストでカバーできる範囲は委ね, E2Eテストは代表的な正常系/異常系のテス トに限定するなど)は有効 壊れにくいE2Eテストを書く技術もある cf. アクセシビリティ情報を使った壊れにくいE2Eテスト E2Eテストの自動化は高コストでメンテナンスも大変?
  7. 17 / 68 Playwrightとは ブラウザ操作のAPI + テスト関連の機能(テス トランナーやアサーション等)をオールイン・ ワンで提供 クロスブラウザ/クロスプラットフォーム

    堅牢で安定したテスト 制限のないテストシナリオ テストの完全な分離と高速化 テスト作成とデバッグのための強力なツール WebフロントエンドのE2Eテストを自動化するた めのツール Playwright enables reliable end-to-end testing for modern web apps. GET STARTED Star 65k+ Playwright
  8. 19 / 68 他のE2Eテストツール Cypress https://www.cypress.io/ Puppeteer https://pptr.dev/ Nightwatch https://nightwatchjs.org/

    [1] [*1] 正確にはテストツールではなく, ブラウザ操作の自動化ツール
  9. 22 / 68 セットアップ ▶️ Node.js 環境 ▶️ Playwright インストール

    https://playwright.dev/docs/intro#installing-playwright $ node -v v20.12.2 [1] $ npm init playwright@latest Need to install the following packages: [email protected] Ok to proceed? (y) y Getting started with writing end-to-end tests with Playwright: Initializing project in '.' ✔ Do you want to use TypeScript or JavaScript? · TypeScript ✔ Where to put your end-to-end tests? · tests ✔ Add a GitHub Actions workflow? (y/N) · false ✔ Install Playwright browsers (can be done manually via 'npx playwright install')? (Y/n) · true ✔ Install Playwright operating system dependencies (requires sudo / root - can be done manually via 'sudo npx playwright in [*1] 2024年4月時点での最新バージョンは v1.43.1
  10. 23 / 68 ディレクトリ/ファイル構造 https://playwright.dev/docs/intro#whats-installed . ├── node_modules ├── package.json

    ├── package-lock.json ├── playwright.config.ts // Playwrightの設定ファイル ├── tests // テストコードの格納ディレクトリ │ └── example.spec.ts // テストコード (サンプル) └── tests-examples // より詳細なサンプルの格納ディレクトリ (削除してよい) └── demo-todo-app.spec.ts // より詳細なサンプル
  11. 23 / 68 ディレクトリ/ファイル構造 https://playwright.dev/docs/intro#whats-installed ├── playwright.config.ts // Playwrightの設定ファイル .

    ├── node_modules ├── package.json ├── package-lock.json ├── tests // テストコードの格納ディレクトリ │ └── example.spec.ts // テストコード (サンプル) └── tests-examples // より詳細なサンプルの格納ディレクトリ (削除してよい) └── demo-todo-app.spec.ts // より詳細なサンプル
  12. 23 / 68 ディレクトリ/ファイル構造 https://playwright.dev/docs/intro#whats-installed ├── tests // テストコードの格納ディレクトリ │

    └── example.spec.ts // テストコード (サンプル) . ├── node_modules ├── package.json ├── package-lock.json ├── playwright.config.ts // Playwrightの設定ファイル └── tests-examples // より詳細なサンプルの格納ディレクトリ (削除してよい) └── demo-todo-app.spec.ts // より詳細なサンプル
  13. 23 / 68 ディレクトリ/ファイル構造 https://playwright.dev/docs/intro#whats-installed └── tests-examples // より詳細なサンプルの格納ディレクトリ (削除してよい)

    └── demo-todo-app.spec.ts // より詳細なサンプル . ├── node_modules ├── package.json ├── package-lock.json ├── playwright.config.ts // Playwrightの設定ファイル ├── tests // テストコードの格納ディレクトリ │ └── example.spec.ts // テストコード (サンプル)
  14. 24 / 68 サンプルテストを読む example.spec.ts 1 import { test, expect

    } from "@playwright/test"; 2 3 test("has title", async ({ page }) => { 4 await page.goto("https://playwright.dev/"); 5 6 // Expect a title "to contain" a substring. 7 await expect(page).toHaveTitle(/Playwright/); 8 }); 9 10 test("get started link", async ({ page }) => { 11 await page.goto("https://playwright.dev/"); 12 13 // Click the get started link. 14 await page 15 .getByRole("link", { name: "Get started" }) 16 .click(); 17 18 // Expects page to have a heading with the name of Installatio 19 await expect( 20 page.getByRole("heading", { name: "Installation" }) 21 ).toBeVisible(); 22 });
  15. 24 / 68 サンプルテストを読む 各テストケースは test() 関数を使って定義する test("テストの名前", async ({

    page }) => { // テストケース本体 }); 第1引数はテスト名, 第2引数はテスト本体となる関数 テスト関数の引数として渡される page は、ブラウザで開かれ たウェブページを指すオブジェクト example.spec.ts 1 import { test, expect } from "@playwright/test"; 3 test("has title", async ({ page }) => { 8 }); 2 4 await page.goto("https://playwright.dev/"); 5 6 // Expect a title "to contain" a substring. 7 await expect(page).toHaveTitle(/Playwright/); 9 10 test("get started link", async ({ page }) => { 11 await page.goto("https://playwright.dev/"); 12 13 // Click the get started link. 14 await page 15 .getByRole("link", { name: "Get started" }) 16 .click(); 17 18 // Expects page to have a heading with the name of Installatio 19 await expect( 20 page.getByRole("heading", { name: "Installation" }) 21 ).toBeVisible(); 22 });
  16. 24 / 68 サンプルテストを読む 各テストケースは test() 関数を使って定義する test("テストの名前", async ({

    page }) => { // テストケース本体 }); 第1引数はテスト名, 第2引数はテスト本体となる関数 テスト関数の引数として渡される page は、ブラウザで開かれ たウェブページを指すオブジェクト page.goto() で指定したURLをブラウザで開く example.spec.ts 4 await page.goto("https://playwright.dev/"); 1 import { test, expect } from "@playwright/test"; 2 3 test("has title", async ({ page }) => { 5 6 // Expect a title "to contain" a substring. 7 await expect(page).toHaveTitle(/Playwright/); 8 }); 9 10 test("get started link", async ({ page }) => { 11 await page.goto("https://playwright.dev/"); 12 13 // Click the get started link. 14 await page 15 .getByRole("link", { name: "Get started" }) 16 .click(); 17 18 // Expects page to have a heading with the name of Installatio 19 await expect( 20 page.getByRole("heading", { name: "Installation" }) 21 ).toBeVisible(); 22 });
  17. 24 / 68 サンプルテストを読む 各テストケースは test() 関数を使って定義する test("テストの名前", async ({

    page }) => { // テストケース本体 }); 第1引数はテスト名, 第2引数はテスト本体となる関数 テスト関数の引数として渡される page は、ブラウザで開かれ たウェブページを指すオブジェクト page.goto() で指定したURLをブラウザで開く expect(page).toHaveTitle(/Playwright/) でペ ージタイトルに "Playwright" という文字列が含まれて いることを確認する example.spec.ts 6 // Expect a title "to contain" a substring. 7 await expect(page).toHaveTitle(/Playwright/); 1 import { test, expect } from "@playwright/test"; 2 3 test("has title", async ({ page }) => { 4 await page.goto("https://playwright.dev/"); 5 8 }); 9 10 test("get started link", async ({ page }) => { 11 await page.goto("https://playwright.dev/"); 12 13 // Click the get started link. 14 await page 15 .getByRole("link", { name: "Get started" }) 16 .click(); 17 18 // Expects page to have a heading with the name of Installatio 19 await expect( 20 page.getByRole("heading", { name: "Installation" }) 21 ).toBeVisible(); 22 });
  18. 25 / 68 サンプルテストを読む example.spec.ts 1 import { test, expect

    } from "@playwright/test"; 10 test("get started link", async ({ page }) => { 11 await page.goto("https://playwright.dev/"); 12 13 // Click the get started link. 14 await page 15 .getByRole("link", { name: "Get started" }) 16 .click(); 17 18 // Expects page to have a heading with the name of Installatio 19 await expect( 20 page.getByRole("heading", { name: "Installation" }) 21 ).toBeVisible(); 22 }); 2 3 test("has title", async ({ page }) => { 4 await page.goto("https://playwright.dev/"); 5 6 // Expect a title "to contain" a substring. 7 await expect(page).toHaveTitle(/Playwright/); 8 }); 9
  19. 25 / 68 サンプルテストを読む page.goto() で指定したURLをブラウザで開く example.spec.ts 11 await page.goto("https://playwright.dev/");

    1 import { test, expect } from "@playwright/test"; 2 3 test("has title", async ({ page }) => { 4 await page.goto("https://playwright.dev/"); 5 6 // Expect a title "to contain" a substring. 7 await expect(page).toHaveTitle(/Playwright/); 8 }); 9 10 test("get started link", async ({ page }) => { 12 13 // Click the get started link. 14 await page 15 .getByRole("link", { name: "Get started" }) 16 .click(); 17 18 // Expects page to have a heading with the name of Installatio 19 await expect( 20 page.getByRole("heading", { name: "Installation" }) 21 ).toBeVisible(); 22 });
  20. 25 / 68 サンプルテストを読む page.goto() で指定したURLをブラウザで開く page.GetByRole(...).click() で"Get started" という名称のリンクをクリックする

    example.spec.ts 13 // Click the get started link. 14 await page 15 .getByRole("link", { name: "Get started" }) 16 .click(); 1 import { test, expect } from "@playwright/test"; 2 3 test("has title", async ({ page }) => { 4 await page.goto("https://playwright.dev/"); 5 6 // Expect a title "to contain" a substring. 7 await expect(page).toHaveTitle(/Playwright/); 8 }); 9 10 test("get started link", async ({ page }) => { 11 await page.goto("https://playwright.dev/"); 12 17 18 // Expects page to have a heading with the name of Installatio 19 await expect( 20 page.getByRole("heading", { name: "Installation" }) 21 ).toBeVisible(); 22 });
  21. 25 / 68 サンプルテストを読む page.goto() で指定したURLをブラウザで開く page.GetByRole(...).click() で"Get started" という名称のリンクをクリックする

    expect(page.GetByRole(...)).toBeVisible() で "Installation" という名称のヘッダが表示されてい ることを確認する example.spec.ts 18 // Expects page to have a heading with the name of Installatio 19 await expect( 20 page.getByRole("heading", { name: "Installation" }) 21 ).toBeVisible(); 1 import { test, expect } from "@playwright/test"; 2 3 test("has title", async ({ page }) => { 4 await page.goto("https://playwright.dev/"); 5 6 // Expect a title "to contain" a substring. 7 await expect(page).toHaveTitle(/Playwright/); 8 }); 9 10 test("get started link", async ({ page }) => { 11 await page.goto("https://playwright.dev/"); 12 13 // Click the get started link. 14 await page 15 .getByRole("link", { name: "Get started" }) 16 .click(); 17 22 });
  22. 26 / 68 テストを実行する 6つのテスト(2テスト × 3ブラウザ)が実行され, 6つのテストが成功 テストはデフォルトで3つのブラウザ(chromium, firefox,

    webkit)で実行される $ npx playwright test Running 6 tests using 6 workers 6 passed (3.5s) To open last HTML report run: npx playwright show-report
  23. 27 / 68 テストレポートを見る テストレポートは playwright-report 配 下に出力される $ npx

    playwright show-report Serving HTML report at http://localhost:9323. Press Ctrl+C to quit. 4/18/2024, 2:55:30 PM Total time: 3.5s example.spec.ts example.spec.ts:3 example.spec.ts:10 example.spec.ts:3 example.spec.ts:10 example.spec.ts:3 example.spec.ts:10 All 6 Passed 6 Failed 0 Flaky 0 Skipped 0 253ms has title chromium 390ms get started link chromium 672ms has title firefox 735ms get started link firefox 469ms has title webkit 730ms get started link webkit
  24. 30 / 68 4種類のテスト用API 名称 役割 実装クラス ロケーター ページ内要素の特定 Page,

    Locator ナビゲーション ページ遷移, ページ情報返却 Page アクション ユーザ操作のシミュレート Locator マッチャー 選択要素の状態と期待値の検証 LocatorAssertions など テストを記述するためのAPIは大きく4つに分類できる
  25. 32 / 68 ロケーター page.getByRole() アクセシビリティ情報(要素のロールや名称)から要素を特定する HTML要素のアクセシビリティ属性は, WAI-ARIAという仕様で定められている ▶️ HTML

    ▶️ テストコード <div> <button>更新</button> <nav><a href="/news">最新情報</a></nav> </div> test("ロール名で要素取得", async ({ page }) => { await page.goto("/") await expect(page.getByRole("button", {name: /更新/})).toBeVisible() await expect(page.getByRole("link", {name: /最新情報/})).toBeVisible() });
  26. 32 / 68 ロケーター page.getByRole() アクセシビリティ情報(要素のロールや名称)から要素を特定する HTML要素のアクセシビリティ属性は, WAI-ARIAという仕様で定められている ▶️ HTML

    ▶️ テストコード <div> <button>更新</button> <nav><a href="/news">最新情報</a></nav> </div> test("ロール名で要素取得", async ({ page }) => { await page.goto("/") await expect(page.getByRole("button", {name: /更新/})).toBeVisible() await expect(page.getByRole("link", {name: /最新情報/})).toBeVisible() }); 第一引数のロール名には ARIAロール を指定する ロールには暗黙のロール(例. a 要素なら link )と明示的な ロール( role 属性の指定)がある 第二引数のオプションとして, アクセシブル名を指定できる アクセシブル名は要素のテキストコンテンツ(可視コンテンツ) や aria-label 属性の値(不可視コンテンツ)から得られる
  27. 33 / 68 ロケーター page.getByRole() は優先的に使用すべし E2Eテストにおいて優先的に使用するべき3つの理由 HTMLタグやクラス名などDOMの具体的な構造に強く依存したテストは, 些細な内部実装の変化ですぐ壊れてしまうが, DOMの構造ではな

    く役割(role)に依存することでより壊れにくく堅牢なテストとなる アクセシビリティ情報に基づいて要素を特定することは, スクリーンリーダー等のツールがページを解釈する方法と同じであり, テス ト対象のウェブシステムがよりアクセシブルであることを保証する助けとなる 役割に基づいて要素を選択することは, 人間がウェブページをどのように認識して操作するかに近いため, テストコード自体がより直 感的で理解しやすいものとなる
  28. 34 / 68 ロケーター page.getByLabel() HTMLフォーム要素のラベルテキストから要素を特定する ▶️ HTML ▶️ テストコード

    <div> <label for="searchbox">検索</label> <input type="search" name="searchword" id="searchbox" placeholder="検索ワード"> </div> test("ラベル名で要素取得", async ({ page }) => { await page.goto("/") await expect(page.getByLabel(/検索/)).toBeVisible() });
  29. 35 / 68 ロケーター page.getByPlaceholder() HTML要素の placeholder 属性を持つ要素を特定する プレースホルダはテキストボックスにまだデータが入っていない場合に表示される文字列 ▶️

    HTML ▶️ テストコード <div> <label for="searchbox">検索</label> <input type="search" name="searchword" id="searchbox" placeholder="検索ワード"> </div> test("プレースホルダで要素取得", async ({ page }) => { await page.goto("/") await expect(page.getByPlaceholder(/検索ワード/)).toBeVisible() });
  30. 36 / 68 ロケーター page.getByText() HTML要素に含まれるテキストから要素を特定する テキストは部分文字列, 完全な文字列, 正規表現でマッチできる ▶️

    HTML ▶️ テストコード <div> <a href="/home">ホーム</a> </div> test("テキストで要素取得", async ({ page }) => { await page.goto("/") await expect(page.getByText(/ホーム/)).toBeVisible() });
  31. 37 / 68 ロケーター page.getByAltText() HTML要素の alt 属性から要素を特定する alt 属性は

    <img> タグなどで使用され, 画像の内容や機能をテキスト形式で説明する ▶️ HTML ▶️ テストコード <div> <img width="200" height="200" src="./assets/cute-dog.png" alt="かわいいわんこ" title="2024/02/21 撮影"> </div> test("alt属性で要素取得", async ({ page }) => { await page.goto("/") await expect(page.getByAltText(/かわいいわんこ/)).toBeVisible() });
  32. 38 / 68 ロケーター page.getByTitle() HTML要素の title 属性から要素を特定する title 属性は

    <a> タグや <img> タグなどの一部のタグで使用され, マウスオーバーしたときに情報(撮影日や撮影場所といったメタ情報を入れるのが正しい 使い方)を表示する ▶️ HTML ▶️ テストコード <div> <img width="200" height="200" src="./assets/cute-dog.png" alt="かわいいわんこ" title="2024/02/21 撮影"> </div> test("title属性で要素取得", async ({ page }) => { await page.goto("/") await expect(page.getByTitle("2024/02/21 撮影")).toBeVisible() });
  33. 39 / 68 ロケーター page.getByTestId() HTML要素の data-testid 属性から要素を特定する data- から始まる属性は,

    プログラムが任意で付与しても良い属性としてWHATWGでルール化されおり, data-testid はテスト用に要素を一意に特定するための 属性として慣例的に使用される ▶️ HTML ▶️ テストコード <div> <button data-testid="admin-menu">管理者メニュー</button> </div> test("data-testid 属性で要素取得", async ({ page }) => { await page.goto("/") await expect(page.getByTestId("admin-menu")).toBeVisible() });
  34. 40 / 68 ロケーター page.getByTestId() は使用すべきではない? ソースコードを見ないとわからない data-testid 利用したテストは内部実装に依存したホワイトボック ステストになってしまい,

    高水準のブラックボックステストであるべきE2Eテストでは使用すべきではない 他のロケーターが使用できない場合の最終手段として利用する
  35. 41 / 68 その他のロケーター CSSセレクタやXPathを使用したロケーター 前述の data-testid を使用したと同様、DOMの構造に強く依存したテストになってしまうため、E2Eテス トでは極力使用すべきではない await

    page.locator('button').click(); // タグで指定 await page.locator('#reset-button').click(); // id で指定 await page.locator('.primary-button').click(); // class で指定 await page.locator('//button').click(); // XPath で指定
  36. 42 / 68 ロケーターまとめ 名称 役割 優先度 page.getByRole() アクセシビリティ情報から要素を特定する ◎

    page.getByLabel() HTMLフォーム要素のラベルテキストから要素を特定する ◯ page.getByPlaceholder() HTML要素の placeholder 属性を持つ要素を特定する ◯ page.getByText() HTML要素に含まれるテキストから要素を特定する ◯ page.getByAltText() HTML要素の alt 属性から要素を特定する ◯ page.getByTitle() HTML要素の title 属性から要素を特定する ◯ page.getByTestId() HTML要素の data-testid 属性から要素を特定する △ page.locator() CSSセレクタやXPathから要素を特定する ×
  37. 43 / 68 ナビゲーション ナビゲーションはURLに関連する「アクション」である ▶️ 基本的なナビゲーション goto() メソッドにて指定されたページをロードする await

    を付与することで, 対象のページにて load イベント が発生するのを待機する ▶️ ナビゲーションの待機 ボタンクリックによりサーバアクセスを伴う処理の後にページ遷移が行われる場合など, 必要に応じてURLの変化を待機することができる 例えば, 後続の結果確認のロケーターが遷移前のページの要素にアクセスして誤った検証を行なってしまう可能性がある場合に使用する https://playwright.dev/docs/navigations await page.goto("http://example.com") [1] await page.getByText("Click me").click(); await page.waitForURL("**/login"); [*1] load イベントは, CSS, JavaScript, 画像などの依存リソースを含め、ページ全体が読み込まれたときに発生する
  38. 44 / 68 アクション アクションはロケーターで選択した要素に対して, キーボード操作やマウス操作などのユーザ操作をシミュレートする アクション可能性について アクションは実際のウェブ操作と近くなるようPlaywrightが面倒をみてくれている display: none

    や disabled 属性が設定されている要素に対するアクションは実行されない readonly 属性が設定されている要素に対して入力はできない 他の要素がオーバーレイして隠されている要素に対してマウス操作はできない ただし {force: true} オプションを付与することでバイパス可能 https://playwright.dev/docs/input
  39. 45 / 68 アクション fill() / clear() ロケーターで選択した要素がテキストボックスだった場合, fill() で入力,

    clear() で入力値のクリ アを行う await page.getByRole("textbox", {name: /username/i}).fill("Peter Parker") await page.getByRole("textbox", {name: /organization/i}).clear() キーボード操作
  40. 46 / 68 アクション press() キーストロークを生成する pressSequentially() 実際のキーボードで入力するようにフィールドに1文字ずつ入力する これにより keydown

    , keyup , keypress の全てのキーボードイベントを適切に発生させることができる キーボード操作 await page.getByRole("textbox").press("Enter") await page.getByRole("textbox").press("Control+KeyA") await page.getByRole("textbox").pressSequentially("hello")
  41. 48 / 68 アクション selectOption() 単一選択のみの <select> 要素(combobox ロール)に対しては selectOption()

    を使って選択する multiple 属性がついた <select> 要素(listbox ロール)に対しては配列を指定することで複数要素 を選択できる セレクトボックスの選択 await page.getByRole("combobox", {name: /ペット/}).selectOption("ハムスター") await page.getByRole("listbox", {name: /飲み物/}).selectOption(["コーヒー", "ルートビア"])
  42. 49 / 68 アクション click() / dblclick() シングルクリック / ダブルクリックを行う

    オプションの { button: "right"} や {button: "middle"} を指定することで、右クリックや中クリックイベントを発行できる {modifiers: ["Control"]} といった形式で、装飾キーを押しながらクリックするイベントを発行できる (修飾キーには "Alt", "Control", "Meta", "Shift" が指定可能) 単純なマウス操作 await page.getByRole("button").click(); await page.getByText("Item").dblclick(); await page.getByText("Item").click({ button: 'right' }); await page.getByText('Item').click({ modifiers: ['Shift'] });
  43. 50 / 68 アクション mouse.move() / mouse.up() / mouse.down() 座標を指定して操作することで,

    ドラッグアンドドロップを再現できる 座標を指定したテストは壊れやすいため, 用法・用量を守ること クリック可能エリアを何か識別可能にするなど, テストしやすい構造にすることも検討する 高度なマウス操作 // x:100, y:100 の地点から x:300, y:100 の地点までドラッグアンドドロップ await page.mouse.move(100, 100); await page.mouse.down(); await page.mouse.move(300, 100); await page.mouse.up();
  44. 51 / 68 マッチャー 選択した要素の状態と期待値の検証を行う(検証するためのメソッド群) マッチャーは検証対象となるロケーターを引数する expect() 関数の戻り値のオブジェクト( LocatorAssertions クラス)のメソ

    ッドとして提供される 提供されているマッチャー await expect(locator).toBeAttached() await expect(locator).toBeChecked() await expect(locator).toBeDisabled() await expect(locator).toBeEditable() await expect(locator).toBeEmpty() await expect(locator).toBeEnabled() await expect(locator).toBeFocused() await expect(locator).toBeHidden() await expect(locator).toBeInViewport() await expect(locator).toBeVisible() await expect(locator).toContainText() await expect(locator).toHaveAttribute() await expect(locator).toHaveClass() await expect(locator).toHaveCount() await expect(locator).toHaveCSS() await expect(locator).toHaveId() await expect(locator).toHaveJSProperty() await expect(locator).toHaveScreenshot() await expect(locator).toHaveText() await expect(locator).toHaveValue() await expect(locator).toHaveValues() await expect(page).toHaveScreenshot() await expect(page).toHaveTitle() await expect(page).toHaveURL() await expect(response).toBeOK() https://playwright.dev/docs/test-assertions await expect(locator).toContainText()
  45. 52 / 68 マッチャー toContainText() / toHaveText() / toBeVisible() /

    toBeAttached() ボタン操作やリンク操作など何らかのアクションに伴い, 何らかの最終結果の表示を確認する ▶️ HTML ▶️ テストコード toContainText() は部分一致, toHaveText() は完全一致となるが, 正規表現を使用する場合は実質的に差はない <h1>Success</h1> // このページの見出しには "Success" のテキストが含まれる await expect(page.getByRole('heading')).toContainText(/Success/) // このページの見出しは "Success" のテキストを持っている await expect(page.getByRole('heading')).toHaveText(/Success/) // このページには "Success" という名前の見出しがある await expect(page.getByRole('heading', {name: /Success/})).toBeVisible() // このページには見出しがある await expect(page.getByRole('heading')).toBeAttached()
  46. 53 / 68 マッチャー toBeChecked() チェックボックスにチェックが入っていることを確認する ▶️ HTML ▶️ テストコード

    <label for="verification">私は18歳以上です</label> <input type="checkbox" id="verification" checked> await expect(page.getByRole("checkbox", {name: /18歳以上です/})).toBeChecked()
  47. 54 / 68 マッチャー toBeDisabled() / toBeEnabled() 要素が利用できるかできないか( disabled 属性により非活性化されているかどうか)を確認する

    ▶️ HTML ▶️ テストコード <button disabled>送信</button> <button>リセット</button> await expect(page.getByRole("button", {name: /送信/})).toBeDisabled() await expect(page.getByRole("button", {name: /リセット/})).toBeEnabled()
  48. 56 / 68 マッチャー toBeHidden() 要素が存在しない or 不可視状態であることを確認する ▶️ HTML

    ▶️ テストコード 先ほどの toBeEmpty() に似ているが, toBeHidden() は要素自体が存在しないときや, display: none や visibility: hidden が設定されている場合に もマッチする。 <li></li> await expect(page.getByRole("listitem")).toBeHidden()
  49. 57 / 68 マッチャー toHaveValue() / toHaveValues() フォーム要素が持つ値を検証する ▶️ HTML

    ▶️ テストコード toHaveValues() は文字列や正規表現の配列を引数に指定することで、マルチセレクトボックス( <select multiple> )の選択結果の検証に使用する <label for="email">Email</label> <input type="email" id="email" name="email" required/> await page.getByRole("textbox", {name: /Email/}).fill("[email protected]") await expect(page.getByRole("textbox", {name: /Email/})).toHaveValue("[email protected]")
  50. 58 / 68 マッチャー .not マッチャーの前に not をつけることで否定の意味になる ▶️ HTML

    ▶️ テストコード マッチャーには否定の意味のメソッドを持つものも一部ある( toBeEnabled() に対する toBeDisabled() , toBeVisible() に対する toBeHidden() )ため, これらに該当しないマッチャーの否定が必要な場合は .not を利用する <h1>Failure</h1> await expect(page.getByRole("heading")).not.toContainText(/Success/);
  51. 59 / 68 改めてサンプルコード cf. https://demo.playwright.dev/todomvc/#/ demo-todo-app.spec.ts より抜粋 This is

    just a demo of TodoMVC for testing, not the real TodoMVC app. Double-click to edit a todo Double-click to edit a todo Created by Created by Remo H. Jansen Remo H. Jansen Part of Part of TodoMVC TodoMVC todos What needs to be done? test.beforeEach(async ({ page }) => { await page.goto('https://demo.playwright.dev/todomvc'); }); const TODO_ITEMS = [ 'buy some cheese', 'feed the cat', 'book a doctors appointment' ]; test('should allow me to add todo items', async ({ page }) => { // create a new todo locator const newTodo = page.getByPlaceholder('What needs to be done?'); // Create 1st todo. await newTodo.fill(TODO_ITEMS[0]); await newTodo.press('Enter'); // Make sure the list only has one todo item. await expect(page.getByTestId('todo-title')).toHaveText([ TODO_ITEMS[0] ]); // Create 2nd todo. await newTodo.fill(TODO_ITEMS[1]); await newTodo.press('Enter'); // Make sure the list now has two todo items. await expect(page.getByTestId('todo-title')).toHaveText([ TODO_ITEMS[0],
  52. 60 / 68 Playwrightの設定ファイル playwright.config.ts より抜粋 import { defineConfig, devices

    } from "@playwright/test"; export default defineConfig({ testDir: "./tests", /* Configure projects for major browsers */ projects: [ { name: "chromium", use: { ...devices["Desktop Chrome"] }, }, { name: "firefox", use: { ...devices["Desktop Firefox"] }, }, { name: "webkit", use: { ...devices["Desktop Safari"] }, }, ], });
  53. 62 / 68 何をテストするか やみくもにE2Eを作成するのはよくない E2Eテストはメンテナンスコストも実行時間も大きいため, 効果のないテストを増やすことによるデメリッ トが大きい 手動でおこなって効果のないテストを自動化しても無駄である  

    そもそも、テストプロセス(e.g.テスト分析、テスト設計、テスト実装、報告)、特にテスト分析、テスト設計が適切に行われていな いテストは、優秀なテスターが手動でテストを実施したところで、テストに期待される動作の保証やバグの検出といった効果を発揮し ない。いわんや、自動テストにおいておや、である。 cf. テスト自動化研究会 - テスト自動化の8原則 Playwright自体はあくまで手段(How)であり, E2Eテストとして「何をテストするのか(What)」を適切に 決定することが重要となる
  54. 64 / 68 何をテストするか シナリオを考えるポイント コアとなる正常系のユーザシナリオを考える 例. 社内の検索フォームのユーザシナリオ 1. ログインする

    2. 検索フォームのあるページへ移動する 3. フォームに値を入力する 4. 検索ボタンを押下する 5. 検索結果が表示される ビジネス上の影響が大きいユーザシナリオを考える 例. 会員登録や購入・課金など
  55. 65 / 68 ここまでのまとめ 1. 導入編 ソフトウェアテストとは何かを学ぶ E2Eテストとは何かを学ぶ Playwrightとは何かを学ぶ 2.

    基礎編 Playwrightを動かしてみる PlaywrightによるE2Eテストの書き方を学ぶ 3. 応用編 E2Eテストの戦略を学ぶ
  56. 67 / 68 会社紹介 未来報 フューチャーのオウンドメディアです。人、カルチャー、仕事などが知れる! https://note.future.co.jp/ 技術ブログ 最新の技術トピックから初心者向けのおすすめ記事まで技術視点でFutureの魅力がわかる! https://future-architect.github.io/

    Tech Cast 社員が有志で運営しているラジオ放送です。今話題になっていることから社員が語りたいことをひたすら語る会まで様々なラインナッ プがございます! https://open.spotify.com/show/6ntaBQ28GSDaOztBphH6oi About Future