使用TestCafe对Angular应用程序进行E2E测试

来自菜鸟教程
跳转至:导航、​搜索

TestCafe 是一个免费的开源 Node.js 工具,用于测试 Web 应用程序。 它的一个主要优点是设置和开始测试大约需要一分钟(并且它不使用 WebDriver)。

它适用于大多数流行的操作系统和浏览器。 测试是用 JavaScript 或 TypeScript 编写的。

在本文中,我们将专门介绍测试 Angular 应用程序。 你将学到如何:

  • 设置测试环境
  • 使用 Angular 选择器创建基本测试
  • 使用页面模型模式改进您的测试
  • 调试失败的测试

测试设置

首先,确保您安装了 Node.js 4.x 或更高版本。

我们需要两个 npm 模块:TestCafe 本身,以及一个带有 Angular 选择器的插件。 运行此命令:

$ npm i testcafe testcafe-angular-selectors

让我们也将 Angular Augury 扩展安装到 Google Chrome。 我们将使用它在应用程序的组件树中查找组件选择器。

就是这样,我们准备好测试了!

编写一个简单的测试

我们将测试一个示例 Book Collection Angular 应用程序,在此处发布:https://miherlosev.github.io/e2e_angular。 我们的第一个测试将只是一个登录表单。

请注意,testcafe-angular-selectors 和 Angular Augry 需要在开发模式下运行的应用程序。


让我们创建一个 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 文件,内容如下:

包.json

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

test 任务从 Google Chrome 中的 index.js 运行测试。 现在运行这个命令:

$ npm test

TestCafe 的报告显示我们的测试已经通过。

使用页面模型模式改进测试

Page Model 是一种模式,您可以在其中创建测试页面的抽象,并使用它来引用页面元素。 如果页面标记发生更改,它有助于节省支持测试的时间。

让我们为登录页面创建一个页面模型。 该页面包含三个元素:用户名和密码输入,以及登录按钮。 我们将整个页面表示为一个 JavaScript 类,并将其元素表示为选择器。

创建一个 page-model.js 文件并添加以下代码:

页面模型.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。 首先,在开头再添加一个import

import { t } from 'testcafe';

t 是一个名为 TestContext 的 TestCafe 对象,它允许执行各种操作,例如单击和键入。

然后在最后添加以下内容:

页面模型.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 在干净的环境中运行每个测试以避免不稳定的测试。 为了本教程的目的,我们将保持简单,但还有一种更高级的授权机制,称为 User Roles。 它允许隔离身份验证并在您需要切换用户帐户时应用它。

如您所见,代码仅包含测试逻辑。 我们不需要等待页面元素加载、动画、XHR 完成或任何其他样板代码。

现在让我们用 npm test 运行测试并查看结果。

测试通过了。 默认情况下,TestCafe 在控制台中显示测试结果。 还有一些插件为 TeamCity、Slack 等提供自定义报告器。

测试失败时定位错误

让我们看看失败的测试是什么样子的。 更改最后一个断言,使测试失败:

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

现在运行测试。

对于每个失败的测试,TestCafe 都会突出显示失败的步骤。 它还报告浏览器、调用站点和其他详细信息,让您快速找到失败的原因。 要修复测试,请改回代码。

接下来是什么:有用的链接

在本教程中,我们使用 TestCafe 和页面模型模式进行了简单的测试。 有关更多示例和建议,请查看 官方文档

您可以在 GitHub 上找到本教程 的完整 代码。

如果您对 TestCafe 有任何疑问,请随时在 论坛 上提问。 有关功能请求和错误,请转到 GitHub 问题页面