← Back to libraryQuestion 180 of 273
🎭PlaywrightIntermediate

Handling iframes & Shadow DOM

📌 Definition:

Playwright reaches into iframes via frameLocator(), and pierces open shadow DOM automatically with its normal locators. This removes two classic Selenium pain points: manual switchTo().frame() context switching and inability to see into web components.

📖 Detailed Explanation:

In Selenium you must switchTo().frame(...) before interacting with iframe content and switch back afterward — easy to forget, and a common source of NoSuchElementException. Playwright uses page.frameLocator('iframe#checkout').getByRole('button', ...), scoping into the frame without changing global context, and you can chain further. For shadow DOM, Playwright's engine pierces open shadow roots transparently: getByRole('button', { name: 'Add' }) finds a button inside a web component's open shadow tree without special syntax. Closed shadow roots remain inaccessible (by design, as they are to users). This makes testing embedded widgets (payment iframes, third-party chat, design-system web components) markedly simpler.

🔑 Key Points:
  • iframes: page.frameLocator('selector').getByRole(...) — no global context switching
  • frameLocator is chainable for nested frames
  • Open shadow DOM is pierced automatically by normal locators — no special API
  • Closed shadow roots are intentionally inaccessible (same as for real users)
  • Eliminates Selenium's switchTo().frame()/defaultContent() bookkeeping
🌍 Real-World Example:

A Stripe-style payment form lives in an iframe. In Selenium the team constantly forgot to switch back out of the frame, causing failures in the next step. In Playwright, page.frameLocator('iframe[name="card"]').getByLabel('Card number').fill('4242…') interacts inside the frame while the rest of the test continues against the main page normally.

📎 Code Example:
// Interact inside an iframe — no switchTo needed
const card = page.frameLocator('iframe[name="card"]');
await card.getByLabel('Card number').fill('4242424242424242');
await card.getByLabel('Expiry').fill('12/34');

// Nested frames: chain frameLocator
await page
  .frameLocator('#outer')
  .frameLocator('#inner')
  .getByRole('button', { name: 'Confirm' })
  .click();

// Open shadow DOM: normal locators just work
await page.getByRole('button', { name: 'Add to cart' }).click();
🎯 Scenario-Based Interview Question:

Scenario: A Selenium suite is riddled with switchTo().frame() / switchTo().defaultContent() calls and frequently fails because someone forgot to switch back. A payment widget also uses web components the team 'can't locate'. How does Playwright address both?