Playwright中如何在构造函数中赋值异步方法?最优模式探讨
构造函数中能否赋值异步值?Playwright POM的最优实现模式
问题描述
我采用Page Object Model(POM)管理浏览器相关逻辑,尝试在构造函数中通过Browser实例创建并赋值新的BrowserContext和Page,但由于这些是异步方法,无法直接实现,必须在对象创建后调用额外的initialize方法完成初始化。请问:
- 能否在构造函数中赋值异步值?
- 这种场景下的最优实现模式是什么?
当前实现代码:
AppPage类
export class AppPage { readonly browser: Browser; readonly page: Page; readonly context: BrowserContext; public currentPage: Page; constructor(browser: Browser) { this.browser = browser; // 这里无法直接赋值,因为是异步方法 // this.context = await this.browser.newContext(); // this.page = await context.newPage(); } async initialize() { const context = await this.browser.newContext(); const page = await context.newPage(); // 必须调用额外方法完成初始化 this.currentPage = page; } async goto() { await this.currentPage.goto('https://playwright.dev'); } }
测试代码
test('test using context with page object model', async ({ browser }) => { const app = new AppPage(browser); await app.initialize(); await app.goto(); });
回答
首先明确:构造函数无法直接处理异步赋值
JavaScript/TypeScript的构造函数不能是异步函数,也不允许在构造函数内部使用await关键字,所以你没办法在构造函数里直接完成BrowserContext和Page的异步初始化赋值。这是语言层面的限制——构造函数必须同步返回实例对象,不能返回Promise。
最优实现模式推荐
针对Playwright POM的这个场景,有两种主流的优雅实现方式:
1. 静态工厂方法(推荐)
创建一个静态的异步工厂方法,把实例创建和异步初始化逻辑封装在一起,直接返回已经初始化完成的实例。这样可以避免使用者忘记调用initialize方法,减少出错概率。
修改后的AppPage类:
export class AppPage { readonly browser: Browser; readonly context: BrowserContext; readonly page: Page; // 把构造函数设为私有,强制通过工厂方法创建实例 private constructor(browser: Browser, context: BrowserContext, page: Page) { this.browser = browser; this.context = context; this.page = page; } // 静态工厂方法,完成初始化并返回实例 static async create(browser: Browser) { const context = await browser.newContext(); const page = await context.newPage(); return new AppPage(browser, context, page); } async goto() { await this.page.goto('https://playwright.dev'); } }
对应的测试代码:
test('test using context with page object model', async ({ browser }) => { // 直接通过工厂方法获取已初始化的实例 const app = await AppPage.create(browser); await app.goto(); });
优点:
- 封装性好,使用者无需关心内部初始化逻辑
- 避免了未初始化就调用方法的错误(比如忘记调用
initialize导致currentPage为undefined) - 类型安全,实例属性可以设为
readonly,保证不可变
2. 延迟初始化(懒加载)
如果不想强制使用工厂方法,可以把Page/Context的获取改为异步getter,第一次调用相关方法时自动完成初始化。
修改后的AppPage类:
export class AppPage { readonly browser: Browser; private _context?: BrowserContext; private _page?: Page; constructor(browser: Browser) { this.browser = browser; } // 异步getter,获取context,不存在则创建 private async get context(): Promise<BrowserContext> { if (!this._context) { this._context = await this.browser.newContext(); } return this._context; } // 异步getter,获取page,不存在则创建 private async get page(): Promise<Page> { if (!this._page) { const context = await this.context; this._page = await context.newPage(); } return this._page; } async goto() { // 调用getter自动完成初始化 const page = await this.page; await page.goto('https://playwright.dev'); } }
对应的测试代码:
test('test using context with page object model', async ({ browser }) => { const app = new AppPage(browser); // 调用goto时自动触发初始化,无需手动调用initialize await app.goto(); });
优点:
- 使用者无需关注初始化步骤,使用方式更简洁
- 初始化延迟到实际需要时才执行,适合某些不一定会用到所有功能的场景
缺点:
- 属性不能设为
readonly,因为需要在后续赋值 - 第一次调用方法时会有初始化的性能开销(不过在测试场景下影响极小)
内容的提问来源于stack exchange,提问作者Tabares
相关产品推荐
相关产品推荐

