VALIDATION LAB•IFRAMES
Embed
Iframes
Reach into an iframe, fill a form inside it, and verify the parent receives the submission via postMessage.
Scenario
Stripe, Auth0, embedded video players, support widgets — iframes appear everywhere. Tests that ignore the frame boundary find nothing because `page.locator(...)` cannot pierce frames. `frameLocator` is the answer.
Parent counter: 0
Live widget · interact freely
Manual test checklist
- 1Submit the iframe form — parent counter increments
- 2Inspect the message in DevTools — confirm `postMessage` shape is what the parent expects
- 3Reload the parent — counter resets to 0 (no localStorage persistence)
- 4Switch viewports — iframe width is responsive
- 5Confirm the iframe has a meaningful `title` attribute
Expected result
Submitting inside the frame updates a counter on the parent page.
Automation challenge
Use `page.frameLocator('[data-testid=frame-host] iframe')` to target the inner form. Fill, submit, then assert the parent counter incremented. Forgetting `frameLocator` is the most common iframe testing mistake.
Stable selectors
- Embedded form
[data-testid="frame-host"] - Parent counter
[data-testid="frame-counter"]
Locator strategy
Three levels from simple IDs to scoped Playwright locators. IDs and names are easy to learn but are not always the best long-term choice when labels change or components repeat.
Use simple IDs and names to understand how locating elements works.
1234
await page.goto('https://lab.hakdogan.com/practice/iframes');
const frame = page.frameLocator('[data-testid="frame-host"] iframe');
await frame.getByTestId('frame-input').fill('x@y.com');Avoid as primary strategies
XPath (unless there is no alternative), long CSS chains, Tailwind-style utility class selectors, generated or unstable IDs, and volatile framework internals break when layout, styling, or DOM structure shifts.
12
await page.locator('.w-full.rounded-xl.border.bg-blue-500').fill('demo@example.com');
await page.locator('//div[2]/form/div[1]/input').fill('demo@example.com');Reference Playwright spec
1234567891011
import { test, expect } from '@playwright/test';
test('interact across iframe boundary', async ({ page }) => {
await page.goto('https://lab.hakdogan.com/practice/iframes');
const frame = page.frameLocator('[data-testid="frame-host"] iframe');
await frame.getByTestId('frame-input').fill('cross-frame value');
await frame.getByTestId('frame-submit').click();
await expect(page.getByTestId('frame-counter')).toContainText('1');
});Headed Test Playback
Simulated headed-browser flow — no real browser is launched.
Automation-style playback (Playwright-shaped logs). No real browser; no commands run on your machine.
iframes.spec.ts- Reaching into iframe
- Submitting cross-frame form
- Asserting parent counter