N-LAB

Nuxt 3 × PuppeteerでE2Eテストを実施する


目標


前提

※本手順書ではソースディレクトリを「src」に変更している想定としています。テストコードは「src/e2eTest」に保存します。
※Vitestのインストール手順に関しては以下を参照ください。
https://n-laboratory.jp/articles/nuxt3-vitest-unittest

・Nuxt 3.5.2
・Vitest 0.31.4
・puppeteer 20.5.0
・VeeValidate 4.9.6

目次

  1. Puppeteerインストール
  2. VeeValidateインストール
  3. 入力フォームの作成
  4. E2Eテストケースの作成


Puppeteerインストール

1. Puppeteerをインストールします。

$ npm install puppeteer


VeeValidateインストール

1. VeeValidateをインストールします。

$ npm install --save-dev vee-validate @vee-validate/i18n @vee-validate/rules


2. 「plugins」ディレクトリに「vee-validate-plugin.ts」を新規作成し、以下を追加します。

import { localize, setLocale } from '@vee-validate/i18n'
import ja from '@vee-validate/i18n/dist/locale/ja.json'
import AllRules from '@vee-validate/rules'
import { defineRule, configure } from 'vee-validate'
import { defineNuxtPlugin } from '#app'

export default defineNuxtPlugin((_nuxtApp) => {
  configure({
    generateMessage: localize({
      ja
    })
  })

  Object.keys(AllRules).forEach((rule) => {
    // すべてのルールをインポート
    defineRule(rule, AllRules[rule])
  })

  setLocale('ja')
})


入力フォームの作成


1. 「pages」配下に「form.vue」を新規作成し、以下の内容で保存します。

<script lang="ts" setup>
import { Form, Field, ErrorMessage } from 'vee-validate'

const emailRef = ref('')
const foo = (values: Record<string, any>) => {
  emailRef.value = values.email
}
</script>

<template>
  <div>
    <Form v-slot="{ meta }" data-testid="validation-form" @submit="foo">
      <!-- バリデーション対象の項目 -->
      <Field rules="required|email" name="email" as="input" type="text" data-testid="input-email" />
      <!-- バリデーションエラーメッセージの表示 -->
      <ErrorMessage name="email" data-testid="email-error-msg" />
      <!-- meta.validは有効な値を入力された場合にtrueを返します -->
      <button :disabled="!meta.valid" data-testid="submit-btn">
        Submit
      </button>
    </Form>
    <p>Result:{{ emailRef }}</p>
  </div>
</template>


2. 以下のURLにアクセスします。フォーム画面が表示されており、送信ボタンが非活性であることを確認します。
http://localhost:3000/form
フォーム画面の初期表示確認
3. 入力欄に「a」を入力します。エラーメッセージが表示されることを確認します。
エラーメッセージの確認
4. 入力欄を空欄にします。エラーメッセージが表示されることを確認します。
エラーメッセージの確認
5. 入力欄に「a@a.com」を入力します。送信ボタンが活性していることを確認します。
送信ボタンの制御確認
6. 送信ボタンをクリックします。Resultに「a@a.com」が表示されていることを確認します。
送信処理の確認

E2Eテストケースの作成

src
└── e2eTest
     ├── form.spec.ts ← テストファイル
     └── xxx.png      ← 各テストケースのスクリーンショット(エビデンス)


1. 「e2eTest」配下に「form.spec.ts」を新規作成し、以下の内容で保存します。

import { afterAll, beforeAll, describe, expect, test } from 'vitest'
import { launch, PuppeteerLaunchOptions } from 'puppeteer'
import type { Browser, Page } from 'puppeteer'

// ブラウザの起動オプションの設定。使用できるパラメータに関しての詳細は以下を参照ください。
// https://pptr.dev/api/puppeteer.browserlaunchargumentoptions
const options: PuppeteerLaunchOptions = {
  headless: false,
  slowMo: 75,
  defaultViewport: {
    width: 1280,
    height: 1024
  },
  devtools: true,
  args: ['--window-size=1680,1024']
}

