<h3 dir="auto">System info</h3>
<ul dir="auto">
<li>Playwright Version: 1.31.2</li>
<li>Operating System: Ubuntu 22.04</li>
<li>Browser: WebKit</li>
</ul>
<h3 dir="auto">Source code</h3>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="import { test } from '@playwright/test';

test('getByRole behaviour', async ({ page }) =&gt; {
  await page.setContent(`
    &lt;html&gt;
      &lt;body&gt;
        &lt;my-dialog&gt;
          &lt;button&gt;dont click me&lt;/button&gt;
        &lt;/my-dialog&gt;
        &lt;button&gt;click me instead&lt;/button&gt;
        &lt;script type=&quot;module&quot;&gt;
        class MyDialog extends HTMLElement {
          connectedCallback() {
            this.attachShadow({mode: 'open'});
            this.shadowRoot.innerHTML = 'we dont add a slot here so the light dom is not visible'
          }
        }
        customElements.define('my-dialog', MyDialog);
        &lt;/script&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  `);

  // chrome finds 1 element, because only one is actually available to the user
  // webkit finds 2 elements, so strict validation fails only in webkit
  await page.getByRole('button').click(); 

  // finds 2 elements, because there are two elements in the dom
  // strict validation fails in all browsers, as expected
  // await page.locator('button').click();  
});"><pre class="notranslate"><span class="pl-k">import</span> <span class="pl-kos">{</span> <span class="pl-s1">test</span> <span class="pl-kos">}</span> <span class="pl-k">from</span> <span class="pl-s">'@playwright/test'</span><span class="pl-kos">;</span>

