playwright
playwright copied to clipboard
[Feature]: have toHaveAttribute(name, value) behave like toHaveText(value) when parameter is an array
🚀 Feature Request
The assertion expect(locator).toHaveText(value) accepts arrays as parameter and will check that the locator matches n elements and check that the nth element has the nth text value.
A similar behavior for other assertions would be very useful. In particular I'm in need of said behavior for the toHaveAttribute assertion. See example below.
Example
Given this document
<div >
<div class="theClass" someAttr="a"></div>
<div class="theClass" someAttr="b"></div>
<div class="theClass" someAttr="c"></div>
</div>
I'd like this code to succeed
await expect(page.locator(".theClass")).toHaveAttribute("someAttr", ["a", "b", "c"])
and this code to fail
// unexpected "b" in DOM, "d" not found, "a" found in wrong position
await expect(page.locator(".theClass")).toHaveAttribute("someAttr", ["c", "d", "a"])
Motivation
I can write a function that does what I described, using a combination of expect.poll() and locator.getAttribute but I think this is a general enough case that deserves support out of the box. Motivation is basically ease of use.
Addendum: at the moment I'm doing this, and it works, but I feel it's a bit clunky
await expect.poll(async () => {
const headers = await page.getByRole('columnheader').all();
const headersIds = await Promise.all(headers.map(l => l.getAttribute("col-id")));
return headersIds;
}).toEqual(['FirstName', 'LastName', 'Age']);
Another thing that should work is this:
await expect.soft(page.locator(".theClass").nth(0)).toHaveAttribute("someAttr", "a")
await expect.soft(page.locator(".theClass").nth(1)).toHaveAttribute("someAttr", "b")
await expect.soft(page.locator(".theClass").nth(2)).toHaveAttribute("someAttr", "c")
await expect(page.locator(".theClass")).toHaveCount(3)
Note the use of soft assertions, so that you'll see the values of all elements if one of them has the wrong attribute.
@Oscaruzzo would an array of dictionaries be as good(i.e. instead of : await expect(page.locator(".theClass")).toHaveAttribute("someAttr", ["a", "b", "c"]) we'll have: await expect(page.locator(".theClass")).toHaveAttribute({"someAttr":"a"},{ "someAttr":"b"},{"someAttr": "c"}])?
@Oscaruzzo would an array of dictionaries be as good(i.e. instead of :
await expect(page.locator(".theClass")).toHaveAttribute("someAttr", ["a", "b", "c"])we'll have:await expect(page.locator(".theClass")).toHaveAttribute([{"someAttr":"a"},{ "someAttr":"b"},{"someAttr": "c"}])?
I have to say I'm not a fan. Looks quite verbose and I'm not sure about the advantages, unless you have to assert something about multiple different attributes.
Why was this issue closed?
Thank you for your involvement. This issue was closed due to limited engagement (upvotes/activity), lack of recent activity, and insufficient actionability. To maintain a manageable database, we prioritize issues based on these factors.
If you disagree with this closure, please open a new issue and reference this one. More support or clarity on its necessity may prompt a review. Your understanding and cooperation are appreciated.
For posterity: ATM I'm doing this. It could probably be improved (I'm not happy about the way errors are reported, but I don't know how to improve it)
export const expect = baseExpect.extend({
async toHaveAttributeArray(locator: Locator, attribute: string, expected: string[], options?: { timeout?: number }) {
const assertionName = "toHaveAttributeArray";
let pass: boolean;
let msg: string;
try {
await baseExpect.poll(async () => {
const locators = await locator.all();
const attributeValues = await Promise.all(locators.map(l => l.getAttribute(attribute)));
return attributeValues;
}, options).toEqual(expected);
pass = true;
} catch (e: unknown) {
pass = false;
if (e instanceof Error) {
msg = e.message;
}
}
const message = () => msg;
return {
message,
pass,
name: assertionName,
expected,
};
},
});