describe('form.vue', () => {
  let browser: Browser
  let page: Page

  beforeAll(async () => {
    browser = await launch(options)
    page = await browser.newPage()
  })

  afterAll(async () => {
    await browser.close()
  })

  test('1:フォーム画面が表示されること', async () => {
    // Act
    await page.goto('http://localhost:3000/form')
    await page.waitForNavigation({ timeout: 5000, waitUntil: 'domcontentloaded' })

    // スクリーンショット(エビデンス)の撮影
    await page.screenshot({
      path: './src/e2eTest/test1.png',
      fullPage: true
    })

    const existsPage = await page.$eval(
      '[data-testid="validation-form"]',
      element => !!element
    )

    // Assert
    expect(existsPage).toBe(true)
  }, 20000)

  describe('バリデーションチェック', () => {
    test.each([
      [
        2,
        '不正なメールアドレスを入力した',
        'abcABC0123456789',
        'emailは有効なメールアドレスではありません'
      ],
      [
        3,
        '未入力の',
        '',
        'emailは必須項目です'
      ]
    ])(
      '%s:メールアドレスの入力フィールドに[%s]場合、エラーメッセージが表示されること',
      async (
        testNo,
        description,
        inputValue,
        expectedErrorMsg
      ) => {
        // Act
        await page.type('input[data-testid="input-email"]', inputValue)
        await page.keyboard.press('Tab')

        // スクリーンショット(エビデンス)の撮影
        await page.screenshot({
          path: `./src/e2eTest/test${testNo}.png`,
          fullPage: true
        })

        // エラーメッセージの取得
        const errorMsg = await page.$eval(
          '[data-testid="email-error-msg"]',
          element => element.textContent
        )

        // Assert
        expect(errorMsg).toBe(expectedErrorMsg)

        // 入力した値をリセット
        await page.click('input[data-testid="input-email"]', {
          clickCount: inputValue.length
        })
        await page.keyboard.press('Backspace')
      },
      200000
    )
  })

  describe('Submitボタンの活性/非活性状態確認', () => {
    test('4:初期状態では送信ボタンが非活性であること', async () => {
      // Act
      await page.goto('http://localhost:3000/form')
      await page.waitForNavigation({ timeout: 5000, waitUntil: 'domcontentloaded' })

      // Act
      await page.screenshot({
        path: './src/e2eTest/test4.png',
        fullPage: true
      })

      const disabled = await page.$eval(
        '[data-testid="submit-btn"]',
        element => (element as HTMLButtonElement).disabled
      )

      // Assert
      expect(disabled).toBe(true)
    }, 10000)

    test('5:有効なメールアドレスを入力した場合、送信ボタンが活性であること', async () => {
      // Act
      await page.type('input[data-testid="input-email"]', 'test@test.com')
      await page.keyboard.press('Tab')
      await page.screenshot({
        path: './src/e2eTest/test5.png',
        fullPage: true
      })

      const disabled = await page.$eval(
        '[data-testid="submit-btn"]',
        element => (element as HTMLButtonElement).disabled
      )

      // Assert
      expect(disabled).toBeFalsy()

      // 入力した値をリセット
      await page.click('input[data-testid="input-email"]', { clickCount: 13 })
      await page.keyboard.press('Backspace')
    }, 10000)
  })

  test('6:Submitボタンを押下した場合、Result欄にメールアドレスが表示されること', async () => {
    // Act
    await page.type('input[data-testid="input-email"]', 'test@test.com')
    await page.keyboard.press('Tab')
    await page.click('[data-testid="submit-btn"]')
    await page.screenshot({
      path: './src/e2eTest/test6.png',
      fullPage: true
    })

    const result = await page.$eval(
      '[data-testid="result"]',
      element => element.textContent
    )

    // Assert
    expect(result).toBe('Result:test@test.com')
  }, 10000)
})


2. package.jsonの「scripts」に以下を追加します。

{
  "scripts": {
    "dev": "nuxt dev",
    "test": "vitest",
  },
}


3. テストを実行します。

$ npm run dev
$ npm run test


4. ブラウザが自動で起動し、テストが実行されることを確認します。
テストの起動確認
5. テスト終了後はブラウザが自動で終了し、テストが全件passしていることを確認します。

> test
> vitest
 ✓ src/e2eTest/form.spec.ts (6) 18482ms
   ✓ form.vue (6) 18482ms
     ✓ 1:フォーム画面が表示されること 1455ms
     ✓ バリデーションチェック (2) 6388ms
       ✓ 2:メールアドレスの入力フィールドに[不正なメールアドレスを入力した]場合、エラーメッセージが表示されること 4715ms
       ✓ 3:メールアドレスの入力フィールドに[未入力の]場合、エラーメッセージが表示されること 1673ms
     ✓ Submitボタンの活性/非活性状態確認 (2) 5509ms
       ✓ 4:初期状態では送信ボタンが非活性であること 1283ms
       ✓ 5:有効なメールアドレスを入力した場合、送信ボタンが活性であること 4226ms
     ✓ 6:Submitボタンを押下した場合、Result欄にメールアドレスが表示されること 3494ms

 Test Files  1 passed (1)
      Tests  6 passed (6)
   Start at  20:09:08



以上で全ての手順は完了になります