IKivan-kozenko -aqa
Try yourself as a QA tester
All topics·Intermediate·19 / 27

Frames

When a page embeds an iframe — a payment widget, a chat, a map — Playwright can't interact with elements inside it directly through page locators. I use page.frameLocator() to get a locator scoped to the iframe's DOM, then use standard locators inside it. The most common real-world case: testing a Stripe or PayPal embedded payment form.

Why iframes need special handling

An iframe is a separate document — page locators stop at the boundary, frameLocator() crosses it

An iframe is a separate HTML document embedded inside the main page. It has its own DOM, its own JavaScript context, and its own origin (often a different domain). page.getByRole() and other locators only search the main frame's DOM — they don't cross iframe boundaries.

The solution: page.frameLocator(selector) returns a FrameLocator — a locator object that's scoped to the iframe's content. I chain standard locators on it just like on page.

frameLocator — the preferred approach

frameLocator returns a locator that scopes all subsequent locator calls to the iframe's DOM. I target the iframe by its CSS selector (usually a class, id, or title attribute), then use normal locators inside.

The most common real-world scenario — embedded payment form where the card fields are inside a Stripe iframe:

ts
// Базове використання frameLocator
const frame = page.frameLocator('.frame-class')
await frame.getByLabel('User Name').fill('John')

// Реальний кейс: Stripe payment form
const stripeFrame = page.frameLocator('iframe[title="Stripe payment form"]')
await stripeFrame.getByLabel('Card number').fill('4242 4242 4242 4242')
await stripeFrame.getByLabel('Expiry').fill('12/26')
await stripeFrame.getByLabel('CVC').fill('123')

// Або через індекс якщо немає інших атрибутів
const firstFrame = page.frameLocator('iframe').first()

Frame objects — direct access

page.frame() gives access to the raw Frame object, which is useful when I need to run JavaScript inside the frame, evaluate expressions, or access frame metadata like the URL. I target frames by name attribute or URL pattern.

ts
// Отримати фрейм за атрибутом name
const frame = page.frame('frame-login')
if (frame) {
  await frame.fill('#username-input', 'John')
}

// Отримати фрейм за URL (regex)
const paymentFrame = page.frame({ url: /stripe.com/ })

// Виконати JavaScript всередині фрейму
const iframeTitle = await frame?.evaluate(() => document.title)

// Список всіх фреймів на сторінці
const allFrames = page.frames()
console.log(allFrames.map(f => f.url()))

Nested iframes

When a frame contains another iframe, I chain frameLocator calls. Each level scopes the search to the next iframe:

ts
// Вкладені iframes: зовнішній → внутрішній → елемент
const button = page
  .frameLocator('#outer-frame')
  .frameLocator('#inner-frame')
  .getByRole('button', { name: 'Submit' })

await button.click()