microsoft / playwright

Playwright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.
https://playwright.dev
Apache License 2.0
65.6k stars 3.57k forks source link

Visible radio button cannot be clicked ("element is outside of the viewport") #3688

Closed ghost closed 4 years ago

ghost commented 4 years ago

Context:

Code Snippet

The objective is to click/select the radio button with the label "JA" via its ID "existing-customer-input-0":

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch({headless: false}, {slowMo: 50});
  // ...
  await page.click('#existing-customer-input-0');
})();

Describe the bug

The problematic section of the tested web page consists of a radio button group with two buttons: image The corresponding HTML source code:

<div class="c-button-group">
  <label class="c-button-group__item" for="existing-customer-input-0">
    <input class="c-button-group__input" type="radio" id="existing-customer-input-0" name="existingCustomer" 
    value="yes">
    <span class="c-button-group__label">ja</span>
  </label>
  <label class="c-button-group__item" for="existing-customer-input-1">
    <input class="c-button-group__input" type="radio" id="existing-customer-input-1" name="existingCustomer" 
    value="no">
    <span class="c-button-group__label">nein</span>
  </label>
</div>

Expected behavior: Button "JA" is found and clicked/selected.

Actual behavior: Playwright can't successfully select the element referenced by the above ID and throws the following error message:

TimeoutError: page.click: Timeout 30000ms exceeded. =========================== logs =========================== [api] waiting for selector "#existing-customer-input-0" [api] selector resolved to visible <input value="yes" type="radio" name="existingCustomer"…/> [api] attempting click action [api] waiting for element to be visible, enabled and not moving [api] element is visible, enabled and does not move [api] scrolling into view if needed [api] done scrolling [api] element is outside of the viewport [api] retrying click action [...] It keeps retrying with the same messages as above until the timeout kicks in.

Regarding "element is outside of the viewport": Not sure what this exactly implies but the element with the ID "existing-customer-input-0" is clearly visible and not overshadowed/hidden during a headful test.

arjunattam commented 4 years ago

Thanks @klctrl. Is this a public website? It's hard to tell for sure without looking at the css of the html.

Looking at the screenshot, my guess is that the radio button circle is hidden with css and playwright is waiting for the circle to be visible. Couple of alternatives you could try:

  1. Force the click to bypass these checks page.click('#existing-customer-input-0', { force: true} ) (this might have other side-effects)
  2. Click the parent element with page.click('[for=existing-customer-input-0]')

If you can share a link to the page or a repro case with css, we could find a better solution.

ghost commented 4 years ago

Thanks for your feedback, @arjun27.

I have sent the URL to you directly.

Re 1.: This still leads to the same error, however this time there is no automatic retry.

Error: page.click: Element is outside of the viewport =========================== logs =========================== [api] waiting for selector "#existing-customer-input-0" [api] selector resolved to visible <input value="yes" type="radio" name="existingCustomer"…/> [api] attempting click action [api] scrolling into view if needed [api] done scrolling

Note: use DEBUG=pw:api environment variable and rerun to capture Playwright logs. at ElementHandle._retryPointerAction (node_modules/playwright/lib/dom.js:252:27) at /[...]/node_modules/playwright/lib/frames.js:674:32 at ProgressController.run (node_modules/playwright/lib/progress.js:75:28) at Frame.click (node_modules/playwright/lib/frames.js:686:9) -- ASYNC -- at Frame.click (node_modules/playwright/lib/helper.js:79:23) at /[...]/node_modules/playwright/lib/page.js:377:61 at Page._attributeToPage (node_modules/playwright/lib/page.js:370:20) at Page.click (node_modules/playwright/lib/page.js:377:21) at Page.click (node_modules/playwright/lib/helper.js:80:31) at /[...]/test/e2e-dcc.ts:56:24 at step (test/e2e-dcc.ts:33:23) at Object.next (test/e2e-dcc.ts:14:53) at /[...]/test/e2e-dcc.ts:8:71 at new Promise () at __awaiter (test/e2e-dcc.ts:4:12) at Context. (test/e2e-dcc.ts:55:47) at processImmediate (internal/timers.js:458:21)

Re 2.: This works.

Interestingly, it also works when using the following text-based selector:

await page.click('text="ja"');

Normally, I always try to use text-based selectors to avoid having to use details from a page's source code (also, strictly speaking, for this text-based selector I had to look at the source code because using "JA" (capital letters, as it gets displayed on the page) won't lead to the button getting selected but also won't produce an error), but in this case the text-based selector is not ideal because there's another button with the same text further below on the page. Therefore, I want to ensure that definitely the right button gets clicked at this point.

arjunattam commented 4 years ago

Thanks for sharing the link on email. I noticed that the radio button has the following styles and is therefore not located on the page.

left: -9999px;
position: absolute;

I understand how using the text-based selector feels unreliable because of the other element on the page. Do you think we could combine the text selector with another text selector localize the search (text=Parent >> text=Child) into a part of the page? It might also be worth to get your thoughts on #2370

arjunattam commented 4 years ago

I'll close the issue for now since this isn't a bug. We can continue the discussion to find an ideal selector to choose.

ghost commented 4 years ago

Combining several text selectors to localize the search is a nice suggestion that I wasn't aware of. However, in this particular case and since that kind of text selector combination does indeed only work in case of parent >> child scenarios, I haven't found a way to localize it given the following html code:

<fieldset class="o-fieldset u-mb-large">
<div class="o-fieldset__row">
<div class=" o-layout">
<div class="o-layout__item u-1/1">
<label><span>Wir sind gesetzlich dazu verpflichtet, Ihre Identität zu überprüfen. Die Legitimation können Sie direkt nach dem Abschluss bequem online durchführen. Falls Sie in den letzten drei Jahren Kunde waren, ist keine Legitimation erforderlich.&nbsp;*</span></label>
<div class="c-button-group">
<label class="c-button-group__item" for="existing-customer-input-0">
<input class="c-button-group__input" type="radio" id="existing-customer-input-0" name="existingCustomer" value="yes">
<span class="c-button-group__label">ja</span></label>
<label class="c-button-group__item" for="existing-customer-input-1">
<input class="c-button-group__input" type="radio" id="existing-customer-input-1" name="existingCustomer" value="no" checked="">
<span class="c-button-group__label">nein</span></label>
</div></div></div></div>
</fieldset>

If there were another operator than >> that would not only look at the parent's child but also check the same level in the tree as the parent is in (my wording may be wrong, I'm not deep into HTML, DOM, etc.) -- or maybe an option to that operator -- then something like this could have worked (which I actually tried out in the hope that the string after the operator doesn't necessarily have to be a child node): await page.click('text=Wir sind gesetzlich dazu verpflichtet >> text="ja"');

Such a feature would IMHO touch the broader issue of proximity/relative selectors (see #2877 and #3667). That, in my view, would be the ultimate goal: having the option to write your tests w/o having to look at the HTML source code and even w/o having to know much about HTML, CSS, etc.

2370 sounds like an interesting enhancement but for my usual use cases I prefer using simple APIs that can be easily understood after a short look at them, which makes both the creation and the maintenance of test code easier and keeps efforts low. That's why I like text-based selectors.