今回はCypressをAngularのプロジェクトに入れて実際にテストを書いてみます
実際にブラウザを起動して操作するところが見れたりみたいな部分は他の記事でも紹介されているので今回は割愛して主にpluginを入れてテストしてみる話をしたいと思います
Cypress
JavaScript End to End Testing Framework | Cypress.io
end-to-endのtesting toolということで、ブラウザ操作を自動化したりスクリーンショットを撮ったり、操作を動画として保存したりとE2Eテストでやりそうなことはだいたいカバーしているっていうイメージですね
AngularでCypress
- Angularのバージョンは下記となります
$ npx ng version Angular CLI: 7.3.8 Node: 10.15.3 OS: linux x64 Angular: 7.2.14
cypress + Angularのschematicsが公式で紹介されていたのでng add
で対応できます
briebug/cypress-schematic: Add cypress to an Angular CLI project
READMEにしたがって追加してみます
$ npm install --save @briebug/cypress-schematic $ npx ng add @briebug/cypress-schematic ? Would you like to remove Protractor from the project? Yes DELETE e2e CREATE cypress/tsconfig.json (114 bytes) CREATE cypress/plugins/cy-ts-preprocessor.js (387 bytes) CREATE cypress/plugins/index.js (155 bytes) UPDATE package.json (2989 bytes) UPDATE angular.json (4915 bytes)
? Would you like to remove Protractor from the project?
と質問されるので今回はYesで既存のprotractorのコードは削除します
すると下記操作が自動でされます
- protractor用の
e2e
ディレクトリの削除 - angular.jsonからe2e設定の削除
そして下記3ファイルが追加されます
- cypress/plugins/cy-ts-preprocessor.js
- cypress/tsconfig.json
- cypress/plugins/index.js
ちゃっかりtypescriptプラグインまで動くようにしてくれているようですね!
schematicsのおかげでng add
コマンドを打つだけでいろいろ面倒みてくれるのは体験としてとても良いですね
実際に生成されたファイルは下記3つです
- cypress/plugins/index.js
const cypressTypeScriptPreprocessor = require('./cy-ts-preprocessor') module.exports = on => { on('file:preprocessor', cypressTypeScriptPreprocessor) }
そのままではありますがfile:preprocessor
でTypeScriptプラグインからrequireしてきたオブジェクトを指定してあげることで事前にTypeScriptからコンパイルさせることができるようですね
preprocessorに関してはドキュメントにもあるようにTypeScript以外にもいくつかあるようです
Plugins | Cypress Documentation docs.cypress.io
- cypress/plugins/cy-ts-preprocessor.js
const wp = require('@cypress/webpack-preprocessor') const webpackOptions = { resolve: { extensions: ['.ts', '.js'] }, module: { rules: [ { test: /\.ts$/, exclude: [/node_modules/], use: [ { loader: 'ts-loader' } ] } ] } } const options = { webpackOptions } module.exports = wp(options)
webpackのpreprocessorを使っているのでTypeScriptに合わせた設定を渡してあげるということですね
webpackは正直雰囲気でしかさわれないのでそういうことなんだと言うことで覚えておきます・・・
- cypress/tsconfig.json
{ "extends": "../tsconfig.json", "include": ["integration/*.ts", "support/*.ts", "../node_modules/cypress"] }
cypressで使うテストコードようのtsconfig
です
実行してみる
すでにjsで一部テストを書いていたのでそのままtsに変えてテストしてみます
- cypress/integrations/demo-spec.ts
describe('viewchildren', () => { const urlBase = 'http://localhost:4200/'; it('demo with typescript', () => { cy.visit(`${urlBase}viewchildren`); cy.get('i.fa-exclamation-circle').eq(0).click(); cy.get('div.popover-content') .eq(0) .should('have.text', 'a'); }); });
操作としてはこんな感じの操作をしています
$ npx cypress run -s cypress/integration/demo-spec.ts ==================================================================================================== (Run Starting) ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ │ Cypress: 3.2.0 │ │ Browser: Electron 59 (headless) │ │ Specs: 1 found (demo-spec.ts) │ │ Searched: cypress/integration/demo-spec.ts │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ ──────────────────────────────────────────────────────────────────────────────────────────────────── Running: demo-spec.ts... (1 of 1) (Results) ┌────────────────────────────┐ │ Tests: 1 │ │ Passing: 1 │ │ Failing: 0 │ │ Pending: 0 │ │ Skipped: 0 │ │ Screenshots: 0 │ │ Video: true │ │ Duration: 8 seconds │ │ Spec Ran: demo-spec.ts │ └────────────────────────────┘ (Video) - Started processing: Compressing to 32 CRF - Finished processing: /home/vagrant/sandbox/ngx-sample/cypress/videos/demo-spec.ts.mp4 (7 seconds) ==================================================================================================== (Run Finished) Spec Tests Passing Failing Pending Skipped ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ │ ✔ demo-spec.ts 00:08 1 1 - - - │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ All specs passed! 00:08 1 1 - - -
成功しました
ちゃんとtsでも実行できる事を確認
楽ちんですね
cypressでxpathを使って要素を取得したい
個人的にはDOMの操作とかはxpathで指定してとってきたいと思っている派なのでCypressでもxpathが使いたいです
ということでpluginが用意されているので使ってみます
cypress-io/cypress-xpath: Adds XPath command to Cypress test runner
- cypress/support/index.js
require('cypress-xpath')
を追加するだけでOKなようです
早速やってみます
と思ったのですが Can't use "cy.xpath()" with "Typescript" plugin #28 のissueにある通りtypescriptプラグインを入れているとエラーになるようです
他プラグインとの併用
TypeScript化もさくっといけたしさぁ書くか!と思っていたのに水を差されてしまいました。
上記issueに書いてあるように型定義を入れろと言ってるようです
cypressで使用するTypeScriptのファイルはcypress/tsconfig.json
で設定をしているのでその中のincludeに型定義ファイルがあるディレクトリを指定します
幸いcypress-xpath
に関してはindex.d.ts
が存在したのでディレクトリを指定するだけでOKそうです
- cypress/tsconfig.json
include: [ ..... ..... ..... "../node_modules/cypress-xpath" ]
とすればエラーになることはなくなります
index.d.ts
がないライブラリに関しては適当なディレクトリを作って勝手に定義してしまっても大丈夫には大丈夫です
完全ワークアラウンドですが本家が対応するまではこうするしかないかなといった感じですね
後述するcypress-visual-regression
はこの方法で対応しました
custom-types
というディレクトリを用意してその中にcypress-visual-regression
用の型定義ファイルを用意しました
最終的なcypress/tsconfig.json
と追加した型定義情報
- cypress/tsconfig.json
{ "extends": "../tsconfig.json", "include": ["integration/*.ts", "support/*.ts", "../node_modules/cypress", "../node_modules/cypress-xpath", "custom-types"] }
- cypress/custom-types/cypress-visual-regression.index.d.ts
// for cypress-visual-regression declare namespace Cypress { interface Chainable<Subject> { compareSnapshot(name: string, threashold?: number): void; } }
他には、Cypress.Commands.add
で独自コマンドを入れたりしている場合はその都度型定義ファイルを追加しないと怒られるのでカジュアルにCypress.Commands.add
などをやっている場合は対応が必要そうです
正直面倒だなと思ってしまいますね。。。
何はともあれ実際に書いてみます
- cypress/integration/demo-spec.ts
1つit
を足して同じような処理をxpathで取得して書いて見ました
describe('viewchildren', () => { const urlBase = 'http://localhost:4200/'; it('demo with typescript', () => { cy.visit(`${urlBase}viewchildren`); cy.get('i.fa-exclamation-circle').eq(0).click(); cy.get('div.popover-content') .eq(0) .should('have.text', 'a'); }); it('demo with xpath', () => { cy.visit(`${urlBase}viewchildren`); cy.xpath('//i[contains(@class, "fa-exclamation-circle")]').eq(1).click(); cy.xpath('//div[contains(@class, "popover-content")]') .should('have.text', 'b'); }); });
$ npx cypress run -s cypress/integration/demo-spec.ts ==================================================================================================== (Run Starting) ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ │ Cypress: 3.2.0 │ │ Browser: Electron 59 (headless) │ │ Specs: 1 found (demo-spec.ts) │ │ Searched: cypress/integration/demo-spec.ts │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ ──────────────────────────────────────────────────────────────────────────────────────────────────── Running: demo-spec.ts... (1 of 1) (Results) ┌────────────────────────────┐ │ Tests: 2 │ │ Passing: 2 │ │ Failing: 0 │ │ Pending: 0 │ │ Skipped: 0 │ │ Screenshots: 0 │ │ Video: true │ │ Duration: 18 seconds │ │ Spec Ran: demo-spec.ts │ └────────────────────────────┘ (Video) - Started processing: Compressing to 32 CRF - Compression progress: 74% - Finished processing: /home/vagrant/sandbox/ngx-sample/cypress/videos/demo-spec.ts.mp4 (13 seconds) ==================================================================================================== (Run Finished) Spec Tests Passing Failing Pending Skipped ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ │ ✔ demo-spec.ts 00:18 2 2 - - - │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ All specs passed! 00:18 2 2 - - -
cypress-visual-regression
cypress-visual-regression/command.js at master · mjhea0/cypress-visual-regression
visual regression test用のプラグインもあるようです(中身読んだらそんなに難しいことはしてなさそうだった)
READMEにしたがって使ってみます
- インストール
npm install --save-dev cypress-visual-regression
- cypress/plugin/index.js
const getCompareSnapshotsPlugin = require('cypress-visual-regression/dist/plugin'); module.exports = (on) => { getCompareSnapshotsPlugin(on); };
- cypress/support/command.js
const compareSnapshotCommand = require('cypress-visual-regression/dist/command'); compareSnapshotCommand();
テストのファイルではどこのスナップショットかを識別する名前とfailと判断する閾値を引数として渡します
引数のデフォルトはコード読んだ感じ0になってました
今まで使ってるテストファイルにスナップショットを取るだけのケースを追加します
- cypress/integration/demo-spec.ts
it('demo with screenshot', () => { cy.visit(`${urlBase}viewchildren`); cy.xpath('//button[contains(@class, "btn-primary")]').eq(0).click(); cy.compareSnapshot('demo-screenshot', 0.1); });
- まずはベースの状態のsnapshotをとります
npx cypress run --env type=base --config screenshotsFolder=cypress/snapshots/base
ここで渡しているディレクトリのパスはリグレッションのときに参照するので固定の模様
このような状態のスナップショットが生成されます
次にソースコードに変更を加えて再度実行してみます
今現在a,b,cとあるのでdを追加してから実行してみます
--type=actual
で実行します
$ npx cypress run -s cypress/integration/demo-spec.ts --env type=actual ==================================================================================================== (Run Starting) ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ │ Cypress: 3.2.0 │ │ Browser: Electron 59 (headless) │ │ Specs: 1 found (demo-spec.ts) │ │ Searched: cypress/integration/demo-spec.ts │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ ──────────────────────────────────────────────────────────────────────────────────────────────────── Running: demo-spec.ts... (1 of 1) (Results) ┌────────────────────────────┐ │ Tests: 3 │ │ Passing: 3 │ │ Failing: 0 │ │ Pending: 0 │ │ Skipped: 0 │ │ Screenshots: 1 │ │ Video: true │ │ Duration: 35 seconds │ │ Spec Ran: demo-spec.ts │ └────────────────────────────┘ (Screenshots) - /home/vagrant/sandbox/ngx-sample/cypress/snapshots/actual/demo-spec.ts/demo-screenshot-actual.png (1000x660) (Video) - Started processing: Compressing to 32 CRF - Compression progress: 38% - Compression progress: 78% - Finished processing: /home/vagrant/sandbox/ngx-sample/cypress/videos/demo-spec.ts.mp4 (25 seconds) ==================================================================================================== (Run Finished) Spec Tests Passing Failing Pending Skipped ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ │ ✔ demo-spec.ts 00:35 3 3 - - - │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ All specs passed! 00:35 3 3 - - -
テスト結果としては成功してます
実行するとsnapshots
ディレクトリにそれぞれスナップショットが生成されます
$ tree sypress/snapshots/ cypress/snapshots/ |-- actual | `-- demo-spec.ts | `-- demo-screenshot-actual.png |-- base | `-- demo-spec.ts | `-- demo-screenshot-form-base.png `-- diff `-- demo-spec.ts `-- demo-screenshot-diff.png
- 変更前 -> base
- 変更後 -> actual
- 差分 -> diff
という感じにスクリーンショットが生成されます
base
actual
diff
ちゃんと差分が検出されていますね
このくらいの差分だと0.1
の閾値でもfailしないようです
0にしてfailさせると下記のような出力になりました
npx cypress run -s cypress/integration/demo-spec.ts --env type=actual ==================================================================================================== (Run Starting) ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ │ Cypress: 3.2.0 │ │ Browser: Electron 59 (headless) │ │ Specs: 1 found (demo-spec.ts) │ │ Searched: cypress/integration/demo-spec.ts │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ ──────────────────────────────────────────────────────────────────────────────────────────────────── Running: demo-spec.ts... (1 of 1) (Results) ┌────────────────────────────┐ │ Tests: 3 │ │ Passing: 2 │ │ Failing: 1 │ │ Pending: 0 │ │ Skipped: 0 │ │ Screenshots: 2 │ │ Video: true │ │ Duration: 17 seconds │ │ Spec Ran: demo-spec.ts │ └────────────────────────────┘ (Screenshots) - /home/vagrant/sandbox/ngx-sample/cypress/snapshots/actual/demo-spec.ts/demo-screenshot-actual.png (1000x660) - /home/vagrant/sandbox/ngx-sample/cypress/snapshots/actual/demo-spec.ts/viewchildren -- demo with screenshot (failed).png (1280x720) (Video) - Started processing: Compressing to 32 CRF - Compression progress: 100% - Finished processing: /home/vagrant/sandbox/ngx-sample/cypress/videos/demo-spec.ts.mp4 (10 seconds) ==================================================================================================== (Run Finished) Spec Tests Passing Failing Pending Skipped ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ │ ✖ demo-spec.ts 00:17 3 2 1 - - │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ 1 of 1 failed (100%) 00:17 3 2 1 - -
ちゃんと差分が出てて失敗していることがわかります
これをCircleCIのartifactと組み合わせればビジュアルリグレッションテスト的な事ができそうです
まとめ
結局プラグイン入れただけになってしまいましたがこれで少しづつでもテストコードを書いていける状態が整いました
発端としては
- Renovateを使って定期的にnpmパッケージのバージョンアップをさせたい
- テスト書く必要がある
- Bootstrapだったりデザイン系の変更も検知したい
- ビジュアルリグレッションテスト的なことまでやってみたい
ということでCypressが良さそう!?というのを見つけたので試してみました
良かった点
- 導入までとても簡単
- ビデオまで撮ってくれる
- screenshotがコマンド一発でOK
- 実際の操作時のDOMの状態を保存しているのでデバッグがしやすい
- どこでどういう失敗をしているのかわかりやすい
微妙な点
- jQueryのセレクターかー。。。
- 個人的にはスクレイピング系のDomの指定はxpathでやりたい派
- plugin少ない
- 開発もそこまで活発ではなさそう
次回はこのサンプルプロジェクトでCircleCIを使ってCIを自動化した話かCypressで実際にテストコード書いてったときの詳細の話をできればと思います