skip to Main Content

I have a form with many elements of the following pattern (ng-star-inserted et al. removed):

<ui-input label="First Name">
    <div>First Name</div>
    <div>
        <input type="text" placeholder="" autocomplete="off">
    </div>
</ui-input>

I would like Playwright to type text into the corresponding box on the form. As I understand it, the Playwright school of thought is to find the inner <input> element and await input.fill("Humbleton").

I’ve tried:

page.getByRole('textbox', { name: 'First Name' });
page.getByText('First Name').locator('input');
page.getByLabel('First Name').locator('input');
page.getByText('First Name').getByRole('textbox');
page.getByLabel('First Name').getByRole('textbox');

of which none work.

The go-to workaround is to insert a secret, unique-value attribute into the HTML and use the resulting unique XPath. However, I consider this to be cheating and against the spirit of e2e testing, clean code, propriety, and Playwright’s philosophy. Is there a Playwrightesque solution?

Finally I used page.keyboard to fill out the form using page.keyboard.press('Tab') to navigate from one field to another, but I have a hunch that this is not as robust and user-oriented as can be.

I feel there must be some overlooked elegant, expressive way to tell Playwright to just fill out the damn ‘First Name’ box.

2

Answers


  1. Try using

    page.locator('//ui-input[@label="First Name"]/descendant::input')

    as

    page.getByLabel('First Name').getByRole('textbox'); is not working.

    If this is also not working, Can you check if there is no iframe at the top level of this form?

    Login or Signup to reply.
  2. For your getByLabel to work, you’d need to use aria-label:

    <ui-input aria-label="First Name">
    

    Other of your locators don’t work because the input isn’t actually a child of the <div>First Name</div> element, which is what .getByText would return.

    Also, your <div>First Name</div> doesn’t appear to be acting as a clickable label. I’d expect modern, accessible HTML to be something more along the lines of:

    <ui-input aria-label="First Name">
      <div>
        <label for="first-name">First Name</label>
      </div>
      <div>
        <input
          id="first-name"
          placeholder="Enter your first name"
          autocomplete="off"
          aria-labelledby="first-name"
        >
      </div>
    </ui-input>
    

    If you can make your markup more like this, selection becomes straightforward.

    But assuming it’s out of your control, you can use CSS selectors to find the input by the label attribute, or use .filter() to select a ui-input with particular text.

    import {expect, test} from "@playwright/test"; // ^1.46.1
    
    const html = `<!DOCTYPE html><html><body>
    <ui-input label="First Name">
      <div>First Name</div>
      <div>
        <input placeholder="" autocomplete="off">
      </div>
    </ui-input>
    </body></html>`;
    
    test("Input can be located by First Name", async ({page}) => {
      await page.setContent(html);
      await page.locator('[label="First Name"] input').waitFor()
    
      // or, more "Playwright"-y:
      await page
        .locator("ui-input")
        .filter({hasText: "First Name"})
        .getByRole("textbox").waitFor();
    });
    

    Note that these may not work or even be best practice, depending on your full HTML context, which wasn’t shown.

    And yeah, don’t use XPath under almost any circumstances.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search