こんにちは、エンジニアのぱやぴです!

前回は「Playwright 実践~超簡単 4Step~」を解説しました。

今回は、Microsoft Visual Studio Code(以下VSCode)とPlaywrightを使って、ウェブサイトのテスト自動化を実現する方法をご紹介します。具体的には、こちらのウェブサイトを例に、テストコードの作成から実行までの流れを解説します。

環境

  • Node.js 22.4.1
  • VSCode 1.91.0
  • Playwright 1.45.1

準備

Node.jsVSCodeのインストールは割愛させていただきます。

Playwrightのインストール

  1. VSCodeのPlaywrightの拡張機能をインストールする
    Playwright Test for VSCode – Visual Studio Marketplace
  1. Playwrightのインストール
    VSCodeのコマンドパレットを開き(Windows: shift + ctrl + p, mac: shift + command + p) Test: Install Playwright
    を入力、選択しPlaywrightのインストールを行います。

  以下必要なものにチェックを入れOKを押下。

  1. ターミナルに以下の文章が出ればインストール完了です。

実行

PlaywrightのRecord機能を使ってテストコードを生成

Playwrightには、ブラウザでの操作を記録し、自動的にテストコードを生成するRecord機能があります。

  1. VSCodeのサイドバーの以下のアイコンをクリック
  1. Record new をクリック
  1. 開いたブラウザで、テストしたい操作を行います。 例えば、ページのタイトル確認、メニューアイテムの確認、特定のセクションの存在確認などを行います。
  1. 以下のようなコードが自動生成されました。
import { test, expect } from '@playwright/test';

test('test', async ({ page }) => {
  await page.goto('https://agest.co.jp/');
  await page.locator('#gh_search').click();
  await page.getByRole('searchbox', { name: 'キーワードを入力' }).click();
  await page.getByRole('searchbox', { name: 'キーワードを入力' }).fill('テスト');
  await page.getByRole('searchbox', { name: 'キーワードを入力' }).press('Enter');
  await page.getByRole('link', { name: 'テストアウトソースにおけるアウトプット資料例 本資料では、アウトプット資料のイメージをご紹介しています。[…]' }).click();
  await page.getByRole('link', { name: '株式会社AGEST(アジェスト)' }).click();
});

テストの実行

  1. 先ほどできたテストコードの「Run Test」ボタンをクリックして、テストを実行します。
  1. ここでshow borwserやshow trace viewer にチェックをいれると視覚的にテスト実行を見ることができます。
  1. テストを実行することができました!

おまけ: 生成されたコードをLLMに渡しPage Object Model(以下POM)に変換

このままでもテストを実行することができるのですが、テストコードの可読性とメンテナンス性を向上させるために、生成されたコードをPOMに変換します。

 💡 POMとは?
POM (Page Object Model)は、Webアプリケーションのテスト自動化のためのデザインパターンです。以下にPOMの主なポイントを簡潔にまとめます。
- 各Webページを独立したクラスとしてモデル化する
- ページ要素(ボタン、フォームなど)をプロパティとして定義する
- ページ上の操作をメソッドとして実装する
- テストコードとページ操作を分離し、保守性を向上させる
- 要素のセレクタ変更時に修正箇所を局所化できる
- コードの再利用性が高まる
- テストコードがより読みやすく、理解しやすくなる
- ページ間の遷移を明確に表現できる
- テストフレームワークに依存せず使用可能
POMを活用することで、テストコードの保守性、再利用性、可読性が向上し、長期的なテスト自動化の効率が高まります。

変換にはLLM(ChatGPT, Gemini, Copilot, Claude等)を用います。LLMの利用は自己判断でお願いいたします。 今回はGithub Copilot ChatとClaude3.5 sonnetを用いました。

1. Github copilot

  1. 先ほど生成されたコードを以下のようなプロンプトでLLMに渡します。
以下のplaywrightのコードをPOM(page object model)に変換してください。

