Answer
Handling iframes in Playwright
Playwright uses frameLocator() to interact with elements inside an <iframe>. This is much simpler than Selenium's switchTo().frame() approach.
Basic iframe Access
TypeScript
// Access an iframe by CSS selector
const iframe = page.frameLocator('iframe#payment-frame');
// Now use normal locators inside the iframe
await iframe.getByLabel('Card Number').fill('4111111111111111');
await iframe.getByLabel('Expiry').fill('12/26');
await iframe.getByRole('button', { name: 'Pay' }).click();
Access by iframe Name or Title
TypeScript
// By name attribute: <iframe name="checkout">
const iframe = page.frameLocator('[name="checkout"]');
// By src URL pattern
const iframe = page.frameLocator('iframe[src*="stripe.com"]');
Nested iframes
TypeScript
// Outer iframe
const outerFrame = page.frameLocator('#outer-iframe');
// Inner iframe inside the outer one
const innerFrame = outerFrame.frameLocator('#inner-iframe');
await innerFrame.getByRole('button', { name: 'Submit' }).click();
Using locator().contentFrame() (Playwright v1.43+)
TypeScript
const frameLocator = page.locator('iframe').contentFrame();
await frameLocator.getByLabel('Email').fill('test@example.com');
Wait for iframe to Load
TypeScript
// Wait for the iframe itself to appear
await page.locator('iframe#payment-frame').waitFor();
// Then access it
const iframe = page.frameLocator('iframe#payment-frame');
await iframe.getByLabel('Card number').fill('4111111111111111');
Full Example — Stripe Payment iframe
TypeScript
import { test, expect } from '@playwright/test';
test('complete payment with iframe', async ({ page }) => {
await page.goto('/checkout');
await page.getByRole('button', { name: 'Proceed to Payment' }).click();
// Wait for Stripe iframe to appear
await page.locator('iframe[src*="stripe.com"]').waitFor();
const stripe = page.frameLocator('iframe[src*="stripe.com"]');
await stripe.getByPlaceholder('Card number').fill('4242424242424242');
await stripe.getByPlaceholder('MM / YY').fill('12/25');
await stripe.getByPlaceholder('CVC').fill('123');
await page.getByRole('button', { name: 'Pay Now' }).click();
await expect(page).toHaveURL('/order-confirmed');
});
Key Difference from Selenium: No
switchTo().frame()/switchTo().defaultContent()juggling. Playwright'sframeLocator()returns scoped locators that automatically interact inside the frame.
