You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);
    }
}

问题原因

  1. DashboardPage元素定位错误:lnkLeave和lnkPIM错误复用了登录页的Username/Password占位符定位器,这些元素在Dashboard页面不存在,导致Playwright定位上下文混乱,触发"Target closed"错误。
  2. 断言逻辑处理不当:测试中直接将异步函数isDashboardTxtVisible()传入expect,未正确处理Promise,无法捕获函数内部的错误,也无法正确验证元素状态。
  3. 冗余的等待逻辑: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 23:04:50