TestCafe は、Webアプリケーションをテストするための無料のオープンソースNode.jsツールです。 その主な利点の1つは、セットアップとテストの開始に約1分かかることです(WebDriverを使用しません)。

最も一般的なオペレーティングシステムとブラウザで動作します。 テストはJavaScriptまたはTypeScriptで記述されています。

この記事では、Angularアプリケーションのテストについて具体的に説明します。 次の方法を学習します:

  • テスト環境のセットアップ
  • Angularセレクターを使用して基本的なテストを作成する
  • ページモデルパターンでテストを改善する
  • 失敗したテストをデバッグする

テスト用のセットアップ

まず、Node.jsバージョン4.x以降がインストールされていることを確認します。

2つのnpmモジュールが必要です。TestCafe自体と、Angularセレクターを備えたプラグインです。 次のコマンドを実行します。

$ npm i testcafe testcafe-angular-selectors

AngleAugury拡張機能もGoogleChromeにインストールしましょう。 これを使用して、アプリケーションのコンポーネントツリーでコンポーネントセレクターを検索します。

Component Tree view in Angular Augury

これで、テストの準備が整いました。

簡単なテストを書く

サンプルのBookCollection Angularアプリケーションをテストします。ここで公開されています:https://miherlosev.github.io/e2e_angular。 最初のテストは単なるログインフォームです。

Login page of the sample application

testcafe-angular-selectorsとAngularAugryには、開発モードで実行されているアプリケーションが必要であることに注意してください。

index.jsファイルを作成し、次のコードを追加しましょう。

index.js

import { AngularSelector, waitForAngular } from 'testcafe-angular-selectors';

fixture `Book Collection`
  .page('https://miherlosev.github.io/e2e_angular/')
  .beforeEach(async t => {
    await waitForAngular();
  });

test('Login', async t => {
  const loginForm = AngularSelector('bc-login-form');

  await t
    .typeText(loginForm.find('#md-input-1'), 'test')
    .typeText(loginForm.find('#md-input-3'), 'test')
    .click(loginForm.find('.mat-button'));
});

これが何が起こるかです。 Angularセレクターのインポートから始めます。 これらを使用して、コンポーネントセレクターでページ要素をアドレス指定します。

フィクスチャでは、テストされたページを指定し、beforeEachテストフックを使用します。 このフックは、各テストを実行する前に特定のアクションを実行する必要がある場合に役立ちます。 この場合、waitForAngularメソッドを使用すると、Angularコンポーネントツリーが読み込まれるまで待機できます。 その後、テストではコンポーネント名でログインフィールドとパスワードフィールドを見つけ、それらに入力してログインボタンを押します。

テストを実行してみましょう。 次の内容のpackage.jsonファイルを作成します。

package.json
{
  "scripts": {
    "test": "testcafe chrome index.js"
  }
}

testタスクは、GoogleChromeのindex.jsからテストを実行します。 次に、次のコマンドを実行します。

$ npm test

The first test has passed

TestCafeのレポートは、テストに合格したことを示しています。

ページモデルパターンでテストを改善する

ページモデルは、テストされたページの抽象化を作成し、それを使用してページ要素を参照するパターンです。 ページのマークアップが変更された場合にテストをサポートする時間を節約するのに役立ちます。

ログインページのページモデルを作成しましょう。 このページには、ユーザー名とパスワードの入力、およびログインボタンの3つの要素があります。 ページ全体をJavaScriptクラスとして表し、その要素をセレクターとして表します。

page-model.jsファイルを作成し、次のコードを追加します。

page-model.js
import { AngularSelector } from 'testcafe-angular-selectors';

export class LoginPage {
  constructor () {
    const loginForm = AngularSelector('bc-login-form');

    this.username = loginForm.find('#md-input-1');
    this.password = loginForm.find('#md-input-3');
    this.loginBtn = loginForm.find('.mat-button');
  }
}

AngularSelectorコンストラクターを使用します。 コンポーネントセレクターをスペースで区切ってこのコンストラクターに渡すことができ、ネイティブのTestCafeセレクターを返します。 TestCafeセレクターを使用すると、タグ名、IDなどによる追加のフィルタリングが可能になります。 この例では、findメソッドを使用して、テキストフィールドとボタンを検索します。

それでは、index.jsをリファクタリングしましょう。 テストからセレクター関連のコードを抽出し、ページモデルに移動しました。 この後、テストコードにはページ要素との相互作用のみが含まれます。

import { waitForAngular } from 'testcafe-angular-selectors';
import { LoginPage } from './page-model.js';