<span class="pl-en">test</span><span class="pl-kos">(</span><span class="pl-s">'getByRole behaviour'</span><span class="pl-kos">,</span> <span class="pl-k">async</span> <span class="pl-kos">(</span><span class="pl-kos">{</span> page <span class="pl-kos">}</span><span class="pl-kos">)</span> <span class="pl-c1">=&gt;</span> <span class="pl-kos">{</span>
  <span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-en">setContent</span><span class="pl-kos">(</span><span class="pl-s">`</span>
<span class="pl-s">    &lt;html&gt;</span>
<span class="pl-s">      &lt;body&gt;</span>
<span class="pl-s">        &lt;my-dialog&gt;</span>
<span class="pl-s">          &lt;button&gt;dont click me&lt;/button&gt;</span>
<span class="pl-s">        &lt;/my-dialog&gt;</span>
<span class="pl-s">        &lt;button&gt;click me instead&lt;/button&gt;</span>
<span class="pl-s">        &lt;script type="module"&gt;</span>
<span class="pl-s">        class MyDialog extends HTMLElement {</span>
<span class="pl-s">          connectedCallback() {</span>
<span class="pl-s">            this.attachShadow({mode: 'open'});</span>
<span class="pl-s">            this.shadowRoot.innerHTML = 'we dont add a slot here so the light dom is not visible'</span>
<span class="pl-s">          }</span>
<span class="pl-s">        }</span>
<span class="pl-s">        customElements.define('my-dialog', MyDialog);</span>
<span class="pl-s">        &lt;/script&gt;</span>
<span class="pl-s">      &lt;/body&gt;</span>
<span class="pl-s">    &lt;/html&gt;</span>
<span class="pl-s">  `</span><span class="pl-kos">)</span><span class="pl-kos">;</span>

  <span class="pl-c">// chrome finds 1 element, because only one is actually available to the user</span>
  <span class="pl-c">// webkit finds 2 elements, so strict validation fails only in webkit</span>
  <span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-en">getByRole</span><span class="pl-kos">(</span><span class="pl-s">'button'</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">click</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">;</span> 

  <span class="pl-c">// finds 2 elements, because there are two elements in the dom</span>
  <span class="pl-c">// strict validation fails in all browsers, as expected</span>
  <span class="pl-c">// await page.locator('button').click();  </span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div>
<p dir="auto">Config file is the default npm init playwright@latest, strict mode enabled.</p>
<p dir="auto"><strong>Steps</strong></p>
<ul dir="auto">
<li>Run the test in chrome and firefox, it works</li>
<li>Run the test in webkit and the click fails strict validation</li>
</ul>
<p dir="auto"><strong>Expected</strong><br>
All browsers behave the same, ideally the test should pass for all browsers.</p>
<p dir="auto"><strong>Actual</strong><br>
WebKit seems to keep light-dom elements in the accessibility tree, even if they have no slot in the corresponding shadow-root. This causes getByRole to find more than one element to click on the page.</p>
<p dir="auto">I am really not sure if this is a playwright issue, a playwright-webkit issues, or a webkit issue. I'd be perfectly happy with filing the issue somewhere else if we find this to be the wrong place.</p>
<p dir="auto">Edit: Whoops, my copy pasta had an accidential display: none on the button, I removed that with an edit</p>
<h3 dir="auto">System info</h3>
<ul dir="auto">
<li>Playwright Version: [v1.XX]</li>
<li>Operating System: [All, Windows 11, Ubuntu 20, macOS 13.2, etc.]</li>
<li>Browser: [All, Chromium, Firefox, WebKit]</li>
<li>Other info:</li>
</ul>
<h3 dir="auto">Source code</h3>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox"> I provided exact source code that allows reproducing the issue locally.</li>
</ul>




<p dir="auto"><strong>Link to the GitHub repository with the repro</strong></p>
<p dir="auto">[https://github.com/your_profile/playwright_issue_title]</p>
<p dir="auto">or</p>
<p dir="auto"><strong>Config file</strong></p>
<div class="highlight highlight-source-js notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'], },
    },
});"><pre class="notranslate"><span class="pl-c">// playwright.config.ts</span>
<span class="pl-k">import</span> <span class="pl-kos">{</span> <span class="pl-s1">defineConfig</span><span class="pl-kos">,</span> <span class="pl-s1">devices</span> <span class="pl-kos">}</span> <span class="pl-k">from</span> <span class="pl-s">'@playwright/test'</span><span class="pl-kos">;</span>

<span class="pl-k">export</span> <span class="pl-k">default</span> <span class="pl-en">defineConfig</span><span class="pl-kos">(</span><span class="pl-kos">{</span>
  <span class="pl-c1">projects</span>: <span class="pl-kos">[</span>
    <span class="pl-kos">{</span>
      <span class="pl-c1">name</span>: <span class="pl-s">'chromium'</span><span class="pl-kos">,</span>
      <span class="pl-c1">use</span>: <span class="pl-kos">{</span> ...<span class="pl-s1">devices</span><span class="pl-kos">[</span><span class="pl-s">'Desktop Chrome'</span><span class="pl-kos">]</span><span class="pl-kos">,</span> <span class="pl-kos">}</span><span class="pl-kos">,</span>
    <span class="pl-kos">}</span><span class="pl-kos">,</span><span class="pl-kos"></span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div>
<p dir="auto"><strong>Test file (self-contained)</strong></p>
<div class="highlight highlight-source-js notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="it('should check the box using setChecked', async ({ page }) =&gt; {
  await page.setContent(`&lt;input id='checkbox' type='checkbox'&gt;&lt;/input&gt;`);
  await page.getByRole('checkbox').check();
  await expect(page.getByRole('checkbox')).toBeChecked();
});"><pre class="notranslate"><span class="pl-en">it</span><span class="pl-kos">(</span><span class="pl-s">'should check the box using setChecked'</span><span class="pl-kos">,</span> <span class="pl-k">async</span> <span class="pl-kos">(</span><span class="pl-kos">{</span> page <span class="pl-kos">}</span><span class="pl-kos">)</span> <span class="pl-c1">=&gt;</span> <span class="pl-kos">{</span>
  <span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-en">setContent</span><span class="pl-kos">(</span><span class="pl-s">`&lt;input id='checkbox' type='checkbox'&gt;&lt;/input&gt;`</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
  <span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-en">getByRole</span><span class="pl-kos">(</span><span class="pl-s">'checkbox'</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">check</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
  <span class="pl-k">await</span> <span class="pl-en">expect</span><span class="pl-kos">(</span><span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-en">getByRole</span><span class="pl-kos">(</span><span class="pl-s">'checkbox'</span><span class="pl-kos">)</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">toBeChecked</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div>
<p dir="auto"><strong>Steps</strong></p>
<ul dir="auto">
<li>[Run the test]</li>
<li>[...]</li>
</ul>
<p dir="auto"><strong>Expected</strong></p>
<p dir="auto">[Describe expected behavior]</p>
<p dir="auto"><strong>Actual</strong></p>
<p dir="auto">[Describe actual behavior]</p>