WebDriverIO中$与$$的返回类型及Protractor迁移后filter方法调用异常的解决方案咨询
解决WebDriverIO中包装$$后无法调用ChainablePromiseArray方法的问题
我当初从Protractor转WebDriverIO的时候,也踩过这个一模一样的坑!咱们来拆解下问题根源,再给你几个可行的解决方案:
问题到底出在哪?
WebDriverIO里的$$(locator)返回的是**ChainablePromiseArray**——这是一个带链式调用能力的Promise类对象,它自带的filter方法是专门处理异步元素操作的。但你的代码出现两种异常:
- 加
await后,ChainablePromiseArray会被解析成普通的Element数组,此时调用的就是JS原生Array.filter,它不支持异步回调(会把Promise直接当作true处理,完全达不到过滤效果); - 不加
await却提示filter未定义,大概率是TypeScript的类型推断出了问题,没有识别到返回值是ChainablePromiseArray,导致编辑器/运行时认为这是个普通Promise,找不到链式方法。
方案1:明确指定返回类型,保留原生链式能力
最简单的修复方式就是给你的getAllElement方法明确标注返回类型,让TypeScript正确识别ChainablePromiseArray:
import { ChainablePromiseArray, ElementArray } from 'webdriverio'; class ElementWrapper { public getAllElement(locator: string): ChainablePromiseArray<ElementArray> { return $$(locator); } }
之后调用的时候直接链式调用filter,不用提前await(WebDriverIO会自动处理异步逻辑):
// 方式1:链式调用后用then处理结果 elementWrapperInstance.getAllElement('locator') .filter(async (e) => { const text = await e.getText(); return text.includes('你要找的内容'); }) .then((filteredElements) => { // 这里拿到的是过滤后的元素数组 console.log(filteredElements.length); }); // 方式2:直接await整个链式调用,拿到最终结果 const filteredElements = await elementWrapperInstance.getAllElement('locator') .filter(async (e) => { return (await e.getAttribute('data-id')) === 'target-id'; });
方案2:封装专属链式方法(更优雅的封装)
如果想让你的ElementWrapper更贴合业务,也可以把常用的WebDriverIO方法封装进去,对外暴露更简洁的API:
import { ChainablePromiseArray, ChainablePromiseElement, WebdriverIO } from 'webdriverio'; class ElementWrapper { private readonly locator: string; constructor(locator: string) { this.locator = locator; } // 内部获取ChainablePromiseArray的私有方法 private get elementChain(): ChainablePromiseArray<WebdriverIO.ElementArray> { return $$(this.locator); } // 封装filter方法 public filter(predicate: (elem: WebdriverIO.Element) => Promise<boolean>) { return this.elementChain.filter(predicate); } // 可以继续封装其他常用方法,比如获取第一个元素、映射元素属性等 public first(): ChainablePromiseElement<WebdriverIO.Element> { return this.elementChain.first(); } public map<T>(fn: (elem: WebdriverIO.Element) => Promise<T>) { return this.elementChain.map(fn); } }
调用的时候就更直观了:
const userListWrapper = new ElementWrapper('.user-item'); // 过滤出名字包含"John"的用户元素 const johnElements = await userListWrapper.filter(async (elem) => { const userName = await elem.$('.name').getText(); return userName.includes('John'); });
关键提醒
千万别用await拿到普通数组后再用原生Array.filter处理异步逻辑!因为原生filter不会等待Promise完成,会直接把每个异步回调返回的Promise当作true,最后得到的是原数组的Promise集合,完全不是你想要的过滤结果。
内容的提问来源于stack exchange,提问作者Tango
相关产品推荐
相关产品推荐