const loginPage = new LoginPage();

fixture `Book Collection`
  .page('https://miherlosev.github.io/e2e_angular/')
  .beforeEach(async t => {
    await waitForAngular();
});

test('Login', async t => {
  await t
    .typeText(loginPage.username, 'test')
    .typeText(loginPage.password, 'test')
    .click(loginPage.loginBtn);
});

同じコマンドでテストを実行し、以前と同じように機能することを確認します。

より複雑なテストを作成する

私たちのテストは、現時点では実際には何もチェックしていません。 アサーションを追加しましょう。特定の本を見つけるようにテストを変更し、検索結果が空でないことを確認します。

ページモデルパターンを続行するので、page-model.jsを更新しましょう。 まず、最初にもう1つimportを追加します。

import { t } from 'testcafe';

tは、TestContextと呼ばれるTestCafeオブジェクトであり、クリックや入力などのさまざまなアクションを実行できます。

そして、最後に以下を追加します。

page-model.js
class BasePage {
  constructor () {
    const navigationItem = AngularSelector('bc-nav-item');

    this.toolbar = AngularSelector('bc-toolbar');
    this.sidenav = {
      myCollectionNavItem: navigationItem.find('a').withText('My Collection'),
      browseBooksNavItem:  navigationItem.find('a').withText('Browse Books')
    };

    this.openToolbarBtn = this.toolbar.find('button');
  }

  async navigateToBrowseBooksPage () {
    await t
      .click(this.openToolbarBtn)
      .click(this.sidenav.browseBooksNavItem);
  }
}

export class FindBookPage extends BasePage {
  constructor () {
    super();

    this.searchInput  = AngularSelector('bc-book-search').find('input');
    this.bookPreviews = AngularSelector('bc-book-preview');
  }
}

BasePageクラスは、他のページ(ツールバーとメインメニュー)間で共有されるUI要素を処理します。 他のすべてのページの場合、例: FindBookPage、このクラスを拡張するだけです。

次に、index.jsを次のように変更します。

import { waitForAngular } from 'testcafe-angular-selectors';
import { LoginPage, FindBookPage, } from './page-model';

const loginPage = new LoginPage();
const findBookPage = new FindBookPage();

fixture `Book Collection`
  .page('https://miherlosev.github.io/e2e_angular/')
  .beforeEach(async t => {
    await waitForAngular();

    await t
      .typeText(loginPage.username, 'test')
      .typeText(loginPage.password, 'test')
      .click(loginPage.loginBtn);
  });

test("find a book", async t => {
  await findBookPage.navigateToBrowseBooksPage();

  await t
    .typeText(findBookPage.searchInput, 'The Hunger Games')
    .expect(findBookPage.bookPreviews.count).gt(0);
});

承認をbeforeEachテストフックに移動したことに注意してください。 これは、アプリに承認が必要であり、TestCafeが各テストをクリーンな環境で実行して、不安定なテストを回避するためです。 このチュートリアルでは簡単に説明しますが、ユーザーロールと呼ばれるより高度な承認メカニズムもあります。 これにより、認証を分離して、ユーザーアカウントを切り替える必要があるときにいつでも適用できます。

ご覧のとおり、コードにはテストロジックのみが含まれています。 ページ要素の読み込み、アニメーション、XHRの完了、またはその他の定型コードを待つ必要はありません。

次に、npm testを使用してテストを実行し、結果を確認しましょう。

The second test has passed

テストに合格しました。 デフォルトでは、TestCafeはテスト結果をコンソールに表示します。 TeamCity、Slackなどのカスタムレポーターを提供するプラグインもあります。

テストが失敗したときのエラーの特定

失敗したテストがどのように見えるか見てみましょう。 テストが失敗するように、最後のアサーションを変更します。

.expect(findBookPage.bookPreviews.count).eql(0);

次に、テストを実行します。

TestCafe highlights the line where the test failed

失敗したテストごとに、TestCafeは失敗したステップを強調表示します。 また、ブラウザ、コールサイト、およびその他の詳細を報告するため、失敗の理由をすばやく見つけることができます。 テストを修正するには、コードを元に戻します。

このチュートリアルでは、TestCafeとページモデルパターンを使用して簡単なテストを行いました。 その他の例と推奨事項については、公式ドキュメントをご覧ください。

完全なコードは、GitHubのこのチュートリアルから見つけることができます。

TestCafeについて質問がある場合は、フォーラムでお気軽にお問い合わせください。 機能のリクエストとバグについては、GitHubの問題ページにアクセスしてください。