最佳实践

本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2021-02-26

clientScripts

提供 clientScripts API 来在测试期间给页面注入脚本。

// 直接指定脚本的路径
fixture `My fixture`
  .page `http://example.com`
  .clientScripts('../before.ts' as ClientScript); // 参数 string | string[]


// 针对对应的页面注入对应的脚本
// 脚本可以是 JavaScript 程序字符串
fixture
  .clientScripts({
    page: /\/user\/profile\//,
    content: 'a.prototype.getCurrentPosition = () => (0, 0);'
    // path: '../before.ts' // 或者使用 path 指定脚本路径
  });

// 注入第三方模块
fixture `My fixture`
  .page `https://example.com`
  .clientScripts({ module: 'lodash' });

或者通过 ClientFunction() 来定义好需要在客户端需要执行的函数,在测试中随时可以调用。

// client-func/index.ts
import { ClientFunction } from 'testcafe';

export const getPageUrl = ClientFunction(() => window.location.href);

// 请求获取验证码,验证码是图片,识别不出来了,只能通过后端提供接口去查当前对应的验证码是多少
// 在客户端需要使用原生AJAX技术
export const getCaptcha = ClientFunction(() => {
  return new Promise((resolve, reject) => {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function(){
      if (xhr.readyState == 4){
        if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
          resolve(xhr.responseText)
        } else {
          reject("Request was unsuccessful: " + xhr.status)
        }
      }
    };
    xhr.open("get", "https://www.test.com/getCaptcha", true);
    xhr.send(null);
  })
});

在测试中这样使用:

import { getPageUrl, getCaptcha } from '../client-func';

test('页面各栏目是否能够正常跳转', async t => {
  await t.click('.btn');
  await t.expect(getPageUrl()).contains('www.baidu.com');

  const capture = await getCaptcha();
  console.log(capture);
})

使用 t.eval() 也能直接在客户端执行一段代码。

test('demo', async t => {
  await t.eval(() => window.location.reload()); // 刷新页面
})

useRole

如果你的测试页面是需要先登录才能获取数据的话,可以使用 useRole。

import { Role, Selector } from 'testcafe';

const registeredUser = Role('http://example.com/login', async t => {
  await t
    .typeText('#login', 'username')
    .typeText('#password', 'pa$$w0rd')
    .click('#sign-in');
});

fixture `My Fixture`
    .page `http://example.com`;

test('My Test', async t => {
  await t
    .useRole(registeredUser)
    .expect(Selector('#avatar').visible).ok();
// 应该注意是否是同域页面

可以结合 beforeEach 钩子使用。

fixture `My fixture`
  .page `http://example.com`
  .beforeEach( async t => {
    await t
      .useRole(admin)
      .click('#open-management-console');
  });

Page Model

Page Model 是一个测试自动化模式,允许你创建一个测试页面并使用它的抽象测试代码来引用和操作页面元素。

// form.ts
import { Selector, t } from 'testcafe';
import VueSelector from 'testcafe-vue-selectors';

class AccountBindPage {
  accountInput: Selector;
  passwordInput: Selector;
  captchaInput: Selector;
  submitBtn: VueSelector; // Vue组件选择API,https://github.com/DevExpress/testcafe-vue-selectors/blob/master/README.md
  username: Selector;

  constructor () {
    this.accountInput = Selector('.eui-form-item').nth(1).find('.eui-form-content input');
    this.passwordInput = Selector('input[placeholder="输入交易密码"]');
    this.captchaInput = Selector('input[placeholder="输入图形密码"]');
    this.submitBtn = VueSelector('EuiButton');
    this.username = Selector('.show-nickname-asset p:nth-child(1)');
  }

  async typeAndSubmit (account: string, password: string, captcha: string) {
    if (account) {
      await t.typeText(this.accountInput, account)
    }

    if (password) {
      await t.typeText(this.passwordInput, password)
    }

    if (captcha) {
      await t.typeText(this.captchaInput, captcha)
    }
    await t.click(this.submitBtn);
  }
}

export default new Page();

在测试代码中就可以这样使用

import page from '../page-model/form.ts';

fixture `homepage`
  .page('www.test.com')

test('demo1', async t => {
  await t.expect(page.username.textContent).contains('临时用户');
  await page.typeAndSubmit('123456789', '123', '888');
  // 断言登录成功 不再是临时用户
  await t.expect(page.username.textContent).notContains('临时用户');
})

但是想把断言部分也放到Page Model中时,Test Controller将不能自动注入到Selector中,可以在Selector中用{ boundTestRun: t }选项,并手工传入Test Controller来实现:

// page model 中定义
async validateTurntableElement(t) {
  const actLuckUnit0 = Selector('.pf-lottery-box .luck-unit-0', { boundTestRun: t });
    
  await t.expect(actLuckUnit0.exists).ok()
}

// 使用的时候
import page from '../page-model/form.ts';

fixture `homepage`
  .page('www.test.com')

test('demo1', async t => {
  await page.validateTurntableElement(t);
})

helper

helper 这个模式类似于 Page Model,都是用来抽取公共代码的,但是helper允许你页面结构和测试逻辑抽象出来。

// helper.js
import { t } from 'testcafe';

export async function enterName(name) {
  await t.typeText('#developer-name', name);
};

export async function typeComment(text) {
  await t
    .click('#tried-test-cafe')
    .typeText('#comments', text);
};

export async function submitForm() {
  await t.click('#submit-button');
};

// test/a.js
import { Selector } from 'testcafe';
import { enterName, typeComment, submitForm } from './helper.js';

fixture `My Fixture`
  .page `https://devexpress.github.io/testcafe/example/`;

test('My Test', async t => {
  const name = 'John Heart';
  await enterName(name);
  await typeComment('Here is what I think...');
  await submitForm();
  await t.expect(Selector('#article-header').textContent).contains(name);
});

addCustomMethods & addCustomDOMProperties

这两个 API 用来为 Selector 扩展方法或属性。

interface CustomSelector extends Selector {
  innerHTML: Promise<any>;
}

const label = <CustomSelector>Selector('label').addCustomDOMProperties({
  innerHTML: el => el.innerHTML; // 参数el为该Selector所选中的DOM元素
  // 原本的 Selector对象是没有 innerHTML 属性的
});

await t.expect(label.innerHTML).contains('input type="checkbox" name="remote"');


interface CustomSelector extends Selector {
  getExpandButtonCell(rowIndex: number): Selector;
  getCellText(rowIndex: number, columnIndex: number): string;
}

fixture `My fixture`
    .page `https://js.devexpress.com/`;

test('My test', async t => {
  const myTable = <CustomSelector>Selector('#customers').addCustomMethods({
    getExpandButtonCell: (elements: HTMLCollection, rowIndex: number) => {
      // 第一个参数为调用该方法的元素,后面的参数就是调用时需要传入的
      return elements[0].querySelectorAll('.dx-group-row')[rowIndex].cells[0];
    }
  }, {
    returnDOMNodes: true, // 如果该方法需要返回的是一个DOM元素,需要为 addCustomMethods 传递该选项 
  })
  .addCustomMethods({
    getCellText: (elements: HTMLCollection, rowIndex: number) => {
      return elements[0].querySelectorAll('.dx-group-row')[rowIndex].cells[0].innerText;
    }
    // 返回其他,比如字符串或者对象,则无需 returnDOMNodes 选项
  });

  await t.expect(myTable.getCellText(3, 1)).contains('Europe')
  await t.click(myTable.getExpandButtonCell(0))
});