test('agestHP test', async ({ page }) => {
  await page.goto('https://agest.co.jp/');
  await page.locator('#gh_search').click();
  await page.getByRole('searchbox', { name: 'キーワードを入力' }).click();
  await page.getByRole('searchbox', { name: 'キーワードを入力' }).fill('テスト');
  await page.getByRole('searchbox', { name: 'キーワードを入力' }).press('Enter');
  await page.getByRole('link', { name: 'テストアウトソースにおけるアウトプット資料例 本資料では、アウトプット資料のイメージをご紹介しています。[…]' }).click();
  await page.getByRole('link', { name: '株式会社AGEST(アジェスト)' }).click();
});
  1. 以下のような回答が返ってきました。
  1. 回答を基にpagesフォルダを作成、その中にAgestHomePage.tsファイルを作成し、以下のようなPage Objectクラスを定義します。:
import { Page } from "@playwright/test";

export class AgestHomePage {
  page: Page;

  constructor(page) {
    this.page = page;
  }

  async navigate() {
    await this.page.goto("https://agest.co.jp/");
  }

  async searchForKeyword(keyword) {
    await this.page.locator("#gh_search").click();
    await this.page
      .getByRole("searchbox", { name: "キーワードを入力" })
      .click();
    await this.page
      .getByRole("searchbox", { name: "キーワードを入力" })
      .fill(keyword);
    await this.page
      .getByRole("searchbox", { name: "キーワードを入力" })
      .press("Enter");
  }

  async clickOnTestOutsourceLink() {
    await this.page
      .getByRole("link", {
        name: "テストアウトソースにおけるアウトプット資料例 本資料では、アウトプット資料のイメージをご紹介しています。[…]",
      })
      .click();
  }

  async clickOnAgestLink() {
    await this.page
      .getByRole("link", { name: "株式会社AGEST(アジェスト)" })
      .click();
  }
}
  1. testsフォルダ内の先程のtest-1.spec.tsagestHomePage.spec.tsとリネーム、ファイル内容を回答を基に先ほど作ったクラスを用いるように更新します。
import { test } from '@playwright/test';
import { AgestPage } from '../pages/AgestPage';

test('AGESTのウェブサイトのテスト', async ({ page }) => {
  const agestPage = new AgestPage(page);

  await agestPage.goto();
  await agestPage.verifyTitle();
  await agestPage.verifyLogo();
  await agestPage.verifyMenuItems();
  await agestPage.verifyCaseStudySection();
  await agestPage.verifyFooter();
});

テストは実行できましたが、以下の理由からPOMとして正しく汎用的に作成できていません。

  • 各Webページを独立したクラスとしてモデル化できておらず、agestHomePageとして集約されている
  • ページ要素(ボタン、フォームなど)をプロパティとして定義できてない

2. Claude 3.5 sonnetでもう一度

  1. Claude3.5で同じプロンプトで実行し、出力された結果が以下です。
// AgestHomePage.js
class AgestHomePage {
  constructor(page) {
    this.page = page;
    this.searchButton = page.locator('#gh_search');
    this.searchBox = page.getByRole('searchbox', { name: 'キーワードを入力' });
  }

  async goto() {
    await this.page.goto('https://agest.co.jp/');
  }

  async clickSearch() {
    await this.searchButton.click();
  }

  async searchFor(keyword) {
    await this.searchBox.click();
    await this.searchBox.fill(keyword);
    await this.searchBox.press('Enter');
  }
}

// SearchResultsPage.js
class SearchResultsPage {
  constructor(page) {
    this.page = page;
    this.testOutsourceLink = page.getByRole('link', { name: 'テストアウトソースにおけるアウトプット資料例 本資料では、アウトプット資料のイメージをご紹介しています。[…]' });
  }

  async clickTestOutsourceLink() {
    await this.testOutsourceLink.click();
  }
}

// TestOutputPage.js
class TestOutputPage {
  constructor(page) {
    this.page = page;
    this.companyLink = page.getByRole('link', { name: '株式会社AGEST(アジェスト)' });
  }

  async clickCompanyLink() {
    await this.companyLink.click();
  }
}

// test.spec.js
const { test, expect } = require('@playwright/test');
const { AgestHomePage } = require('./AgestHomePage');
const { SearchResultsPage } = require('./SearchResultsPage');
const { TestOutputPage } = require('./TestOutputPage');

