我无法发布代码,但我编写了这个 HTML 文件来复制该场景(https://coral-nanci-88.tiiny.site/):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<style>
body {
margin: 0;
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}
.main {
display: flex;
}
.sidebar {
width: 20vw;
border-right: 2px solid black;
height: 100vh;
font-size: 1.2rem;
}
.content {
flex-grow: 1;
display: flex;
align-items: center;
justify-content: center;
font-size: 4rem;
font-weight: bold;
}
</style>
<body>
<div class="main">
<div class="sidebar">
<ul>
<li class="side-item_lev1">
<div class="lev1-header">Lev1 item - 1</div>
<div class="lev1-body">
<ul>
<li class="side-item_lev2">
<div class="lev1-header">Lev2 item</div>
</li>
</ul>
</div>
</li>
<li class="side-item_lev1">
<div class="lev1-header">Lev1 item - 2</div>
<div class="lev1-body">
<ul>
<li class="side-item_lev2">
<div class="lev1-header">Lev2 item</div>
</li>
</ul>
</div>
</li>
</ul>
</div>
<div class="content">
<p class="content-desc">CONTENT</p>
</div>
</div>
</body>
</html>
如果您运行此测试(我使用带有 Live Server 扩展的 vscode):
test('test-sidebar', async ({ page }) => {
await page.goto("http://127.0.0.1:5500/index.html")
const site = new Site(page)
await site.sidebar.clickLevOneItem("Lev1 item - 1")
})
使用这些类:
class Site {
private readonly page: Page
public readonly sidebar: SideBar
constructor(page: Page) {
this.page = page
this.sidebar = new SideBar(this.page.locator(".sidebar"))
}
}
class SideBar {
public readonly baseNode: Locator
constructor(baseNode: Locator) {
this.baseNode = baseNode;
}
public async clickLevOneItem(name: string) {
await this.baseNode.locator(".side-item_lev1")
.filter({ has: this.baseNode.locator(".lev1-header", { hasText: name }) })
.click()
}
}
就像filter()方法不起作用.. 但是,如果您将整个页面传递到 SideBar:
class Site {
private readonly page: Page
public readonly sidebar: SideBar
constructor(page: Page) {
this.page = page
this.sidebar = new SideBar(page)
}
}
class SideBar {
public readonly baseNode: Page
/**
* Create the common page object
* @param {Locator} baseNode -
*/
constructor(baseNode: Page) {
this.baseNode = baseNode;
}
...
}
过滤器起作用了,找到 DOM 对象然后单击它。 但是为什么如果我使用定位器缩小类范围,而不传递整个页面,它就会停止工作。我不明白。
一旦有了可工作的代码,POM 就非常有用,但是额外的抽象层会稍微混淆基本问题。我认为如果删除 POM 并隔离单个定位器操作,然后在 POM 工作后重新添加它,会更容易看到发生了什么:
import {expect, test} from "@playwright/test"; // ^1.39.0
const html = `<!DOCTYPE html><html><body>
<div class="sidebar">
<ul>
<li class="side-item_lev1">
<div class="lev1-header">Lev1 item - 1</div>
<div class="lev1-body">
<ul>
<li class="side-item_lev2">
<div class="lev0-header">Lev2 item</div>
</li>
</ul>
</div>
</li>
<li class="side-item_lev1">
<div class="lev1-header">Lev1 item - 2</div>
<div class="lev1-body">
<ul>
<li class="side-item_lev2">
<div class="lev1-header">Lev2 item</div>
</li>
</ul>
</div>
</li>
</ul>
</div>
</body></html>`;
test("test-sidebar", async ({page}) => {
await page.setContent(html);
const baseNode = page.locator(".sidebar");
await baseNode
.locator(".side-item_lev1")
.filter({has: baseNode.locator(".lev1-header", {hasText: "Lev1 item - 1"})})
.click();
});
问题归结为是否可以像上面那样以嵌套方式使用
baseNode
。
显然,这是不可能的,因为它没有在任何地方的文档中显示,它说:
过滤定位器必须相对于原始定位器,并且从原始定位器匹配开始查询,而不是从文档根开始。
...然后继续仅显示在
page.
内部使用 has:
的示例。
一种选择是在嵌套定位器中使用
page
:
.filter({has: page.locator(".lev1-header")})
// ^^^^
或者完全跳过
.filter()
并使用纯定位器,这似乎不那么令人困惑:
await page.locator(".sidebar")
.locator(".side-item_lev1")
.locator(".lev1-header")
.getByText("Lev1 item - 1")
.click();