← All practice topics
Automation Practice · Playwright

Frames & Windows

Content that lives in another document: a page inside a page, a frame inside a frame, and new tabs and windows your test has to switch to.

Every solution on this page starts with this hook. With baseURL set to https://hiraanachwani.com in your playwright.config.ts, it opens this page before each test.

test.beforeEach(async ({ page }) => {
  await page.goto('/practice/frames-windows');
});

Iframe

The box below is a separate page loaded in an iframe. Locators on the main page can't see inside it.

Try this

  1. Type Hi from Playwright into the Message field inside the frame and click Send.
  2. Check the frame shows Sent: Hi from Playwright.
  3. Check that page.getByLabel('Message') on the main page finds nothing.

Locator tip: find the iframe element first, then step into it with contentFrame(). Every locator after that searches inside the frame: page.getByTitle('Simple frame').contentFrame().getByLabel('Message').

Show Playwright solution
test('iframe', async ({ page }) => {
  const frame = page.getByTitle('Simple frame').contentFrame();

  await frame.getByLabel('Message').fill('Hi from Playwright');
  await frame.getByRole('button', { name: 'Send' }).click();
  await expect(frame.getByTestId('frame-result')).toHaveText('Sent: Hi from Playwright');

  // The main page can't see inside the frame
  await expect(page.getByLabel('Message')).toHaveCount(0);
});

Nested iframes

A frame inside a frame. You have to step into each one in turn.

Try this

  1. Check the outer frame's heading is Outer frame.
  2. Click the button in the inner frame and check it shows Inner button clicked.

Locator tip: chain contentFrame() once per level: the outer iframe, then the inner iframe inside it. Store each frame in a variable so the test reads top-down.

Show Playwright solution
test('nested iframes', async ({ page }) => {
  const outer = page.getByTitle('Outer frame').contentFrame();
  await expect(outer.getByRole('heading', { level: 1 })).toHaveText('Outer frame');

  const inner = outer.getByTitle('Inner frame').contentFrame();
  await inner.getByRole('button', { name: 'Click inside the inner frame' }).click();
  await expect(inner.getByTestId('inner-result')).toHaveText('Inner button clicked');
});

New tab

A link with target="_blank" opens a new tab. Your test gets it as a second page object.

Try this

  1. Click Open new tab and check the new tab's heading is You're in a new tab.
  2. Check its URL ends with /practice/frames/new-tab.
  3. Close the new tab and check the original page is still usable.

Locator tip: start waiting for the tab before you click: const tabPromise = page.context().waitForEvent('page'). If you wait after the click, the event may already have fired.

Show Playwright solution
test('new tab', async ({ page }) => {
  const tabPromise = page.context().waitForEvent('page');
  await page.getByRole('link', { name: 'Open new tab' }).click();
  const newTab = await tabPromise;

  await expect(newTab.getByRole('heading', { level: 1 })).toHaveText("You're in a new tab");
  await expect(newTab).toHaveURL(/\/practice\/frames\/new-tab$/);

  await newTab.close();
  await expect(page.getByRole('link', { name: 'Open new tab' })).toBeVisible();
});