📝

react+viteをplaywright+mswで自動テストする

2022/07/02に公開

掲題の構成を試してみました。

環境

  • react 18.0.0
  • react-dom 18.0.0
  • react-router-dom 6.3.0
  • typescript 4.6.3
  • msw 0.42.3
  • vite 2.9.9

セットアップ

プロジェクト作成

vite initを使ってプロジェクトを作成します。

npm create vite@latest react-vite-playwright-msw-sample -- --template react-ts

できたプロジェクトに入って追加の依存関係をインストールします。

cd react-vite-playwright-msw-sample
npm i react-router-dom
npm i -D @playwright/test msw
npx playwright install

サンプルのユーザ登録フォームを用意

テスト対象のUIが欲しいので、サンプルのユーザ登録フォームを用意します。

src/register-user.tsx
import { FC, useState } from 'react'
import { useNavigate } from 'react-router-dom'

export const RegisterUser: FC = () => {
  const navigate = useNavigate()
  const [email, setEmail] = useState('')
  const [name, setName] = useState('')

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault()
        ;(async () => {
	  // APIを叩き、ユーザ情報を保存する
          await fetch('http://localhost:8000/api/users', {
            method: 'POST',
            body: JSON.stringify({
              email,
              name,
            }),
            headers: {
              'Content-Type': 'application/json',
            },
          })
	  
	  // マイページに遷移する
          navigate('/my-page')
        })()
      }}
    >
      <input
        type="email"
        name="email"
        onChange={(e) => setEmail(e.target.value)}
        data-test="emailInput"
      />
      <input
        type="name"
        name="name"
        onChange={(e) => setName(e.target.value)}
        data-test="nameInput"
      />
      <button type="submit" data-test="submitButton">
        Submit
      </button>
    </form>
  )
}

APIの http://localhost:8000/api/users をPOSTで叩くとユーザ登録できるという想定です。APIは後でmswでモックします。

ユーザ登録後に遷移する想定のマイページも用意しておきます。

src/my-page.tsx
import { FC } from 'react'

export const MyPage: FC = () => {
  return (
    <div data-test="myPagePage">
      <h1>My Page</h1>
    </div>
  )
}

react-routerによるルーティング設定も行っておきます。

src/main.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import './index.css'
import { BrowserRouter, Route, Routes } from 'react-router-dom'
import { RegisterUser } from './register-user'
import { Registered } from './registered'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <BrowserRouter>
      <Routes>
        <Route path="/register-user" element={<RegisterUser />} />
        <Route path="/my-page" element={<MyPage />} />
      </Routes>
    </BrowserRouter>
  </React.StrictMode>
)
npm run dev

を実行し、ブラウザで http://localhost:3000/register-user を開くとフォームを確認できます。

playwrightを設定

playwrightの設定ファイルを作成します。どのような設定をするかは好みや状況で変わってきますが、とりあえず以下で進めます。

playwright.config.ts
import { devices, PlaywrightTestConfig } from '@playwright/test'

const config: PlaywrightTestConfig = {
  // できるだけ並列にテストを行う
  fullyParallel: true,

  // マルチブラウザテストを行う
  projects: [
    {
      name: 'Pixel 4',
      use: {
        browserName: 'chromium',
        ...devices['Pixel 4'],
      },
    },
    {
      name: 'iPhone 11',
      use: {
        browserName: 'webkit',
        ...devices['iPhone 11'],
      },
    },
  ],
}

export default config

mswを設定

mswの設定を行います。まず以下のinitコマンドを実行し、 public/mockServiceWorker.js を作成します。このファイルは何も触らなくてOK。

npx msw init public

そして以下のmswモック周りの初期化関数を用意しました。 http://localhost:8000/api/users をモックするmswワーカを生成します。

src/testing-util/msw-util.ts
import { setupWorker, rest, SetupWorkerApi } from 'msw'

export const buildMswWorker = (): SetupWorkerApi => {
  const worker = setupWorker(
    rest.post('http://localhost:8000/api/users', (_req, res, ctx) => {
      return res(
        ctx.json({
          message: 'Success.',
        })
      )
    })
  )
  return worker
}

main.tsxで読み込むようにします。VITEの環境変数として VITE_MOCKED_API を用意し、それが 'true' だった時にmswモックを有効化するようにしました。また、dynamic importすることでmswを使用しない場合にバンドルに含まれないようにします。

src/main.tsx
+;(async () => {
+ if (import.meta.env.VITE_MOCKED_API === 'true') {
+   const { buildMswWorker } = await import('./testing-util/msw-util')
+   const worker = buildMswWorker()
+   await worker.start()
+ }

  ReactDOM.createRoot(document.getElementById('root')!).render(
    <React.StrictMode>
      <BrowserRouter>
        <Routes>
          <Route path="/" element={<Form />} />
          <Route path="/registered" element={<Registered />} />
        </Routes>
      </BrowserRouter>
    </React.StrictMode>
  )
+})()

mswを有効化して起動するには

VITE_MOCKED_API=true npm run dev

としてコマンドを実行します。

ブラウザDevToolsのコンソールに

[MSW] Mocking enabled.

と表示されていればOKです。

テストを書く

以下のテストを書き、MSW有効の開発サーバが起動している状態で実行します。

src/register-user.test.ts
import { expect, test } from '@playwright/test'

test('ユーザ登録する', async ({ page }) => {
  // 登録ページを開く
  await page.goto('http://localhost:3000/register-user')
  
  // フォームの入力欄を埋める
  await page.fill('[data-test=emailInput]', 'foo@example.com')
  await page.fill('[data-test=nameInput]', 'foo')
  
  // フォーム送信ボタンを押下
  await page.click('[data-test=submitButton]')
  
  // マイページに遷移したことを確認
  await expect(page.locator('[data-test=myPagePage]')).toHaveCount(1)
})

以下のコマンドでテストを実行できます。

npx playwright test

上のコマンドはヘッドレスブラウザモードです。ブラウザを目視しながらテストを確認したい場合は --headed オプションを付与します。またその場合は並列実行よりもシングルワーカ実行の方が確認しやすいでしょう。 --workers 1 も付与します。

npx playwright test --headed --workers 1

テストが意図通りに動かずデバッグしたい場合もあると思います。その場合は await page.pause() が便利です。

test('ユーザ登録する', async ({ page }) => {
  // 登録ページを開く
  await page.goto('http://localhost:3000/register-user')
  
+ // ここでストップする
+ await page.pause()

また、特定のテストだけ実行したい場合は test.only が使えます。

-test('ユーザ登録する', async ({ page }) => {
+test.only('ユーザ登録する', async ({ page }) => {
  // 登録ページを開く
  await page.goto('http://localhost:3000/register-user')

参考

Discussion