You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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');
  });
});

关键建议

  1. 适配WebdriverIO版本:如果用v5+,一定要替换废弃API:element()→$(),waitForVisible()→waitForDisplayed(),scroll()→scrollIntoView(),避免不必要的报错。
  2. 优先使用单例模式:WebdriverIO测试中,页面对象不需要多实例,单例模式更简洁,也能避免循环导入的潜在问题。
  3. 必须用async/await:WebdriverIO的所有操作都是异步的,v5+强制要求用async/await处理,否则会出现时序混乱,导致测试失败。
  4. 链式调用的核心:每个方法返回当前实例(return this)或者目标页面对象的单例,就能实现流畅的链式调用。
  5. 避免循环依赖:尽量让页面对象的依赖层级清晰,比如基础Page类作为父类,子类之间尽量单向依赖,如果必须互相依赖,确保导出的是实例而非类。

内容的提问来源于stack exchange,提问作者Andrei S

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:37:34