ウェブサイト検索

Playwrightを使用してReactアプリケーションのコンポーネントテストを実装する方法


著者は、Write for DOnationsプログラムの一環としてOpen Sourcing Mental Illnessに寄付を受けることを選びました。

紹介

テストは、ソフトウェア製品が期待通りに動作することを保証するために非常に重要です。伝統的に、品質保証(QA)チームはウェブアプリケーションのために多くのエンドツーエンドテストを手動で実行しています。しかし、QAチームはテストデータの準備、テストの実施、そしてそれらを維持するためにかなりの努力を費やしています。テストは、複数のサービス、サードパーティの依存関係、およびデータベース間の統合を必要とするため、しばしば不安定です。これらの問題が、ウェブアプリケーションに対するコンポーネントテストが重要であり、現在、ソフトウェア開発者とQAエンジニアの両方から注目を集めている理由です。コンポーネントテストを実施することで、彼らはグラフィカルユーザーインターフェース(GUI)を早期にテストすることができ、テストの実施、作成、実行、維持に必要な時間と労力を削減することができます。

Playwrightは、エンドツーエンドのテストおよび一般的なウェブブラウザとの対話のための便利なツールであり、最近コンポーネントテストのサポートを開始しました。Playwrightを使用すると、開発者はバックエンドサービスが完成するのを待つことなく、モックサーバーに多くのモックAPIを作成することなく、GUIのテストを書くことができます。さらに、Playwrightは、ユーザーインターフェースを構築するためのオープンソースのJavaScriptライブラリであるReactなど、一部の人気ウェブフレームワークによるコンポーネントテストをサポートする機能も備えています。また、ChromiumやFirefox、WebKitベースのブラウザなど、主要なブラウザのサポートも含まれています。Playwrightでは、Java、Python、C#、Node.jsなどの複数のクライアント言語を使用してテストを書くこともできます。

このチュートリアルでは、既存のReactアプリケーションに対してコンポーネントテストのためにPlaywrightを実装します。Reactを使用して天気アプリをデプロイし、その後Playwrightを使用してアプリのコンポーネントテストを書いて実行し、コンポーネントが正しく動作していることを確認します。

前提条件

このチュートリアルに従うには、以下のものが必要です:

  • sudo権限を持つ非ルートユーザーと少なくとも2GBのRAMを搭載したUbuntu 20.04サーバー。開始するには、Ubuntu 20.04の初期サーバー設定ガイドに従ってください。

  • サーバーにNode.jsバージョン16以上をセットアップしてください。Node.jsをインストールするには、Ubuntu 20.04にNode.jsをインストールする方法のチュートリアルのオプション2に従って、正しいバージョンをインストールしていることを確認してください。

  • サーバーにGitがインストールされています。まだインストールされていない場合は、Ubuntu 20.04にGitをインストールする方法に関するチュートリアルをお読みください。

  • Reactを使用したJavaScriptによるフロントエンド開発の知識。

  • エンドツーエンドテスト、ユニットテスト、APIテストの知識。

ステップ1 — Reactを使用して天気アプリを展開する

このステップでは、このチュートリアルに必要なReactベースの天気アプリをデプロイします。このアプリは、オープンウェザーのAPIの無料版を使用して、任意の都市の現在の天気データにアクセスします。

アプリケーションをサーバーにデプロイするには、まずアプリケーションのコードを保存する新しいディレクトリを作成し、その後作成したディレクトリに移動します。Projectsを好みのディレクトリ名に置き換えることを忘れないでください:

mkdir Projects
cd Projects

次に、GitHubからアプリケーションコードをクローンします:

git clone https://github.com/do-community/react-component-testing-demo.git

次に、react-component-testing-demoディレクトリに移動し、git checkoutコマンドを使用してappブランチをチェックアウトします:

cd react-component-testing-demo
git checkout app

次に、天気アプリケーションの依存関係をインストールします:

npm install

インストールが完了したら、アプリケーションを開いてください:

npm run start

コンソールには次の出力が表示されます:

Compiled successfully!

You can now view demo-playwright-component-testing in the browser.

  Local:            http://localhost:3000/demo-playwright-component-testing
  On Your Network:  http://192.168.1.7:3000/demo-playwright-component-testing

