I am building an automated integration testing tool in Node.js using Puppeteer to interact with a third-party dashboard. The platform relies heavily on nested structures, and inside those iframes, the components are encapsulated within native open Shadow DOMs.
The problem is that I need to query and click a specific action button based solely on its visible text content because its class names and IDs are dynamically randomized on every session layout render (standard utility-first framework / Tailwind mess).
According to the modern Puppeteer docs, the native text search engine (::-p-text) or ARIA selectors should handle deep queries. However, when trying to chain them across an iframe boundary and into a shadow root, the selector engine continuously returns null or throws a timeout exception.
Here is the minimal reproducible version of my script layout:
JavaScript
const puppeteer = require('puppeteer');
async function run() {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
// URL anonymized for client privacy, but it's a standard internal corporate SPA dashboard
await page.goto('https://internal-client-dashboard.local/analytics', { waitUntil: 'domcontentloaded' });
try {
// 1. Locate and get the iframe handle
const iframeElement = await page.waitForSelector('iframe#sub-dashboard');
const frame = await iframeElement.contentFrame();
// 2. Attempt to penetrate the Shadow DOM inside that frame using text selectors
// This fails consistently with a TimeoutError
const targetButton = await frame.waitForSelector('div >>> ::-p-text(Confirm Settings)');
await targetButton.click();
console.log('Button clicked successfully');
} catch (err) {
console.error('Execution failed:', err.message);
} finally {
await browser.close();
}
}
run();
What I have tried so far:
Chaining the deep combinator directly like
iframe#sub-dashboard >>> div >>> ::-p-text(...)from the root page context, but Puppeteer doesn't seem to resolve cross-frame boundaries automatically this way.Executing an internal
page.evaluate()running standard nativeshadowRoot.querySelector(). This works but it completely bypasses Puppeteer's native driver-level abstraction andwaitForSelectorauto-waiting benefits, making the execution extremely flaky.
What is the standard syntax in modern Puppeteer to pierce through an iframe context down into a nested Shadow DOM component using semantic text engines? I want to avoid resorting to messy un-awaited query selector loops inside an execution context evaluation if possible. Any ideas?