Help us understand the problem. What is going on with this article?

【Angular】これからはじめるE2Eテスト(2019)

この記事は Angular #2 Advent Calendar 2019 10日目の記事です

Angular 9なかなかこないですね!みなさまのAngularのアップデート状況はいかがなものでしょうか。

いざアップデートしたはいいものの、ブラウザ上で正常に動作するか手作業で確認するのはなかなかツライものがあります。
また、E2Eで自動化してみたいけど何から手をつけたらいいか分からなかったり、昔からの製品なのでIEでも動作するか確認したい・・・
など色々困っていることはありませんでしょうか?

この記事では、マルチブラウザで動作し (Chrome、Firefox、Edge、IE11など)
Angularで困ったときのナレッジが割と見つかりやすく
Angular CLIでプロジェクトを作成した際に標準でインストールされている Protractor を使って
はじめてE2Eを書くための方法を紹介していきたいと思います。:grinning:

      images.png

対象読者

  • 初めてプロジェクトにE2Eテストを取り入れようと考えている方
  • マルチブラウザのE2E対応が必要な方
  • ProtractorでE2Eの書き方を知りたい方

実行環境

・OS: Windows10 Pro 1903
・Angular: 8.2.14
・AngularCLI: 8.3.20
・Node.JS: v12.13.1
・protractor: 5.4.2
・webdriver-manager: 12.1.7

ログイン画面のE2Eのテストを書く

今回は下のようなログイン画面元に、
値を入力してホーム画面に移動する簡単なE2Eテストを書いて行きたいと思います。

スクリーンショット 2019-12-09 6.40.02.png

また、サンプルコードはこちらにご用意してますので合わせてご確認ください。
https://github.com/hiroyuki-nishi/angular-e2e

1. 選択する要素に識別子を振る

まずは、選択する要素を識別するために data属性を使って識別子を振っていきます。
今回は、メールアドレスとパスワードに値を入力した後に、ボタンをクリックしてログインしたいので
3つの要素に識別子を振っていきます。

login.component.html
<h1>login</h1>
<form>
  <div>
    <mat-form-field>
      <input matInput placeholder="email" [attr.data-test]="'email'"> // ← 要素①: data-*属性で一意となる識別子を振る
    </mat-form-field>
  </div>

  <div>
    <mat-form-field>
      <input type="password" matInput placeholder="passoword" [attr.data-test]="'password'"> // ← 要素②
    </mat-form-field>
  </div>

</form>
<div class="p-button-wrapper">
  <button mat-raised-button class="p-button" color="accent" (click)="login()" [attr.data-test]="'login-button'">Login</button>  // ← 要素③
</div>

E2Eテストで要素を識別するために他に使用される属性として、id属性・class属性などがありますが
これらの属性はデザインの変更などで変更される恐れがあるため
その影響によりテストが動かなくなってしまう可能性があります。

そのため、別のE2EフレームワークであるCypressのBest Practicesに記載されているように
data-*属性のようなセレクターを利用したり、デザインにあまり影響を受けないならid属性を利用するなど
プロジェクトにあった適切な方法で識別子を振っていきましょう。

Cypress Best Practices:Selecting-Elements
https://docs.cypress.io/guides/references/best-practices.html#Selecting-Elements
Protractor style guide:Locator strategies
https://www.protractortest.org/#/style-guide

2. テストを書く

次にE2Eのテストを書いていきます。
ここでは主に、*.e2e-spec.ts*.po.tsの2つのファイルが登場します。

Page Object Patternで設計する

Angular CLIでプロジェクトを作成するとe2e/srcディレクトリ直下に、
*.e2e-spec.ts*.po.tsの2つのファイルが作成されていると思います。

これは、アプリケーションの画面単位で1オブジェクトを定義するPage Objects としてE2Eテストが作成されているためです。

PageObjects とは、アプリケーションの画面を1つのオブジェクトとしてとらえるデザインパターンです。

E2Eテストはデザインの変更を受けやすいという特徴があります。
もし、テストケースに直接セレクタを記述する場合、デザインの変更があった場合、セレクタを記述している全てのテストケースを修正しなければなりません。

