Description
🚀 Feature Request
A new function on the Page and Locator classes (and wherever else these things appear), similar to getByText
or getByRole
. getByValue
would find input elements with the given value and return a Locator. I'm not sure if input
s are the only thing this would apply to - I guess whichever types of elements the LocatorAssertion toHaveValue
looks at.
Example
My current use case is a form with multiple rows, where the user can set area, startTime and endTime using the inputs which trigger a dropdown. Each row is identical, save for the values currently selected.
If I want my test to do something like form.changeArea('area 1', 'area 2')
, I need to locate the right <input> to click on it.
I could just get the nth(1)
row and get the area input that way, but selecting by area name makes for a more readable test, and I imagine situations exist where you know the value but not its place in a list.
This is what the input element looks like in the DOM - I assume the reason something like locator('input[value="whatever"]')
doesn't work is it doesn't actually have a value
property, just a computed Value attribute.
<input data-v-3f542e30="" class="co-input__inner" type="text" autocomplete="off" tabindex="0" aria-label="" inputmode="" placeholder="Select">
I've made this function which seems to do the job for now:
// Example: this.getByValue('Bob Bobson')
// Example: this.getByValue('Bob', { exact: false })
// Example: this.getByValue('Bob Bobson', { parent: this.addSchedulePopover() })
@step()
async getByValue(value: string, { parent = this.page, exact = false }: {
parent?: Locator | Page;
exact?: boolean;
} = {}) {
let matchingInput;
const fnExact = (element, name) => element?.value === name,
fnIncludes = (element, name) => element?.value?.includes(name);
// since we can't use inbuilt locator functions for this, expect.toPass gives us some auto-retrying capability
// in case the page isn't done loading when this is called
await expect(
async () => {
matchingInput = null;
for (const input of await parent.locator('input').all()) {
const match = await input.evaluate(
exact ? fnExact : fnIncludes,
value
);
if (match) {
matchingInput = input;
break;
}
}
if (!matchingInput) throw Error(`Could not find input with value '${value}'`);
},
{ message: `find input with value '${value}'` }
).toPass({ timeout: 20_000 });
return matchingInput;
}
It seems to do the job but it's not ideal, because any locator function that uses it now has to return a Promise instead of a Locator, so you have to go up the chain and make everything async.
(I'm now realising I probably could've used inputValue()
to find the element instead of evaluate()
, but that still leaves me with an async function instead of a nice synchronous function that returns a Locator.)
Motivation
This isn't the first time I've run across a situation where getByValue would have helped, I've just always found a less-ideal workaround and never thought to log a request. I could just be crazy or a fool but it seems like 'find element by value' would be a common operation like 'find element by text' is; I was surprised I couldn't find an example of someone asking for it.
I think it would make Playwright better by providing a simple method to perform what I think is a common operation.