WebdriverIO中如何返回有效页面对象并实现链式调用?
WebdriverIO链式调用问题:"_page1.default is not a constructor" 解决方案
首先,你的错误根源很明确:你在page1.js里导出的是Page1类的实例,而不是类本身。当你在page2.js里尝试new Page1()时,Page1已经是一个实例对象,不是构造函数,所以会抛出这个错误。
WebdriverIO完全支持页面对象的链式调用,只是需要调整你的导出方式和返回逻辑,下面是具体的解决方案和最佳实践示例:
错误原因拆解
看你的page1.js最后一行:
export default new Page1()
这里你导出的是Page1类的一个实例,不是类。所以在page2.js中导入的Page1是一个对象,不是可new的构造函数,自然无法用new Page1()创建新实例。
解决方案:两种链式调用实现方式
方式1:复用单例实例(推荐,符合WebdriverIO社区惯例)
保持页面对象的单例导出,在方法中直接返回已有的单例实例,而不是new新对象:
修改page2.js的fillForm方法:
fillForm(name, title) { this.element2.setValue(name); this.element3.setValue(title); this.element4.click(); return Page1; // Page1是你已经导出的单例实例,直接返回它 }
这样你的链式调用代码Page2.clickElement1Page2().fillForm("text", "text").clickElement1Page1();就能正常工作,因为fillForm返回的是Page1的单例,接着就能调用它的clickElement1Page1方法。
方式2:导出类而非实例
如果你更喜欢控制实例创建,可以修改页面对象导出类,在需要时实例化:
修改page1.js的导出:
export default Page1; // 导出类,不是实例
修改page2.js的导出:
export default Page2; // 导出类
然后在测试代码中实例化:
import Page1 from './page1'; import Page2 from './page2'; const page2 = new Page2(); page2.open(); page2.clickElement1Page2().fillForm("text", "text").clickElement1Page1();
这种方式适合需要多实例的场景,但在WebdriverIO测试中很少用到,因为单例足够满足需求。
符合WebdriverIO最佳实践的页面对象模型示例
结合v5+版本的API规范(废弃旧API,使用新API),给你一个可靠的示例:
基础Page类
// page.js export default class Page { open(path) { return browser.url(`/#/${path}`); } get title() { return browser.getTitle(); } get header() { return browser.$('.Header'); // 用$代替element() } get logoutLink() { return browser.$('a.logout'); } }
Page1(单例导出)
// page1.js import Page from './page'; class Page1 extends Page { get element1() { return browser.$('selector1'); } get element2() { return browser.$('selector2'); } async clickElement1() { // 用async/await处理异步操作 await this.element1.scrollIntoView(); // 用scrollIntoView代替scroll() await this.element1.waitForDisplayed(); // 用waitForDisplayed代替waitForVisible() await this.element1.click(); return this; // 返回当前实例,支持链式调用 } } export default new Page1();
Page2(单例导出)
// page2.js import Page from './page'; import Page1 from './page1'; class Page2 extends Page { get element1() { return browser.$('selector1'); } get element2() { return browser.$('selector2'); } get element3() { return browser.$('selector3'); } get element4() { return browser.$('selector4'); } async open() { await super.open('page2'); return this; } async clickElement1() { await this.element1.scrollIntoView(); await this.element1.waitForDisplayed(); await this.element1.click(); return this; } async fillForm(name, title) { await this.element2.setValue(name); await this.element3.setValue(title); await this.element4.click(); return Page1; // 返回Page1单例,继续链式调用 } } export default new Page2();
测试文件中的链式调用
// example.spec.js import Page2 from '../pageobjects/page2'; describe('Chained action test', () => { it('should complete form and navigate', async () => { await Page2.open(); // 链式调用:Page2 -> fillForm返回Page1 -> 调用Page1的clickElement1 await Page2.clickElement1().fillForm("test name", "test title").clickElement1(); const headerText = await Page2.header.getText(); expect(headerText).to.equal('Expected Header Text'); }); });
关键建议
- 适配WebdriverIO版本:如果用v5+,一定要替换废弃API:
element()→$(),waitForVisible()→waitForDisplayed(),scroll()→scrollIntoView(),避免不必要的报错。 - 优先使用单例模式:WebdriverIO测试中,页面对象不需要多实例,单例模式更简洁,也能避免循环导入的潜在问题。
- 必须用async/await:WebdriverIO的所有操作都是异步的,v5+强制要求用async/await处理,否则会出现时序混乱,导致测试失败。
- 链式调用的核心:每个方法返回当前实例(
return this)或者目标页面对象的单例,就能实现流畅的链式调用。 - 避免循环依赖:尽量让页面对象的依赖层级清晰,比如基础Page类作为父类,子类之间尽量单向依赖,如果必须互相依赖,确保导出的是实例而非类。
内容的提问来源于stack exchange,提问作者Andrei S
相关产品推荐
相关产品推荐