そのため、Page Objectsとして、*.po.tsに各画面毎の要素選択などの処理を定義することで
*.e2e-spec.tsは実際のテストケースを手続き的に書いていけるので、コードの見通しがよくなり
また、1度実装したページは使い回しが効くなどのメリットがあります。
より詳細な説明については、最後にまとめている資料リンク先などを確認していただけたらと思います。


はじめてのテスト

上記のような設計思想を頭に入れつつ、まずはlogin.e2e-spec.tsにテストケースを書いていきます。
今回用意したサンプルコードは、ログインが成功するとHome画面に遷移されるので、ログイン後URLが変更されているか確認してみます。

テストケースを書く

login.e2e-spec.ts
import { LoginPage } from './login.po';
import { browser } from 'protractor';


describe('ログインできること', () => {
  const page: LoginPage = new LoginPage();

  it('login', async () => {
    page.login('example@com', 'xxxxx');
    browser.getCurrentUrl().then(url => expect(url).toEqual('http://localhost:4200/#/home'));
  });
});

LoginPageオブジェクトを作成し、ログイン操作を行ったあと
Protractor のbrowser.getCurrentUrl()でURLを取得してJasmine のmatcherであるtoEqual() でURLを比較しています。

他にどのようなAPIがあるかは下記ドキュメントに記載されているので色々試してみてください。:thumbsup:
Protractor APIドキュメント
Jasmine matcherのドキュメント

Page Objectsの実装を書く

login.po.tsでは、ページで行える操作をメソッドに定義していきます。
メールの入力→パスワードの入力→ログインボタンのクリックを行いたいので login()として定義しています。

login.po.ts
import { browser, by, element } from 'protractor';


export class LoginPage {
  private TEST_IDS = {
    EMAIL: 'email',
    PASSWORD: 'password',
    BUTTON: 'login-button',
  };

  private xpathLocator(id: string) {
    return `//*[contains(@data-test, "${id}")]`;
  }

  login(mail: string, password: string): LoginPage {
    browser.get('http://localhost:4200/#/login');
    element(by.xpath(this.xpathLocator(this.TEST_IDS.EMAIL))).sendKeys(mail);
    element(by.xpath(this.xpathLocator(this.TEST_IDS.PASSWORD))).sendKeys(password);
    element(by.xpath(this.xpathLocator(this.TEST_IDS.BUTTON))).click();
    return this;
  }
}

Page Objectsをつかってテストケースからセレクタを追い出していく感じです。

テストを実行する

最後にテストを実行してみます。

// Angularを起動しておく
$ ng serve

// protractorを起動しテストを実行する。 別ターミナルなどで起動してください。
$ npx protractor ./e2e/protractor.conf.js --specs=./e2e/src/**/*

// 特定のファイルのテストを実行する場合
$ npx protractor ./e2e/protractor.conf.js --specs=./e2e/src/login.e2e-spec.ts

無事テストが成功すると下のような結果が出力されていると思います!

[09:09:50] I/launcher - Running 1 instances of WebDriver
[09:09:50] I/hosted - Using the selenium server at http://localhost:4444/wd/hub
Jasmine started

  ログインできること
    ✓ login

Executed 1 of 1 spec SUCCESS in 1 sec.
[09:09:53] I/launcher - 0 instance(s) of WebDriver still running
[09:09:53] I/launcher - chrome #01 passed

ChromeなどでProtractorを使ってE2Eテストを記述する場合は
ここまでの内容でE2Eテストをガリガリ書いていけると思います!

マルチブラウザで同時にテストする

ここからは、Protractorを使ってIEなどでテストを実行するための方法について紹介していきます。

https___qiita-image-store.s3.amazonaws.com_0_3732_85c6a9c5-7810-97f3-0010-b521939faf70.jpeg

Protractorのサイトによると、Protractorは、WebDriverJSというSelenium WebDriver APIのラッパーであり
このWebDriverJS を使って Selenium Server に命令を送り、 Selenium Serverはテストからのコマンドを解釈した後
Browser Driver がブラウザを操作するという仕組みで作られています。

