RAKUS Developers Blog | ラクス エンジニアブログ

株式会社ラクスのITエンジニアによる技術ブログです。

【Playwright】ココがスゴいぜ!Playwright Component Test!

こんにちは。フロントエンド開発課所属のkoki_matsuraです。
今回はPlaywrightのコンポーネントテストについて個人的な意見を書いています。
目次は以下の通りになっています。

はじめに

PlaywrightはMicrosoftが開発・メンテナンスしているCypress、Puppeteerなどと同じE2E自動テストフレームワークとして有名です。

playwright.dev

Chromium、Edge、Firefoxなどの複数のブラウザに対応しており、全てに単一のAPIで簡単にテストの実装が可能になっています。
目玉機能としては、コード生成が挙げられるのではないでしょうか。
コード生成とはユーザが画面を実際に触り、それがコードとして自動で反映されていく機能で手っ取り早くテストを開始するには最適な機能です。
他にも素晴らしい機能があるのですが、その中でも現在、実験的に試されているComponent Test Runnerがとても興味をそそられました。
そこで導入方法とPlaywright Component Test Runnerの個人的に凄いと思った点を紹介していきます。

導入方法

プロジェクトのトップで下記のコマンドを実行してください。通常のPlaywright導入コマンドにオプションでctをつけたような形になっています。

npm init playwright@latest -- --ct

どのフレームワークに向けて使うのかを聞かれるので、該当するものを選択してください。今回はReact18を選択します。

次はブラウザをインストールするかを聞かれますが、デフォルト(true)にします。
これで導入が完了しました。
プロジェクトのディレクトリには3つのファイルが追加されていると思います。以下がそれぞれの簡単な役割となっています。

テストファイルを作成すれば、npm run test-ctでテストが実行できます。

Playwright Component Test Runner のスゴい点

コンポーネントが実際にレンダリングされる

昨今のコンポーネントテストでよく使われているテストランナーはVitest・Jestではないでしょうか。そして、テストランナーとともに使われるJSDomなどはNode.js環境でDOMをエミュレートするためのライブラリであり実際のブラウザにレンダリングされているわけではありません。
ブラウザと完全に同じレンダリングになるかわからないという問題があります。
しかし、Playwrightはindex.htmlを通して、対象のコンポーネントが実際のブラウザにレンダリングされます。
つまり、ユーザの環境に限りなく近い状態でテストが可能となります。
また、副作用的なメリットとして今まではNode.js上でDOMをエミュレートしていたため、testing-libraryやuser-eventが必要になってましたが、それも要らなくなります。
Playwright Component Test Runnerのみで基本的なテストはできます。
以下はチェックボックスのテストを例に比較したものです。
内容自体は同じですが、簡潔に書けていることがわかります。

// @playwright/experimental-ct-reactを使わない例
import { AddressForm } from "../../Checkout/AddressForm";
import { render } from "@testing-library/react";
import { screen } from "@testing-library/dom";
import { it, expect } from "vitest";
import userEvent from "@testing-library/user-event";
import "@testing-library/jest-dom/vitest";

it("チェックボックスをクリックするとcheck状態が切り替わる", async () => {
  render(<AddressForm />);
  const user = userEvent.setup();
  const checkbox = screen.getByRole("checkbox") as HTMLInputElement;
  await user.click(checkbox);
  expect(checkbox).toBeChecked();
});
// @playwright/experimental-ct-reactを使う例
import { test, expect } from "@playwright/experimental-ct-react";
import { AddressForm } from "../../Checkout/AddressForm";

test("チェックボックスをクリックするとcheck状態が切り替わる", async ({
  mount,
}) => {
  const component = await mount(<AddressForm />);
  const checkbox = component.getByRole("checkbox");
  await checkbox.click();
  expect(checkbox).toBeChecked();
});

画面のサイズを指定できる

コンポーネントには画面のサイズによって見た目が変わるレスポンシブなものもよくあります。
このようなコンポーネントをviewportを変えてテストするようなことはあまりないと思います。
そもそも、そのようなテストをVitest・Jestでするにはwindow.innerWidthやwindow.innerHeight、そしてresizeイベントなどで実装することになり非常に困難です。
だからと言って、E2Eテストで行うには適さないため、手動で確かめることが必要とされることが多いのでないでしょうか。
Playwright Component Testはそれを解決します。
方法は以下のようにviewportを設定するだけです。 テストファイルに書けば、個々に設定でき、playwright-ct.config.tsに書けばテスト全体に設定可能です。

import { test } from '@playwright/experimental-ct-react';

