Skip to content

BROWSER INTERACTIONS · LESSON 17 OF 24

Popups and multiple pages

Capture a new tab without losing control of the original page.

  • popup
  • Page
  • context
  • cleanup

Your runnable checkpoint

Extract into a new practice folder. Each download contains tests, configuration, the complete local practice app and an offline lesson. Use Node.js 22 or 24. Each checkpoint is independent.

Install dependencies and browser binaries using the commands below. The test runner starts the practice server on port 4174. Tests target localhost, never the production EduPoly site.

1. Understand the concepts

A page can open another page in the same context. Listen for popup on the originating page when the action is expected to open a child window; listen on the context when the source is not known.

Create the event promise before clicking. The new page may exist before its content is ready, so use a locator assertion on that page to wait for the meaningful content.

Do not keep using the original page’s locators for the popup. Both Page objects remain available, and you can assert that the original screen is unchanged.

Close manually opened pages when the scenario is finished if they are no longer needed. The runner will also tear down its test context, but explicit local cleanup makes intent clearer.

2. Build and run

These are complete test files and configuration. The download also includes app/ and server.mjs; preserve their paths. Stop any manually started practice server before running the suite.

Terminal
npm install
npx playwright install chromium
npm run check
npm test

tests/lesson.spec.ts

tests/lesson.spec.ts
import { test, expect } from '@playwright/test';
test('open help in another page', async ({ page }) => {
  await page.goto('/');
  const waiting = page.waitForEvent('popup');
  await page.getByRole('link', { name: 'Open help' }).click();
  const help = await waiting;
  await expect(help.getByRole('heading', { name: 'Practice help' })).toBeVisible();
  await expect(page.getByRole('heading', { name: 'Playwright Lab', exact: true })).toBeVisible();
  await help.close();
});

playwright.config.ts

playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 1 : 0,
  workers: process.env.CI ? 2 : undefined,
  timeout: 30000,
  expect: { timeout: 5000 },
  reporter: [['list'], ['html', { open: 'never' }]],
  use: { baseURL: 'http://127.0.0.1:4174', trace: 'retain-on-failure', screenshot: 'only-on-failure' },
  projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
  webServer: { command: 'node server.mjs', url: 'http://127.0.0.1:4174', reuseExistingServer: false, timeout: 15000 }
});

package.json

package.json
{
  "name": "edupoly-playwright-popups-and-multiple-pages",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "test": "playwright test",
    "test:headed": "playwright test --headed",
    "test:ui": "playwright test --ui",
    "check": "tsc --noEmit",
    "report": "playwright show-report",
    "serve": "node server.mjs"
  },
  "devDependencies": {
    "@playwright/test": "1.56.1",
    "typescript": "5.9.3",
    "@types/node": "22.20.5"
  }
}

tsconfig.json

tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "CommonJS",
    "moduleResolution": "Node",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "types": [
      "node"
    ],
    "noEmit": true
  },
  "include": [
    "tests/**/*.ts",
    "pages/**/*.ts",
    "playwright.config.ts"
  ]
}

.gitignore

.gitignore
node_modules/
playwright-report/
test-results/
playwright/.auth/
.env

3. Trace the code and check the result

  1. The popup promise cannot miss a fast-opened window.
  2. Assertions target the correct Page instance.
  3. The extra page is closed when no longer needed.

Verify your result

  • The help page opens and shows its heading.
  • The original lab page remains available.

4. Practice independently

Check the popup URL ends with /help.html.

Show the explanation

Use await expect(help).toHaveURL(/\/help\.html$/) before closing it.

When something goes wrong

If a browser executable is missing, run npx playwright install chromium in the project folder. If port 4174 is occupied, stop your manual practice server. Read assertion call logs for mismatched locators or expectations. Use npm run report to inspect failures; do not add fixed sleeps or broad resets to hide them.

Read the related official Playwright documentation ↗