Select a radio button answer based on question with Playwright

58 views Asked by At

The Playwright docs show how to select a radio button answer based on the answer label. Is there a way to select it based on the question and answer? I'm trying to answer a series of yes/no questions. Here's the HTML structure of the survey site:

<!DOCTYPE html>
<html lang="en">
  <body>
    <legend>
      <h4> 
        <span>22.</span>
        <span>Do you have wifi?</span>
      </h4>
    </legend>
    <div class='question-body'>
      <div>
        <div class='answer'>
          <div class='radio-container'>
            <input type='radio' aria-labelledby="yes_radio">
            <label for="yes_radio">
              <span>Yes</span>
            </label>
          </div>
        </div>
        <div class='answer'>
          <div class='radio-container'>
            <input type='radio' aria-labelledby="no_radio">
            <label for="no_radio">
              <span>No</span>
            </label>
          </div>
        </div>
      </div>
    </div>
  </body>
</html>

The solutions I've found online involve targetting the ID, labelledby, or other inner HTML properties. I'm looking for a way to do this just by the text of the question and text of the answers, since the questions will change survey to survey.

1

There are 1 answers

0
ggorlen On

Based on your HTML alone, you can click the label by attribute:

const playwright = require("playwright"); // ^1.42.1

const html = `<Your HTML>`;

let browser;
(async () => {
  browser = await playwright.firefox.launch();
  const page = await browser.newPage();
  await page.setContent(html);
  await page.click('[aria-labelledby="yes_radio"]');

  // just to verify that it worked:
  const checked = await page
    .locator("[type='radio']")
    .evaluateAll(e => e.map(e => e.checked));
  console.log(checked); // => [true, false]
})()
  .catch(err => console.error(err))
  .finally(() => browser?.close());

But I suspect there's more going on, so I suggest sharing the actual page. The strange thing is, the radio buttons are disconnected since they don't share the same name, so you can check both Yes and No, which would be impossible to do in any quiz worth calling itself that.

Similarly, you can't click the label or span since there's no id association in the for= attribute, so that core functionality is also broken.

If the element were:

<input type='radio' id="yes_radio" aria-labelledby="yes_radio">

then you could click by something like label text:

await page.click('.question-body :text-is("Yes")');

Note that on some sites, Playwright's trusted click doesn't have an effect due to visibility, so you can use .click("...", {force: true}) or .evaluate(el => el.click()) instead.


If there are multiple questions on a page and you want to answer all of them, something like this might work:

// your choices; true = yes, false = no
const choices = [false, false, false, true];

await page
  .locator(".question-body")
  .evaluateAll((els, choices) => {
    els.forEach((el, i) => {
      const choice = choices[i] ? "yes" : "no";
      const label = `[aria-labelledby="${choice}_radio"]`;
      el.querySelector(label).click();
    });
  }, choices);