在TypeScript中通过属性装饰器为属性所属对象添加函数的方法
解决Intern JS 4中属性装饰器访问页面对象实例的问题
我之前在Intern项目里也碰到过类似的装饰器与页面对象实例关联的问题,核心卡点就是属性装饰器在类定义阶段执行,此时还没有实例对象,没法直接拿到实例引用。不过我们可以通过给类原型添加方法,利用方法执行时的this指向来间接获取实例,完美解决这个需求。
方案1:基础版装饰器(给原型添加通用方法)
先写一个参数化装饰器,接收验证消息的XPath,然后给页面对象的原型添加一个获取验证文本的方法。方法执行时this就是当前页面对象实例,就能调用Intern的元素定位方法了。
// 定义验证消息装饰器 function validationMessage(xpath: string) { return function(target: any, propertyKey: string) { // 给类原型添加获取验证消息的异步方法 target.getValidationMessage = async function() { // this 指向当前页面对象实例,直接调用Intern的findByXpath const errorElement = await this.findByXpath(xpath); return errorElement.getText(); }; // 可选:把XPath绑定为实例属性,方便后续复用 Object.defineProperty(target, propertyKey, { get() { return xpath; }, enumerable: true, configurable: true }); }; } // 在页面对象中使用装饰器 class LoginPage extends PageObject { // 用装饰器标记验证消息的XPath @validationMessage('//div[contains(@class, "form-error") and @data-for="username"]') usernameErrorXpath!: string; @findByXpath('//input[@name="username"]') usernameInput!: Element; // 测试中调用装饰器添加的方法 async verifyUsernameRequiredError() { await this.usernameInput.clear(); await this.usernameInput.submit(); const errorText = await this.getValidationMessage(); assert.strictEqual(errorText, "用户名不能为空"); } }
方案2:进阶版装饰器(支持多个验证消息)
如果你的页面对象有多个输入框需要验证(比如用户名、密码),上面的通用方法会冲突。可以改进装饰器,根据属性名自动生成唯一的方法名,或者允许自定义方法名:
function validationMessage(xpath: string, customMethodName?: string) { return function(target: any, propertyKey: string) { // 生成唯一方法名:比如属性是usernameError,方法名就是getUsernameError const methodName = customMethodName || `get${propertyKey.charAt(0).toUpperCase()}${propertyKey.slice(1)}`; target[methodName] = async function() { const errorElement = await this.findByXpath(xpath); return errorElement.getText(); }; // 绑定XPath到属性 Object.defineProperty(target, propertyKey, { get() { return xpath; }, enumerable: true }); }; } // 使用示例 class LoginPage extends PageObject { @validationMessage('//div[@data-for="username"]') usernameError!: string; @validationMessage('//div[@data-for="password"]', 'getPasswordValidationError') passwordError!: string; async checkAllErrors() { const usernameErr = await this.getUsernameError(); const passwordErr = await this.getPasswordValidationError(); assert.strictEqual(usernameErr, "用户名不能为空"); assert.strictEqual(passwordErr, "密码长度不能小于6位"); } }
关键原理说明
属性装饰器在类定义时执行,此时只能拿到类的原型(target参数),还没有实例。但我们给原型添加的方法,会在实例调用时执行,这时方法内部的this就指向当前页面对象实例,自然就能调用Intern提供的findByXpath、getText等异步方法了。
注意事项
- 所有操作元素的方法必须用
async/await,因为Intern的页面对象方法都是基于Promise的。 - 如果用TypeScript,需要给装饰器添加类型定义,避免类型检查报错。
- 方法名要避免和页面对象中已有的方法冲突,自定义方法名是更稳妥的选择。
内容的提问来源于stack exchange,提问作者CodeBlooded
相关产品推荐
相关产品推荐

