Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature]: getByValue #34364

Open
bdcarr opened this issue Jan 17, 2025 · 1 comment
Open

[Feature]: getByValue #34364

bdcarr opened this issue Jan 17, 2025 · 1 comment

Comments

@bdcarr
Copy link

bdcarr commented Jan 17, 2025

🚀 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 inputs 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.

Image

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.

@yury-s
Copy link
Member

yury-s commented Jan 17, 2025

Please tell us more about your use case. How do you select the input field where you enter "area 1" when all rows in the table are empty? Is each row comprised of one input field or multiple, maybe you can share a DOM snippet or trace file with a DOM snapshot?

There have been similar requests in the past #18970, #14679, #3228, but they didn't gather enough upvotes. Also, maybe in your case table selectors would solve the problem, if they were implemented?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants