检查元素类是否包含使用剧作家的字符串



我试图获得第18天的元素,并检查它是否有禁用在其类。

<div class="react-datepicker__day react-datepicker__day--tue" aria-label="day-16" role="option">16</div>
<div class="react-datepicker__day react-datepicker__day--wed react-datepicker__day--today" aria-label="day-17" role="option">17</div>
<div class="react-datepicker__day react-datepicker__day--thu react-datepicker__day--disabled" aria-label="day-18" role="option">18</div>
这是我的代码,假设
this.xpath = 'xpath=.//*[contains(@class, "react-datepicker__day") and not (contains(@class, "outside-month")) and ./text()="18"]'
async isDateAvailable () {
const dayElt = await this.page.$(this.xpath)
console.log(dayElt.classList.contains('disabled'))) \this should return true

我似乎不能使它工作。错误提示TypeError:无法读取未定义属性"contains"。你能指出我哪里做错了吗?

看起来你可以直接写

await expect(page.locator('.selector-name')).toHaveClass(/target-class/)

/target-class/-斜杠是必需的,因为它是RegExp

对于一个调用检查几个类,我使用这个帮助器(这是因为api方式不适合我https://playwright.dev/docs/test-assertions#locator-assertions-to-have-class):

async function expectHaveClasses(locator: Locator, className: string) {
// get current classes of element
const attrClass = await locator.getAttribute('class')
const elementClasses: string[] = attrClass ? attrClass.split(' ') : []
const targetClasses: string[] = className.split(' ')
// Every class should be present in the current class list
const isValid = targetClasses.every(classItem => elementClasses.includes(classItem))
expect(isValid).toBeTruthy()
}

className中,你可以写几个用空格分隔的类:

const result = await expectHaveClasses(page.locator('.item'), 'class-a class-b')

您必须在浏览器中对其进行评估。$将返回一个ElementHandle,这是一个围绕浏览器DOM元素的包装,所以你必须使用例如evaluate然后对它。或者简单的$eval,它将查找元素,将其传递给回调,该回调将在浏览器的JavaScript引擎中执行。这意味着类似这样的东西可以工作:

// @ts-check
const playwright = require("playwright");
(async () => {
const browser = await playwright.chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();
await page.setContent(`
<div id="a1" class="foo"></div>
`)
console.log(
await page.$eval("#a1", el => el.classList.contains("foo1"))
)
await browser.close();
})();

关于OP的原始代码,一般不推荐使用XPath来选择剧作家中的元素。喜欢定位器。我可能会根据文本和角色进行选择,然后断言类存在。而且,类不是用户可见的,所以在那里可能也有更好的断言机会。

如果剧作家测试不在图片中,似乎是OP的情况,这个答案正确地提供了一个evaluate,尽管我会用定位器写它:

const isDisabled = await page.getByLabel("day-18")
.evaluate(el => el.classList.contains("disabled"));

将此线程视为断言元素类的规范,此答案很好地展示了如何使用regex匹配单个类,但也提倡在其expectHaveClasseshelper中使用反模式:

const isValid = targetClasses.every(classItem => elementClasses.includes(classItem))
expect(isValid).toBeTruthy()

问题是失败的错误信息将是不清楚的,并且可能难以调试。此外,如果元素在DOM中,则断言不会等待类更改为正确的类,而是会异步调整其类列表。

类列表通常没有那么长,所以我会单独列举每个类的名称:

const loc = page.locator("p")
await expect(loc).toHaveClass(/bfoob/);
await expect(loc).toHaveClass(/bbarb/);
await expect(loc).toHaveClass(/bbazb/);

这是一个很好的例子,WET和避免过早抽象可能比DRY更好,因为正确干燥代码的门槛相当高。

从技术上讲,查找也是可能的:

await expect(loc).toHaveClass(/(?=.*bab)(?=.*bbb)(?=.*bcb)/);

但是这是不可读的,所以如果你要这样做,那么一个帮助器可能是有意义的:

import {expect, test} from "@playwright/test"; // ^1.30.0
test.beforeEach(({page}) => page.setContent('<p class="a b c d"></p>'));
const hasWordsRegex = (...a) =>
new RegExp(a.map(e => `(?=.*\b${e}\b)`).join(""));
test("has classes 'a', 'b' and 'c'", async ({page}) => {
await expect(page.locator("p")).toHaveClass(hasWordsRegex("a", "b", "c"));
});

注意,这不是严格的,所以d类存在是可以的。这是常见的情况。

如果你对这个模式很认真,你可以把它放到一个自定义匹配器中:

import {expect, test} from "@playwright/test"; // ^1.30.0
test.beforeEach(({page}) => page.setContent('<p class="a b c d"></p>'));
const hasWordsRegex = (...a) =>
new RegExp(a.map(e => `(?=.*\b${e}\b)`).join(""));
expect.extend({
async toHaveAllClasses(received, ...classes) {
const className = await received.evaluate(el => el.className);
if (hasWordsRegex(...classes).test(className)) {
return {
message: () => "passed",
pass: true,
};
}
return {
message: () =>
`failed: element class '${className}' ` +
`did not match expected '${classes.join(" ")}'`,
pass: false,
};
},
});
test("has classes 'a', 'b' and 'c'", async ({page}) => {
await expect(page.locator("p")).toHaveAllClasses("a", "b", "c");
});
test("is missing at least one of 'a', 'b', 'c' or 'x'", async ({page}) => {
await expect(page.locator("p")).not.toHaveAllClasses("a", "b", "c", "x");
});

除了可读性之外,regex的另一个问题是忘记转义regex特殊字符。小心使用正则表达式!在上面的示例中,可能不清楚在底层使用了regex,从而导致令人困惑的失败。可以不使用regex重写:

expect.extend({
async toHaveAllClasses(received, ...classes) {
const [classList, className] =
await received.evaluate(el => [[...el.classList], el.className]);
const missing = classes.filter(e => !classList.includes(e));
if (missing.length) {
return {
message: () =>
`failed: element class '${className}' ` +
`did not contain '${missing.join(", ")}'`,
pass: false,
};
}
return {
message: () => "passed",
pass: true,
};
},
});

如果您希望在存在额外类时失败,您可以在相反的方向添加检查:

const extra = classList.filter(e => !classes.includes(e));
if (extra.length) {
return {
message: () =>
`failed: element class '${className}' ` +
`had extra class '${extra.join(", ")}' ` +
`that wasn't part of the expected class '${classes.join(" ")}'`,
pass: false,
};
}

这可以让你准确地检查多个类,以任何顺序。

(希望)这里的最后一个问题是evaluate不会等待类通过测试——它会立即检查一次,然后判断成功或失败。

waitForFunction,轮询或重试可以帮助创建等待,但希望这些小麻烦足以激发为什么使用一些toHaveClass调用可能是在撰写本文时的最佳解决方案。

最新更新