TestCafe 单页应用Page Models架构设计与性能优化问题咨询
关于按需导入section的可行性
这种方案是完全合理的,而且能直接解决你现在遇到的TestCafe启动慢的问题。你当前的性能瓶颈本质是CarPage构造阶段就全量初始化了所有section,不管测试用例是否用到,连带所有层级依赖都会被提前加载解析,项目越大启动速度越慢。
对于涉及多个section的长流程测试,这种方案依然适用,你只需要导入当前测试用到的所有section即可,额外的导入/实例化代码量非常少,换来的启动速度收益远大于这点编码成本。如果有高频用到的section组合,你也可以单独封装成组合导出项,进一步减少重复代码。
符合约束的调整方案
你不需要完全推翻现有结构,只要把业务section的实例化逻辑从CarPage中剥离即可,完全可以保留你现有的两个约束:
- 仍支持带参数的构造函数:section可以接收对应容器的Selector作为入参
- 仍支持单个文件导出所有section类:你原来的
CarPageSections.ts可以继续保留,测试用例按需导入需要的类即可
调整后的代码参考:
精简后的CarPage
import {Selector, t} from 'testcafe'; import { BasePage } from './BasePage'; import { SectionControl } from './SectionControl'; import { ModelSelectionModal, SomeOtherModal } from './Modals'; export class CarPage extends BasePage { // 仅保留页面通用元素、全局弹窗和轻量的section容器定义 CarSelectionTimer: Selector; ModelSelectionModal: ModelSelectionModal; SomeOtherModal: SomeOtherModal; // 这里只初始化轻量的SectionControl,不会引入业务section的重依赖 sectionControls = { Consumer: new SectionControl('Consumer'), Manufacturer: new SectionControl('Manufacturer'), Supplier: new SectionControl('Supplier'), // 其他section的容器定义保留 } constructor() { super(); this.CarSelectionTimer = Selector('#car-selection-timer'); this.ModelSelectionModal = new ModelSelectionModal(); this.SomeOtherModal = new SomeOtherModal(); } // 页面通用的全局方法保留 async SomeActionToPerformOnThePage(params) { // 原逻辑不变 } }
测试用例写法
import { CarPage } from './CarPage'; // 按需导入当前测试需要的section,未用到的不会被加载 import { ConsumerSection, SupplierSection } from './CarPageSections'; const carPage = new CarPage(); // 仅实例化用到的section,传入对应容器即可 const consumerSection = new ConsumerSection(carPage.sectionControls.Consumer.control); const supplierSection = new SupplierSection(carPage.sectionControls.Supplier.control); fixture`汽车页面测试` .page`你的测试页面地址`; test('跨section长流程测试示例', async t => { await consumerSection.fillBaseInfo({name: '测试用户', phone: '13xxxxxxxxx'}); await supplierSection.uploadQualification('./test.pdf'); await carPage.SomeActionToPerformOnThePage({submit: true}); await t.expect(carPage.CarSelectionTimer.exists).ok(); })
探讨问题解答
1. 是否应当让所有Page Model之间完全解耦?
不需要追求绝对的完全解耦,做好层级隔离即可:
- 基础层(BasePage、通用控件、工具方法)可以正常复用,不需要强行解耦
- 业务层的Page、Section之间不要做强依赖关联,比如不要在Section内部直接导入并实例化父Page,所有依赖通过构造函数传入即可。这样既可以避免循环依赖,也能提升Section的复用性,同一个Section可以复用到其他包含同类型模块的页面中。
2. 操作方法返回页面对象的模式在TestCafe中如何实现?
TestCafe完全支持这种流畅调用模式,实现非常简单,只需要在异步操作方法的末尾返回对应页面的实例即可,示例如下:
// 登录页Page Model示例 import { t, Selector } from 'testcafe'; import { HomePage } from './HomePage'; export class LoginPage { usernameInput = Selector('#username'); passwordInput = Selector('#password'); submitBtn = Selector('#login-btn'); async submitLogin(username: string, password: string) { await t .typeText(this.usernameInput, username) .typeText(this.passwordInput, password) .click(this.submitBtn); // 跳转完成后先验证首页元素加载完成,再返回实例更稳妥 await t.expect(new HomePage().welcomeBanner.exists).ok(); return new HomePage(); } }
测试用例中可以直接链式调用:
import { LoginPage } from './LoginPage'; const loginPage = new LoginPage(); fixture`登录流程测试`.page`/login`; test('登录成功后跳转到首页', async t => { // 直接接收返回的首页实例进行后续操作 const homePage = await loginPage.submitLogin('admin', 'test123'); await t.expect(homePage.userNameText.innerText).eql('管理员'); })
内容的提问来源于stack exchange,提问作者Trogburn
相关产品推荐
相关产品推荐