test('agestHP test', async ({ page }) => {
  const homePage = new AgestHomePage(page);
  const searchResultsPage = new SearchResultsPage(page);
  const testOutputPage = new TestOutputPage(page);

  await homePage.goto();
  await homePage.clickSearch();
  await homePage.searchFor('テスト');

  await searchResultsPage.clickTestOutsourceLink();

  await testOutputPage.clickCompanyLink();
});
  1. このままでは動かない & typescriptではなかったため、回答を基にして以下の様に修正しました。 修正点は主に各ページクラスのpage, searchButton等のページ要素の変数定義と型指定です。 ”playwright/test”からLocator, Pageをimportし変数を定義し、ファイルを作成しました。
import { Locator, Page } from "playwright/test";

export class AgestHomePage {
  page: Page;
  searchButton: Locator;
  searchBox: Locator;

  constructor(page: Page) {
    this.page = page;
    this.searchButton = page.locator("#gh_search");
    this.searchBox = page.getByRole("searchbox", { name: "キーワードを入力" });
  }

  async goto() {
    await this.page.goto("<https://agest.co.jp/>");
  }

  async clickSearch() {
    await this.searchButton.click();
  }

  async searchFor(keyword) {
    await this.searchBox.click();
    await this.searchBox.fill(keyword);
    await this.searchBox.press("Enter");
  }
}
import { Locator, Page } from "playwright/test";

export class TestOutputPage {
  page: Page;
  companyLink: Locator;

  constructor(page: Page) {
    this.page = page;
    this.companyLink = page.getByRole("link", {
      name: "株式会社AGEST(アジェスト)",
    });
  }

  async clickCompanyLink() {
    await this.companyLink.click();
  }
}
import { Locator, Page } from "playwright/test";

export class SearchResultsPage {
  page: Page;
  testOutsourceLink: Locator;
  constructor(page: Page) {
    this.page = page;
    this.testOutsourceLink = page.getByRole("link", {
      name: "テストアウトソースにおけるアウトプット資料例 本資料では、アウトプット資料のイメージをご紹介しています。[…]",
    });
  }

  async clickTestOutsourceLink() {
    await this.testOutsourceLink.click();
  }
}
import { test, expect } from "@playwright/test";
import { AgestHomePage } from "../pages/agestHP";
import { TestOutputPage } from "../pages/TestOutputPage";
import { SearchResultsPage } from "../pages/SearchResultsPage";

test("agestHP test", async ({ page }) => {
  const homePage = new AgestHomePage(page);
  const searchResultsPage = new SearchResultsPage(page);
  const testOutputPage = new TestOutputPage(page);

  await homePage.goto();
  await homePage.clickSearch();
  await homePage.searchFor("テスト");

  await searchResultsPage.clickTestOutsourceLink();

  await testOutputPage.clickCompanyLink();
});

少し修正は必要でしたが、Claude3.5 sonnetでは正しくPOMとして出力されました!

よりプロンプトを詳細にかければ、修正も必要なくPOMとして出力できるかと思いますが、今回のような簡単なプロンプトでもPOMの雛形を簡単に作成することができました。 これでページごとにクラスオブジェクトとして分けることができ、テストも実行することができました。

まとめ

VSCodeとPlaywrightを組み合わせることで、効率的にウェブサイトのテスト自動化を実現できます。PlaywrightのRecord機能を使えば、簡単にテストコードを生成でき、それをPOMに変換することで、より保守性の高いテストコードを作成できます。

今回は弊社のウェブサイトを例に取りましたが、同様の方法で他のウェブサイトのテストも作成できます。自動化されたテストを導入することで、ウェブサイトの品質を継続的に確認し、問題を早期に発見することができます。

また、LLMによる「PlaywrightコードからPOM自動生成」を試みた際、使用するLLMによって精度も違うことがわかり、こちらも非常に勉強になりました。

ぜひ、皆さんのプロジェクトでもVSCode、Playwrightを活用して、効率的なテスト自動化を実現してみてください!

ここまで読んでいただきありがとうございました。

▼前回の記事はこちら

SHARE

  • facebook
  • twitter

SQRIPTER

AGEST Engineers

AGEST

記事一覧

AGESTのエンジニアが情報発信してます!
AGESTのサービスやソリューションのお問い合わせページはこちらです。

株式会社AGEST

Sqriptsはシステム開発における品質(Quality)を中心に、エンジニアが”理解しやすい”Scriptに変換して情報発信するメディアです

  • 新規登録/ログイン
  • 株式会社AGEST