test.use({ viewport: { width: 1280, height: 720 } });

例えば、以下のような一定の幅になるとサイドバーがハンバーガーメニューになるコンポーネントがあります。

このコンポーネントに対し、次のようなテストで対応します。

test.use({ viewport: { width: 599, height: 720} });

test('widthが599pxの時にハンバーガーメニューになっていること', async ({ mount }) => {
    const sideMenu = await mount(<SideMenu />);
    const hamburgerButton = sideMenu.getByRole('button');
    await expect(hamburgerButton).toBeVisible();
})

テストを実行するとwidth: 599px、height: 720pxでレンダリングされます。
サイドバーになることを確認する場合もviewportのサイズを変えてテストするだけ大丈夫です。 また、画面サイズではなく、iOSなどの様々なデバイスを指定したい場合もplaywright-ct.config.tsから変更できます。

タイムゾーンや言語を指定できる

時間によって見た目が変わったり、ブラウザの言語設定によってテキストが変わるようなコンポーネントがあると思います。
以下はブラウザの言語設定によってテキストが変わるサイドバーです。

これをVitest・Jestで見た目部分のテストをするとなると非常に困難で、手動で確認したり、VRT(Visual Regresion Test)で対応することになります。
Playwrightはこれにも対応しており、先ほど同様にタイムゾーンや言語を下記のようにテストファイルに書くことで設定できます。

test.use({
  locale: "en-GB",
  timezoneId: "Europe/Paris",
});

これでブラウザの読み込み時の言語やタイムゾーンが変わるのですが、例えば、多言語対応のためにi18nextなどのライブラリを使っている場合は上手くいきません。Providerがないため、Playwrightのブラウザでは以下のようなレンダリングになります。

これへの対応方法は@playwright/experimental-ct-reactをインストールした際に自動で作成されるplaywright/index.tsxにProviderを書きます。

import {beforeMount } from '@playwright/experimental-ct-react/hooks';
import { I18nextProvider } from 'react-i18next';
import i18n from '../src/i18n/configs';

beforeMount(({ App }) => {
    return (
        <I18nextProvider i18n={i18n}>
            <App />
        </I18nextProvider>
    );
});

そして、コンポーネントテストファイル側では特に気にせず、いつも通りのテストを書くだけです。

test.use({ viewport: { width:600 , height: 720}, locale: 'ja' });

test('lang="ja"の時は日本語で表示されていること', async ({mount}) => {
    const sideMenu = await mount(<SideMenu />);
    const listRoot = sideMenu.locator('.MuiList-root');

    const inbox = await listRoot.locator('li').nth(0).innerText();
    const favorite = await listRoot.locator('li').nth(1).innerText();
    const trash = await listRoot.locator('li').nth(2).innerText();
    const sent = await listRoot.locator('li').nth(3).innerText();

    expect(inbox).toBe('受信トレイ');
    expect(favorite).toBe('お気に入り');
    expect(trash).toBe('ゴミ箱');
    expect(sent).toBe('送信済み');
});

Playwrightのブラウザでも問題なく日本語でレンダリングされていることが確認できます。

コンポーネントの振る舞いを見るテストに対応できる

PlaywrightのようなE2Eテストフレームワークでは当然なのですが、実際のブラウザにレンダリングするという特徴が非常に強力で、例で紹介したようなVitest・Jestでは対応が困難であった画面サイズやタイムゾーンに依存するコンポーネントのテストや今までは仕方なく手動で確認していたテストE2Eのような単体テストコードの改善などのコンポーネントの振る舞いを見るテストに最適です。
しかし、コンポーネント内部のロジック面のテストにおいては今までのようにVitest・Jestで対応するのが良いと思います。

まとめ

今回はPlaywright Component Test Runnerについて個人的に凄いと思った点について軽く紹介させていただきました。
既存のPlaywrightにmount関数を組み合わせたシンプルな形でありながら、今まではPage単位だったテストをComponent単位にすることを可能にしてくれました。
Vitest・Jestと少し比較するような書き方をしてしまいましたが、どちらかのみではなく両方 を上手く使い分けることでよりコンポーネントの品質を高く保てるのではないかと考えています。
また、Playwright Component Test Runnerが登場したことにより、本来Vitest・Jestがどのようなテストに取り組むべきなのかがわかってきたような気もします。
まだ実験的機能ですが、正式に公開された際にはコンポーネントテストに積極的に取り込んでいきます!
最後まで読んでいただきありがとうございます。

参考

playwright.dev

Copyright © RAKUS Co., Ltd. All rights reserved.