Playwright/TS POM框架中locator.waitFor报Target closed错误解决
问题:Playwright/TypeScript POM框架登录后断言Dashboard文本出现"Target closed"错误
错误日志
Error: locator.waitFor: Target closed =========================== logs =========================== waiting for getByRole('heading', { name: 'Dashboard' }) to be visible ============================================================ at ../pages/DashboardPage.ts:32 30 | 31 | async isDashboardTxtVisible(){ > 32 | await this.txtDashboard.waitFor({state: "visible"}); | ^ 33 | return this.txtDashboard.isVisible(); 34 | } 35 | } at DashboardPage.isDashboardTxtVisible (/Users/ivit/Desktop/Projects/QA/Playwright/PWPOM_V4/pages/DashboardPage.ts:32:31) at /Users/ivit/Desktop/Projects/QA/Playwright/PWPOM_V4/tests/login.spec.ts:8:34
相关代码
测试文件 login.spec.ts
import { test, expect } from '@playwright/test'; import { App } from '../pages/App'; test("Checking whether user can logged in", async({page}) => { const app = new App(page); await app.LoginPage.visit(); await app.LoginPage.login("Admin","admin123"); await expect(app.DashboardPage.isDashboardTxtVisible()).toBeTruthy(); });
DashboardPage.ts
import { Locator, Page } from "@playwright/test"; export class DashboardPage { readonly page: Page; readonly lnkLeave: Locator; readonly lnkPIM: Locator; readonly txtDashboard: Locator; constructor(page: Page){ this.page = page; this.lnkLeave = page.getByPlaceholder('Username'); this.lnkPIM = page.getByPlaceholder('Password'); this.txtDashboard = page.getByRole('heading', { name: 'Dashboard' }); } async clickLinkLeave(){ this.lnkLeave.click(); } async clickLinkPIM(){ this.lnkPIM.click(); } async isDashboardTxtVisible(){ await this.txtDashboard.waitFor({state: "visible"}); <<<< Here is the issue return this.txtDashboard.isVisible(); } }
LoginPage.ts
import { Locator, Page } from "@playwright/test"; export class LoginPage { readonly page: Page; readonly txtUsername: Locator; readonly txtPassword: Locator; readonly btnLogin: Locator; constructor(page: Page){ this.page = page; this.txtUsername = page.getByPlaceholder('Username'); this.txtPassword = page.getByPlaceholder('Password');; this.btnLogin = page.getByRole('button', { name: ' Login' }); } async login(username: string, password: string){ await this.txtUsername.type(username); await this.txtPassword.type(password); await this.btnLogin.click(); } async visit(){ await this.page.goto("https://opensource-demo.orangehrmlive.com/web/index.php/auth/login"); } }
App.ts(页面类包装器)
import { Page } from "@playwright/test"; import { LoginPage } from "./LoginPage"; import { DashboardPage } from "./DashboardPage"; export class App { protected page: Page; constructor(page: Page){ this.page = page; } public get LoginPage(): LoginPage{ return new LoginPage(this.page); } public get DashboardPage(): DashboardPage{ return new DashboardPage(this.page); } }
问题原因
- DashboardPage元素定位错误:
lnkLeave和lnkPIM错误复用了登录页的Username/Password占位符定位器,这些元素在Dashboard页面不存在,导致Playwright定位上下文混乱,触发"Target closed"错误。 - 断言逻辑处理不当:测试中直接将异步函数
isDashboardTxtVisible()传入expect,未正确处理Promise,无法捕获函数内部的错误,也无法正确验证元素状态。 - 冗余的等待逻辑:
isDashboardTxtVisible()中同时调用waitFor和isVisible(),Playwright的isVisible()本身会自动等待元素出现,重复等待可能引发逻辑冲突。
修复方案
1. 修正DashboardPage的元素定位与方法
将错误的定位器替换为Dashboard页面的正确元素定位,并补充缺失的await:
import { Locator, Page } from "@playwright/test"; export class DashboardPage { readonly page: Page; readonly lnkLeave: Locator; readonly lnkPIM: Locator; readonly txtDashboard: Locator; constructor(page: Page) { this.page = page; // 修正Leave和PIM链接的正确定位 this.lnkLeave = page.getByRole('link', { name: 'Leave' }); this.lnkPIM = page.getByRole('link', { name: 'PIM' }); this.txtDashboard = page.getByRole('heading', { name: 'Dashboard' }); } async clickLinkLeave() { await this.lnkLeave.click(); // 补充await确保操作完成 } async clickLinkPIM() { await this.lnkPIM.click(); // 补充await确保操作完成 } async isDashboardTxtVisible() { // 移除冗余waitFor,isVisible()会自动等待元素可见 return this.txtDashboard.isVisible(); } }
2. 修正测试用例的断言方式
推荐直接使用Playwright的locator断言(更符合最佳实践),或正确处理异步函数的Promise:
import { test, expect } from '@playwright/test'; import { App } from '../pages/App'; test("Checking whether user can logged in", async({page}) => { const app = new App(page); await app.LoginPage.visit(); await app.LoginPage.login("Admin","admin123"); // 方案1:直接断言locator可见性(推荐) await expect(app.DashboardPage.txtDashboard).toBeVisible(); // 方案2:正确处理异步函数的Promise // await expect(app.DashboardPage.isDashboardTxtVisible()).resolves.toBeTruthy(); });
3. 优化登录方法(可选)
在登录时等待页面导航完成,确保页面完全加载后再执行断言:
async login(username: string, password: string){ await this.txtUsername.type(username); await this.txtPassword.type(password); // 并行执行点击登录和等待导航,确保页面跳转完成 await Promise.all([ this.page.waitForNavigation(), this.btnLogin.click() ]); }
内容的提问来源于stack exchange,提问作者AVI
相关产品推荐
相关产品推荐

