Puppeteer and ShadowDOM

If you are testing an app – be it React, Angular or Vue – and you are using an library that uses shadow DOM to create elements(say, Ionic), you’ll find that there are elements(within the shadow dom) that can’t be accessed using ‘waitForSelector‘ call. If you try making an waitFor or waitForSelector call, it will be a rejected promise.

I ran into this issue while testing an error message that used ion-toast – that component used shadow dom to display the message. I’m sure Ionic uses it in other things like popups and dialogs. So to test that you must be able to peirce the shadow DOM.

There are already suggestions in Puppeteer to create calls like shadow$ or shadow$$. But till that becomes live, you can fix this issue using the query-selector-shadow-dom library.

This library workes with other tools like…

  • WebdriverIO
  • Playwright
  • Puppeteer

Here is an example of it in action…

const puppeteer = require('puppeteer');
const {  QueryHandler } = require("query-selector-shadow-dom/plugins/puppeteer");
(async () => {
    try {
        await puppeteer.__experimental_registerCustomQueryHandler('shadow', QueryHandler);
        const browser = await puppeteer.launch({
            headless: false,
            devtools: true
        const page = await browser.newPage()
        await page.goto('')

        // ensure btn exists and return it
        await page.waitForSelector("shadow/.btn-in-shadow-dom");
        const btn = await page.("shadow/.btn-in-shadow-dom");
        await btn.click();

        // check btn was clicked (this page expected btn to change text of output)
        const outputSpan = await page.("shadow/.output");
        const text = await page.evaluate((output) => output.innerText, outputSpan);
        // prints the text from the output

        await browser.close()
    } catch (e) {


Here are a few examples of how you can use query-selector-shadow-dom with Puppeteer

Author: admin

Leave a Reply

Your email address will not be published. Required fields are marked *