Chrome Firefox などのブラウザの場合
protractor.conf.jsdirectConnect をtrueにすることで、直接Browser Driverを操作することができますが
IEなどのブラウザは、Selenium Server経由でWebDriverを介してブラウザを操作する必要があります。

そのための設定を紹介していきます。

IE11で起動するための準備:

ブラウザオプションの変更

IE11でProtractorを起動するために下記サイトを参考にIE11のオプションを変更していきます。
Run protractor test in IE [Internet Explorer]

変更するのは下記の2点です。

  • IE11 のセキュリティレベルのすべてのゾーンの保護モードを有効にします
  • IE11の拡大率を100%にします

Angular側の変更

browserlistでIE11での起動が除外されているため、下記をコメントアウトします。

browserlist
# not IE 9-11 # For IE 9-11 support, remove 'not'.

また、ng serve上でIE11のE2Eテストを実行したい場合
tsconfig.jsonのtargetをes5変更するか別途es5で起動するtsconfig.jsonファイルを作成する必要があります。
ng serve/test/e2e does not work with Internet Explorer 11 #14455

tsconfig.json
{
    "target": "es5",
    ...
}

Protractor側の変更

Selenium Server経由でWebDriverを介してブラウザを操作させるために
protractor.conf.jsのdirectConnectを無効化し、マルチブラウザで同時に起動するように
capabilitiesの代わりにmultiCapabilitiesを設定します。

protracotor.conf.js
  multiCapabilities: [
    { 'browserName': 'chrome'},
    { 'browserName': 'firefox' }, // Firefoxを追加
    { 'browserName': 'internet explorer'}, // IEを追加
    { 'browserName': 'MicrosoftEdge'} // Edgeを追加
  ],
   // directConnect: true, 無効化

以上でIE11をProtractorで操作できるようになりました!

Edgeで起動するための準備

次は、Edge(chronium版じゃないやつ)の設定です。
まずは、ご自身が使用されているEdgeのバージョンを確認した後、下記サイトからDriverをDLしてきます。

Microsoft WebDriver

Microsoft Edge version 18 以降の場合、下記コマンドを実行することでDLすることができます。

DISM.exe /Online /Add-Capability /CapabilityName:Microsoft.WebDriver~~~~0.0.1.0

DLが完了すると、以下のフォルダにMicrosoftWebDriver.exeがDLされているので
Angularのディレクトリにコピーしておきます。

C:¥Windows¥System32¥MicrosoftWebDriver.exe

以上でProtractorでテストを行える準備は整いました。

テストを実行する

Selenium Server経由でProtractorを実行する場合、Selenium Serverを起動しておく必要があるため起動しておきます。

webdriver-manager start --edge <MicrosoftWebDriver.exeのパス>

あとは、同じようにテストを実行してみます。

$ npx protractor ./e2e/protractor.conf.js --specs=./e2e/src/**/*

Success.png

   _人人人人人人人_
   > Success!! <
    ̄Y^Y^Y^Y^Y^Y^Y^ ̄

マルチブラウザで同時にテストが実行されるようになりました。
4ブラウザが起動して自動で動いていくのを見るのはなかなかおもしろいです。

おわりに

なかなかコストが高く、手を出しづらそうなE2Eテストですが、適切に実装することでプロジェクトの助けになります。
今からでも遅くないので、ぜひ取り入れてみてはいかがでしょうか?

11日目は @kawakami-kazuyoshi さんです。よろしくお願いします!

参考

E2Eテストコードのメンテナンス性に立ち向かう
https://qiita.com/tsuemura/items/c6703efc107314b66d7b
idやclassを使ってテストを書くのは、もはやアンチパターンである
https://qiita.com/akameco/items/519f7e4d5442b2a9d2da
こわくない Protractor
https://qiita.com/shuhei/items/6973fe694d29a193f224
PageObjects
https://github.com/SeleniumHQ/selenium/wiki/PageObjects
Cypress:Best Practices
https://docs.cypress.io/guides/references/best-practices.html

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした