Skip to contentPlayForge

How do I work with an iframe in Playwright?

Written by Jatin Thacker, Automation Architect · Updated

Create a frame locator for the iframe with page.frameLocator('selector'), then locate elements through it as you would on the page. Locators that start from page only search the main document, so they never see what is inside an iframe.

This answer is one piece of Chapter 25: Frames, Tabs, Files and Login State.

  • Working with Iframes & Shadow DOM
  • Multiple Tabs, Windows & Popups
  • File Uploads, Downloads & Screenshots
  • Authentication & Storage State
  • Practice and interview questions: Advanced Playwright Techniques

An iframe is a separate document embedded in the page. Its buttons and headings are not part of the main page's DOM, so a locator that starts from page cannot reach them.

The embeds page loads an invoice preview in an iframe with data-testid="invoice-iframe". The test first proves the boundary: from the main page, getByRole('button', { name: 'Mark as paid' }) matches zero elements, although the button is plainly on screen.

page.frameLocator('[data-testid="invoice-iframe"]') enters the frame. Everything chained after it is resolved inside the frame's document, with the same getByRole, getByTestId and filter you use anywhere else. The test reads the invoice heading, clicks Mark as paid, and asserts the status changes to Paid.

If you already have a locator for the <iframe> element, call contentFrame() on it to get the same kind of frame locator. Nested frames chain: the courier widget on the same page has a second iframe inside the first, reached with two frame locators in a row.

qa-052.spec.js(https://practice.learnplayforge.com)
import { test, expect } from '@playwright/test'; test('mark the invoice paid inside its iframe', async ({ page }) => { await page.goto('/embeds.html?mock=static&seed=42'); // From the main page, the button does not exist await expect(page.getByRole('button', { name: 'Mark as paid' })).toHaveCount(0); // Enter the frame first, then locate as usual const invoice = page.frameLocator('[data-testid="invoice-iframe"]'); await expect(invoice.getByRole('heading', { level: 1 })).toHaveText('Invoice INV-2026-0441'); await invoice.getByRole('button', { name: 'Mark as paid' }).click(); await expect(invoice.getByTestId('paid-status')).toHaveText('Paid'); });
Output
1 passed (2.0s)
Test yourself
A Mark as paid button is inside an iframe. What does page.getByRole('button', { name: 'Mark as paid' }) match?

Practised in the full course: the drill Inside the invoice.

See the chapter

AI can write the test now. The job is knowing whether it checks anything. That's what the course teaches, one chapter at a time.

163 lessons · 226 drills · start free

← All questions

Training a team? Talk to us.