使用TestCafe实现流畅的PageModel API



我试图在TestCafe中为测试作者提供一个流畅的PageModel api,比如:

await MyApp // a Page Model class instance  
.navigateTo(xyz) // clicks a button to navigate to a specific part in my app  
.edit() // clicks the edit button  
.setField(abc, 12.34)  
.save()  
.changeStatus('complete'); 

我让所有单独的方法都作为异步方法工作,可以单独等待,但这使得代码非常不可读,因此很容易出错。

然而,无论我试图以何种方式使api流畅,都会导致以下错误:

Selector无法隐式解析其所在上下文中的测试运行应执行。如果您需要从Node.js API调用Selector回调,首先通过Selector的.with({ boundTestRun: t })方法手动传递测试控制器。请注意,您不能执行测试代码外的选择器。

制作一个流畅的异步api的诀窍是imho从异步函数切换到常规函数作为方法,并让这些方法返回一个可调的"this"值。为了防止等待振荡,调用后需要删除"then"函数(然后在时重新安装

下面可以看到一个重现该问题的非常基本的例子:

import { Selector } from 'testcafe'
class MyPage {
queue: [];
async asyncTest() {
return await Selector(':focus').exists;
}
queuedTest() {
this.then = (resolve, reject) => {
delete this.then; // remove 'then' once thenable gets called to prevent endless loop
// calling hardcoded method, in a fluent api would processes whatever is on the queue and then resolve with something
resolve(this.asyncTest());
};
// In a real fluent api impl. there would be code here to put something into the queue 
// to execute once the 'then' method gets called
// ...
return this;
}
}
fixture `Demo`
.page `https://google.com`;

test('demo', async () => {
const myPage = new MyPage();
console.log('BEFORE')
await myPage.asyncTest();
console.log('BETWEEN')
await myPage.queuedTest(); // Here it bombs out
console.log('AFTER')
});

请注意,上面的示例并没有展示fluent api,它只是表明通过"then"函数(imho是创建fluent api的关键(调用使用Selector的方法会导致上述错误。

注意:我知道这个错误意味着什么,建议将.with({boundTestRun: t})添加到选择器中,但这会导致需要的样板代码,并降低可维护性。

任何想法都值得赞赏P.

在您的示例中,选择器无法评估,因为它无法访问测试控制器(t(。您可以尝试避免在没有断言的情况下直接评估选择器。

以下是我的链接页面模型示例(基于本文:节点中的异步方法链接(:

页面型号:

import { Selector, t } from 'testcafe';
export class MyPage {
constructor () {
this.queue = Promise.resolve();
this.developerName = Selector('#developer-name');
this.submitButton  = Selector('#submit-button');
this.articleHeader = Selector('#article-header');
}
_chain (callback) {
this.queue = this.queue.then(callback);
return this;
}
then (callback) {
return callback(this.queue);
}
navigateTo (url) { 
return this._chain(async () => await t.navigateTo(url));
}
typeName (name) { 
return this._chain(async () => await t.typeText(this.developerName, name));
}
submit () {
return this._chain(async () => await t.click(this.submitButton));
}
checkName (name) {
return this._chain(async () => await t.expect(this.articleHeader.textContent).contains(name));
}
getHeader () {
this._chain(async () => console.log(await this.articleHeader.textContent));
return this;
}
}

测试:

import { MyPage } from "./page-model";
fixture`Page Model Tests`;
const page = new MyPage();
test('Test 1', async () => {
await page
.navigateTo('http://devexpress.github.io/testcafe/example/')
.typeName('John')
.submit()
.checkName('John')
.getHeader();
});

最新更新