仕事でNuxtとVuetifyを使ってWEBアプリのフロントエンドを実装しています。リリースごとに毎回手動で動作確認を行っていて、毎回面倒なので、Cypress(E2Eテスト)を導入して正常系だけでも動作確認を自動化し省力化を図りました。
NuxtとVuetifyを使用しているプロジェクトにCypressを導入するにあたって、地味にハマった箇所を共有したいと思います。
環境
使用する主要なライブラリとバージョンは下記になります。
ライブラリ | バージョン |
---|---|
Nuxt | 2.15.8 |
Vue | 2.7.8 |
Vuetify | 2.6.8 |
Cypress | 10.4.0 |
テストプロジェクトの作成
npx create-nuxt-app
コマンドを使ってテストプロジェクトを作成します。設定は基本的に適当に以下にしましたが、後程使いますので、UI framework
では必ずVuetify.js
を選択して下さい。
% npx create-nuxt-app nuxt-cypress-app
create-nuxt-app v4.0.0
✨ Generating Nuxt.js project in nuxt-cypress-app
? Project name: nuxt-cypress-app
? Programming language: JavaScript
? Package manager: Yarn
? UI framework: Vuetify.js
? Nuxt.js modules: (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Linting tools: ESLint, Prettier
? Testing framework: Jest
? Rendering mode: Single Page App
? Deployment target: Static (Static/Jamstack hosting)
? Development tools: jsconfig.json (Recommended for VS Code if you're not using typescript), Dependabot (For auto-updating dependencies, GitHub only)
? Continuous integration: GitHub Actions (GitHub only)
? What is your GitHub username? yamada
? Version control system: Git
cypressをインストールします。
$ cd nuxt-cypress-app
$ yarn add -D cypress
プロジェクト直下にcypress
ディレクトリを作成し、以下のcypressの設定ファイル(cypress.config.js)とテストファイルを格納するディレクトリ(tests)を作成します。
.
├── cypress.config.js
└── tests
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
specPattern: "**/tests/*.spec.js",
supportFile: false,
video: true,
videoUploadOnPasses: false,
defaultCommandTimeout: 10000
}
})
簡単にE2Eテストを実行できるよう、プロジェクト直下のpackage.json
のscripts
に"e2e": "cypress run --config-file=cypress/cypress.config.js --browser chrome"
を追加します。npm run e2e
でE2Eテストを実行できるようになりました。
テスト対象のファイルを作成します。今回は名前や生年月日などを入力する画面と完了画面を作成します。
<template>
<v-container fluid>
<v-text-field
ref="name"
:value="name"
label=""
maxlength="50"
outlined
data-cy="name"
@change="(value) => (name = value)"
>
<template #prepend>Name</template>
</v-text-field>
<v-menu
v-model="menu"
:close-on-content-click="false"
:nudge-right="40"
transition="scale-transition"
offset-y
hide-details
min-width="auto"
>
<template #activator="{ on, attrs }">
<v-text-field
id="birthday"
ref="birthday"
v-model="birthday"
label=""
outlined
readonly
clearable
data-cy="birthday-text-field"
v-bind="attrs"
v-on="on"
@click:clear="birthday = null"
>
<template #prepend>Birthday</template>
</v-text-field>
</template>
<v-date-picker
v-model="birthday"
year-icon="mdi-calendar-edit"
prev-icon="mdi-skip-previous"
next-icon="mdi-skip-next"
data-cy="birthday-date-picker"
:day-format="(date) => new Date(date).getDate()"
@input="menu = false"
></v-date-picker>
</v-menu>
<v-radio-group
ref="sex"
v-model="sex"
row
class="mt-0 mb-0"
>
<template #prepend>Sex</template>
<v-radio
id="male"
value="male"
label="male"
data-cy="sex-male"
></v-radio>
<v-radio
id="female"
value="female"
label="female"
data-cy="sex-female"
></v-radio>
</v-radio-group>
<v-row class="my-5">
<v-col cols="10">
<v-checkbox
v-model="agreedWithEula"
:label="`I agree`"
color="primary"
data-cy="agreed-with-eula"
>
</v-checkbox>
</v-col>
</v-row>
<v-btn
class="mx-0"
cols="12"
large
block
color="primary"
nuxt
data-cy="next-btn"
@click="register"
>
register
</v-btn>
</v-container>
</template>
<script>
export default {
name: 'RegisterIndex',
data() {
return {
menu: false,
name: '',
birthday: '',
sex: '',
agreedWithEula: false
}
},
methods: {
register() {
this.$router.push(
`/register/complete/`
)
}
}
}
</script>
<style scoped>
</style>
<template>
<v-container fluid>
Your registration is complete.
</v-container>
</template>
<script>
export default {
name: 'RegisterComplete'
}
</script>
<style scoped>
</style>
E2Eテストの実装
テスト対象のファイルができたら、実際にE2Eテストの実装します。今回は
- 入力画面のそれぞれの項目に入力
- 入力項目に入力できたら「Register」ボタンを押下
- 完了画面に遷移し、「Your registration is complete.」のテキストがあることを検証
のシナリオで作成します。
cypressのテストディレクトリにregister.spec.js
という名前でテストファイルを作成します。
describe('test register page', () => {
it('should register', () => {
cy.visit('/register/')
cy.get('[data-cy=name]').type('John')
cy.get('[data-cy=birthday-text-field]').click()
const datePicker = cy.get('[data-cy=birthday-date-picker]')
for (let i = 0; i < 18; i++) {
datePicker.get('[aria-label="Previous month"]').click()
}
cy.get('[data-cy=birthday-date-picker]').within(() => {
cy.wait(1000)
cy.get('div').contains('8').click()
})
cy.get('[data-cy=sex-female]').parent().click()
cy.get('[data-cy=agreed-with-eula]').parent().click()
cy.wait(1000)
cy.get('[data-cy=next-btn]').click()
cy.wait(1000)
cy.contains(
'Your registration is complete.'
).should('exist')
})
})
npm run e2e
でCypressを実際に実行すると、下記のようにE2Eテストが走って、動画も撮ることができます。
CypressでVuetifyを扱ってハマった箇所
v-radio
やv-checkbox
でチェックがつかない
cy.get('[data-cy=agreed-with-eula]').click()
と単純にclickメソッドを呼ぶだけではチェックがつきませんでした。
cy.get('[data-cy=agreed-with-eula]').parent().click()
のようにparentメソッドを呼ぶ必要がある(理由についても調べたがまだ分かっていない)。
v-date-picker
で特定の日付を選択したい
// date pickerのコンポーネントを取得
const datePicker = cy.get('[data-cy=birthday-date-picker]')
// プロジェクトで現在の日付から18ヶ月前を選択する必要があるので、「前月に戻る」アイコンを18回押下
for (let i = 0; i < 18; i++) {
datePicker.get('[aria-label="Previous month"]').click()
}
cy.get('[data-cy=birthday-date-picker]').within(() => {
// 後続で日付のDOMが取得できるよう、ブラウザのレンダリングを待つ(とりあえず1秒)
cy.wait(1000)
// 適当に8日をクリック
cy.get('div').contains('8').click()
})
最後に
上記テストプロジェクトはGithubにアップしましたので、分からないことがあればクローンして実際に試してみてください。