Note that the development build is not optimized.

注意: これらのコマンドをグラフィカルユーザーインターフェース(GUI)をサポートするローカルマシンで実行し、すでにChromeやFirefoxのようなブラウザがインストールされている場合、出力に表示されるURL(http://localhost:3000/demo-playwright-component-testing)に移動すると天気アプリが開きます。ただし、リモートサーバーでこれらのコマンドを実行している場合は、SSHを介してポートフォワーディングを使用して、ローカルマシンでアプリを開く必要があります。

新しいターミナルで、以下のコマンドを実行してください:

ssh -L 3000:localhost:3000 your_non_root_user@your_server_ip

サーバーに接続したら、ローカルマシンのウェブブラウザで http://localhost:3000/demo-playwright-component-testing に移動してください。このチュートリアルの残りの間、2番目のターミナルは開いたままにしておいてください。

あなたのブラウザはアプリケーションのホームページを開きます。そこには、ユーザーが天気を確認したい都市を入力する都市検索ボックスが表示されます:

ハノイ(ベトナムにある)を検索ボックスに入力してください。アプリはハノイの現在の天気を表示します:

検索が行われた時点での結果によると、ベトナムのハノイの天候は27°Cで、雲が散在していました。日没は17:38に発生し、湿度は70パーセント、風速は毎秒2.8メートル(m/s)、気圧は1,016ヘクトパスカル(hPa)でした。

アプリケーションを正常にデプロイしたので、テスト依存関係をインストールします。

ステップ 2 — テスト依存関係のインストール

このステップでは、テスト依存関係をインストールして、ステップ3で後にPlaywrightを使用したコンポーネントテストを実装できるようにします。

まず、次のコマンドを実行してください。

npm init playwright@latest -- --ct

このコマンドは、Playwrightを使用してテストを書くことができるコード生成プロセスを開始します。選択したオプションに基づいて、TypeScriptまたはJavaScriptを使用しているか、ReactフレームワークまたはVueフレームワークを使用しているかによって、自動生成されるコードは異なります。

あなたのコンソールには次の出力が表示されます:

Need to install the following packages:
  create-playwright@1.17.123
Ok to proceed? (y)

Y を押してから Enter を押して進んでください。そうすると、プロジェクトで TypeScript または JavaScript のどちらを使用するかを選択するオプションが与えられます。

Getting started with writing end-to-end tests with Playwright:
Initializing project in '.'
? Do you want to use TypeScript or JavaScript? … 
  TypeScript
▸ JavaScript

ステップ3でコンポーネントテストに使用するため、JavaScriptを選択してください。その後、どのフレームワークを使用したいかを尋ねる出力が表示されます。

? Which framework do you use? (experimental) … 
▸ react
  vue
  svelte

天気アプリケーションが使っているため、reactを選択してください。出力ではPlaywrightブラウザをインストールするかどうか尋ねられます:

? Install Playwright browsers (can be done manually via 'npx playwright install')? (Y/n) ‣ true

Yを選択して、後で手動でインストールする必要がないようにしてください。

次に、オペレーティングシステムの依存関係をインストールしたいかどうかを尋ねられます。

 Install Playwright operating system dependencies (requires sudo / root - can be done manually via 'sudo npx playwright install-deps')? (y/N) ‣ yes

Yを選択して、依存関係を手動でインストールしなくて済むようにしてください。システム依存関係をインストールするためには、現在実行しているマシンのユーザーパスワードを入力する必要があることに注意してください。

必要な依存関係がすべてインストールされ、Chromium、Firefox、WebKitの3つのブラウザもインストールされます。また、コンポーネントのテスト中に必要なindex.htmlとindex.jsファイルを含むplaywrightという新しいディレクトリが作成されます。さらに、コンポーネントテストを実行するためのplaywright-ct.config.jsという新しい設定ファイルが生成されます。この設定ファイルをカスタマイズすることで、テストをChromiumブラウザだけで実行したいかどうかなど、コンポーネントテストの動作を変更できます。

注意:スクリプト test-ct は、以下に表示されているように、package.json ファイルに自動的に生成される必要があります。

"scripts": {
    "start": "react-scripts start",
    "test-ct": "playwright test -c playwright-ct.config.js"
  },

アプリコンポーネントのコンポーネントテストを実行するために、このスクリプトがステップ5で必要になります。

コンポーネントテストのための依存関係の設定が完了しました。次のステップでは、2つの天気アプリコンポーネントのうちの1つのテストを作成します。

ステップ3 — CityComponent のコンポーネントテストを書く

天気アプリには二つのコンポーネントがあります: CityComponent と WeatherInfoComponent。このステップでは、CityComponent のコンポーネントテストのためのスクリプトを書きます。このコードでは、CityComponent に対して二つのテストを書きます。最初のテストは、都市入力ボックスがテキスト入力を受け付けるかどうかを確認します。二つ目のテストは、ボタンがクリックされた後に Search ボタンが fetchWeather メソッドを実行するかどうかを確認します。

最初に、srcディレクトリに移動してください:

cd src

次に、テストを保存するためのtestsディレクトリを作成します:

mkdir tests

次に、新しく作成されたディレクトリに移動します:

cd tests

さて、testsディレクトリで、nanoまたはお好きなコードエディタを使用して、CityComponent.spec.jsxという新しいテストファイルを作成して開きます:

nano CityComponent.spec.jsx

各テストフレームワークは、テストランナーがテストファイルを収集し実行する際にどこを探すべきかを知るために、テストファイル用の定義された構文を持っています。Playwrightの場合、フレームワークは*.spec.*ファイルを探し、それらをテストファイルとして扱います。

.jsx拡張子はJavaScript XMLの略で、JavaScriptファイル内にHTMLタグを追加することができます。.jsxファイルを使用すると、テストにCityComponentをマウントできます。これは、CityComponentタグを追加することで実現します。

さあ、今作成したCityComponent.spec.jsxファイルの構築を始めます。次のコードをファイルに追加してください:

import { test, expect } from '@playwright/experimental-ct-react';
import { CityComponent } from '../modules/CityComponent';

このコードでは、test メソッドと expect メソッドを @playwright/experimental-ct-react ライブラリからインポートして、Playwrightでコンポーネントテストを実行しています。また、CityComponent をインポートして、後でテストにマウントできるようにしています。

2番目のimportコマンドを使用すると、CityComponentファイルがsrc/modulesディレクトリ内に配置されます。このファイルは検索ページのコードを実装しており、ユーザーが都市名を入力してその都市の天気を検索できるようにします。

次に、以下のコードをあなたの CityComponent.spec.jsx ファイルに追加してください:

...

const cityLocator = '[placeholder="City"]'
const searchButtonLocator = '[type="submit"]'

const cityName = 'Hanoi'

Playwrightでは、インターフェース要素と対話するために、対象となる要素のUIロケータが必要です。ここでは、cityLocatorとsearchButtonLocatorという2つのconst変数を定義します。constキーワードは定数変数を示し、初期値を割り当てた後はその値を変更できないことを意味します。[placeholder="City"]と[type="submit"]は、CityComponent内のテキスト入力と検索ボタンのCSSロケータです。

3行目のコードでは、検索したい都市名、今回の場合はHanoiを格納するための新しい変数cityNameを作成しています。

次に、CityComponent.spec.jsxファイルに最初のテストコードブロックを追加します。このテストはcityFieldがテキスト値を受け入れるかどうかを確認します。以下のコードをファイルに追加してください:

...

test('cityField accepts text input', async ({ mount }) => {
    const cityComponent = await mount(<CityComponent /> );
    const cityField = cityComponent.locator(cityLocator)
    
    await cityField.fill(cityName)

    await expect(cityField).toHaveValue(cityName)
});

テストコードブロック内では、async関数のパラメーターとしてmountメソッドを追加しています。このmountメソッドは、ステップ2でnpm init playwright@latest -- --ctを使用してインストールしたexperimental-ct-reactから来ています。mountメソッドを使用することで、アプリ内にReactコンポーネントをマウントし、マウントされたコンポーネントのテストを実装することができます。

async キーワードをテストブロック test('cityField accepts text input', async ({ mount }) => { ..}) に追加することで、Playwright にこのテストを非同期に実行するように指示しています。非同期のアプローチでは、各行のコードが順次実行されるのを待つ必要がなく、代わりにコードは並行して実行されます。これにより、テストをより速く実行できるようになります。

次に、mount メソッドに CityComponent タグを追加することで cityComponent を定義しています。React フレームワークは非同期でコードを実行するため、React コンポーネントを扱う際は、CityComponent をマウントする前に await キーワードを追加する必要があります。await キーワードを使用することで、Playwright はマウントされた CityComponent がそのコードの実行を終えるのを待ってから、マウントされた値を cityComponent 変数に割り当てます。

次に、作成したcityLocator変数を使用して、cityFieldをそのロケーターで見つけます。

最後に、cityFieldにcityNameの値を設定し、cityFieldがcityNameと同じ値を持っているかどうかを確認します。ここでawaitキーワードを使用することで、cityFieldがcityNameで埋められていることを確認してから、cityFieldにcityNameの値があるかどうかをチェックします。

今から、CityComponent.spec.jsxファイルに二つ目のテストコードブロックを追加します。このブロックは、SearchボタンがfetchWeatherメソッドを呼び出すかどうかをチェックします。以下のコードをファイルに追加してください:

...

test('Click on `Search` button executes fetchWeather prop', async ({ mount }) => {
    let isCalled = false
    const cityComponent = await mount(
        <CityComponent
        fetchWeather={() => isCalled = true}
        />
    );

    await cityComponent.locator(cityLocator).fill(cityName)
    await cityComponent.locator(searchButtonLocator).click()

    expect(isCalled).toBeTruthy()
});

この第2のテストブロックでは、第1のテストブロックと同様に、async関数のパラメータとしてmountメソッドを追加しています。テストブロックtest('検索ボタンをクリックするとfetchWeatherプロップが実行される', async ({ mount })にasyncキーワードを追加することで、Playwrightにこのテストを非同期に実行するように指示しています。

テスト関数の内部では、まず isCalled と名付けられた新しい変数を作成し、後で fetchWeather メソッドが呼び出されたかどうかを確認できるようにします。let 変数は、新しい変数を定義し、その変数の値を変更することを許可しますが、const 変数は変数の値を変更することを許可しません。

次に、cityComponentを定義し、実際のCityComponentを注入するためにmountメソッドに割り当てます。CityComponentタグ内にfetchWeather={() => isCalled=true}の行を追加します。これは、fetchWeatherメソッドが呼び出されると、isCalledの値がtrueに更新されることを意味します。

次に、cityNameで都市のテキスト入力を埋めてから、clickメソッドを使用して、ユーザーがUIページのSearchボタンをクリックするアクションをシミュレートします。

最後に、isCalled 変数が true かどうかを toBeTruthy メソッドを使って確認します。

この時点で、あなたのファイルには次のコードが含まれています:

import { test, expect } from '@playwright/experimental-ct-react';
import { CityComponent } from '../modules/CityComponent';

const cityLocator = '[placeholder="City"]'
const searchButtonLocator = '[type="submit"]'

const cityName = 'Hanoi'

test('cityField accepts text input', async ({ mount }) => {
    const cityComponent = await mount(<CityComponent /> );
    const cityField = cityComponent.locator(cityLocator)
    
    await cityField.fill(cityName)

    await expect(cityField).toHaveValue(cityName)
});

test('Click on `Search` button executes fetchWeather prop', async ({ mount }) => {
    let isCalled = false
    const cityComponent = await mount(
        <CityComponent
        fetchWeather={() => isCalled = true}
        />
    );

    await cityComponent.locator(cityLocator).fill(cityName)
    await cityComponent.locator(searchButtonLocator).click()

    expect(isCalled).toBeTruthy()
});

ファイルを保存して閉じてください。nanoを使用している場合は、CTRL+Xを押して保存し終了します。

あなたは今、CityComponentのコンポーネントテスト用のスクリプトを書きました。このスクリプトには、1つは都市入力ボックスがHanoiを入力として受け入れるかどうかを確認するテストブロック、もう1つはSearchボタンがクリックされた後にfetchWeatherメソッドを実行するかどうかをテストするブロックが含まれています。次のステップでは、2つ目の天気アプリコンポーネント、WeatherInfoComponentのコンポーネントテストを書きます。

ステップ4 — WeatherInfoComponentのコンポーネントテストを書く

このステップでは、テストしている天気アプリの2番目のコンポーネントであるWeatherInfoComponentのテストを記述します。このテストは、weatherInfo入力としてHanoiを受け取った後、weatherComponentがHanoiというテキストを含んでいるかどうかをチェックします。

最初に、testsディレクトリに、新しいテストファイルWeatherInfoComponent.spec.jsxを作成して開きます:

nano WeatherInfoComponent.spec.jsx

WeatherInfoComponent.spec.jsxファイルに次の内容を追加してください:

import { test, expect } from '@playwright/experimental-ct-react';
import { WeatherComponent } from '../modules/WeatherInfoComponent';

ここでは、コンポーネントテストをPlaywrightで実行できるようにするために、testメソッドとexpectメソッドを@playwright/experimental-ct-reactライブラリからインポートしています。

次に、WeatherInfoComponentをインポートすることで、テストにWeatherComponentをマウントしています。WeatherInfoComponentファイルは結果ページを生成し、ユーザーが提供した都市の天気を表示し、src/modulesディレクトリ内にあります。

次に、テストファイルWeatherInfoComponent.spec.jsxに次のコードブロックを追加してください:

...

test('WeatherInfo accepts name and value', async ({ mount }) => {
    const weatherInfo = {"coord":{"lon":105.8412,"lat":21.0245},"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}],"base":"stations","main":{"temp":302.15,"feels_like":301.35,"temp_min":302.15,"temp_max":302.15,"pressure":1003,"humidity":35,"sea_level":1003,"grnd_level":1002},"visibility":10000,"wind":{"speed":2.71,"deg":73,"gust":3.29},"clouds":{"all":0},"dt":1673694125,"sys":{"type":1,"id":9308,"country":"VN","sunrise":1673652961,"sunset":1673692464},"timezone":25200,"id":1581130,"name":"Hanoi","cod":200}
    const weatherComponent = await mount(<WeatherComponent
    weather={(weatherInfo)}
    /> );
    await expect(weatherComponent).toContainText('Hanoi')
});

ここでは、weatherInfoという名前のconst変数を定義します。constは定数変数であるため、初期値を割り当てた後はその値を変更することはできません。この場合、weatherInfoの値は、JSON形式でのハノイの天気情報です。

次に、WeatherComponentから値を取得するconstという名前の別のweatherComponentを定義します。

Reactフレームワークは非同期でコードを実行するため、WeatherComponentをマウントする際にはawaitを使用する必要があります。これにより、ReactがWeatherComponentの作成を完全に終えるまで待機するようにコードに指示します。

最後に、await expect(weatherComponent).toContainText('Hanoi') では、weatherComponent 変数がテキスト Hanoi を含んでいるかどうかをチェックしています。ここでは await を使用する必要があります。なぜなら、Playwright はコードを非同期に実行するため、await を使うことで、weatherComponent 変数が WeatherComponent から値を受け取ってから、weatherComponent がテキスト Hanoi を含んでいるかを確認することができるからです。

あなたの完全な WeatherInfoComponent.spec.jsx ファイルには、次のコード行が含まれます:

import { test, expect } from '@playwright/experimental-ct-react';
import { WeatherComponent } from '../modules/WeatherInfoComponent';


test('WeatherInfo accepts name and value', async ({ mount }) => {
    const weatherInfo = {"coord":{"lon":105.8412,"lat":21.0245},"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}],"base":"stations","main":{"temp":302.15,"feels_like":301.35,"temp_min":302.15,"temp_max":302.15,"pressure":1003,"humidity":35,"sea_level":1003,"grnd_level":1002},"visibility":10000,"wind":{"speed":2.71,"deg":73,"gust":3.29},"clouds":{"all":0},"dt":1673694125,"sys":{"type":1,"id":9308,"country":"VN","sunrise":1673652961,"sunset":1673692464},"timezone":25200,"id":1581130,"name":"Hanoi","cod":200}
    const weatherComponent = await mount(<WeatherComponent
    weather={(weatherInfo)}
    /> );
    await expect(weatherComponent).toContainText('Hanoi')
});

ファイルを保存して終了するには、CTRL+Xを押してください。

あなたは今、WeatherInfoComponentのテストを書き終えました。次に、インストールされている3つのブラウザすべてでCityComponentとWeatherInfoComponentのテストを実行します。

ステップ5 — コンポーネントテストの実行

このステップでは、インストールされている3つのブラウザ(Chromium、Firefox、WebKit)のすべてで、CityComponentおよびWeatherInfoComponentのテストを実行し、これらのコンポーネントがそれらのブラウザで適切に機能することを確認します。

まず、ルートプロジェクトディレクトリに移動します。

cd ../..

コマンドを実行すると、react-component-testing-demoフォルダーに入ります。その後、テストを実行するために次のコマンドを実行してください:

npm run test-ct

Playwrightは、Chromium、Firefox、WebKitの3つのブラウザすべてに対して2つのテストファイルを実行します。次の出力に似た結果が得られます:

Running 9 tests using 2 workers

vite v3.2.5 building for production...
✓ 89 modules transformed.
playwright/.cache/playwright/index.html      0.32 KiB
playwright/.cache/assets/index.2b5442e2.js   313.68 KiB / gzip: 81.07 KiB

  9 passed (12s)

To open last HTML report run:

  npx playwright show-report

GUI形式でレポートを表示するには、以下のコマンドを実行してください:

npx playwright show-report

注意: グラフィカルユーザーインターフェース(GUI)をサポートし、ChromeやFirefoxなどのブラウザがすでにインストールされているローカルマシンでこれらのコマンドを実行している場合、天気アプリがブラウザで自動的に開きます。ただし、リモートサーバーでこれらのコマンドを実行している場合は、SSHを介してポートフォワーディングを使用して、ローカルマシンでアプリを開く必要があります。

新しいターミナルで、以下のコマンドを実行してください:

ssh -L 9223:localhost:9223 your_non_root_user@your_server_ip

これで、あなたのローカルマシンからテストレポートにアクセスできるようになります。

テストレポートの概要がブラウザに表示されます:

テストレポートは、cityFieldはテキスト入力を受け入れる、SearchボタンをクリックするとfetchWeatherプロパティが実行される、およびWeatherInfoは名前と値を受け入れるという3つの異なるテストに分かれています。各テストは、全体の実行時間と各ブラウザのテストにかかった時間を表示します。

各テストをクリックすることで—例えば、cityFieldはテキスト入力を受け入れます—テストの各ステップに関する時間とコードの行数の詳細を見ることができます:

結果は、CityComponentテストがCity入力ボックスがテキストを受け入れるかどうかを確認するもので、Chromium上で正常に実行されたことを示しています。詳細セクションでは、テスト実行の手順にデフォルトでBefore HooksとAfter Hooksの手順が含まれます。Before Hooksセクションは、コンソールへのログインやテストデータの読み取りなど、初期設定にしばしば使用されます。テスト実行後、After Hooksセクションはテスト環境内のテストデータをクリーンアップすることがよくあります。

locator.fill() 行は、テスト実装において要素がどのように特定されているかを示しています。この場合、要素は値 [placeholder="City"] の CSS セレクターを使用して特定されています。

expect.toHaveValue の行はテストのアサーションが何であるかを示しています。このテストでは、cityField が cityName の値を持っていることを期待しています。

あなたは今、天気アプリケーションのためのCityComponentとWeatherInfoComponentのテストを完全に実装し実行し、テストレポートを確認して、これらの2つのコンポーネントがChromium、Firefox、WebKitで動作することを確認しました。アプリケーションとテストの両方のソースコードは、GitHubリポジトリのメインブランチで確認できます。

結論

あなたは今、Playwrightを使用してReactベースの天気アプリのコンポーネントテストを実装しました。最初に、天気アプリコンポーネントの必要なテスト依存関係であるCityComponentとWeatherInfoComponentをインストールしました。その後、2つのコンポーネントのコンポーネントテストを書いて実行しました。最後に、コンポーネントが期待通りに動作したかを確認するためにGUI形式のテストレポートをレビューしました。

Playwrightやコンポーネントテストをサポートするその他の機能についてさらに学ぶには、Playwrightのドキュメントを訪れてください。

関連記事: