Now that you’ve successfully installed Playwright and run your first test, it’s time to understand the core concepts that make Playwright work. In this chapter, we’ll break down Playwright’s architecture the Browser, Context, Page, and Locator.
Understanding these four parts is essential because they form the foundation for everything you’ll build with Playwright. Once you get comfortable with them, writing automation scripts will become intuitive.
Why these building blocks matter
Playwright’s structure is simple but powerful. It models how real browsers and users behave:
- The Browser is the main application (like Chrome or Firefox).
- A Browser Context is an isolated, independent browser session (like an incognito window).
- A Page is a single tab in that browser context.
- A Locator is how you find and interact with elements on that page.
With these four concepts, you can simulate any user behavior from opening multiple browser sessions to filling out forms, switching tabs, and testing user flows.
1. Browser
The browser object is Playwright’s connection to an actual browser engine (Chromium, Firefox, or WebKit). It’s responsible for launching, managing, and closing the browser instance.
Example:
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: false }); // Start a visible browser
await browser.close(); // Close it
})();
Key options:
| Option | Description |
|---|---|
headless: true | Run browser in background (faster, default) |
headless: false | Run browser visibly (for debugging) |
slowMo: 100 | Slows down actions by 100ms for easier observation |
2. Browser Context
A browser context represents an isolated session like a clean browser window without cookies, cache, or history. Each context behaves like a unique user.
Why is this useful?
Because you can run multiple contexts simultaneously without them interfering with each other. This means you can simulate multiple users logging into your app at the same time.
Example:
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
const context1 = await browser.newContext(); // User 1
const context2 = await browser.newContext(); // User 2
const page1 = await context1.newPage();
const page2 = await context2.newPage();
await page1.goto('https://example.com');
await page2.goto('https://example.org');
console.log(await page1.title()); // Example Domain
console.log(await page2.title()); // Example Domain
await browser.close();
})();
Each context acts like a separate browser window, completely independent.
3. Page
A page is a single browser tab. It’s where you’ll spend most of your time loading URLs, clicking buttons, typing into fields, and validating content.
Every Playwright test involves working with one or more pages inside a context.
Example:
import { test, expect } from '@playwright/test';
test('navigate to Playwright homepage', async ({ page }) => {
await page.goto('https://playwright.dev/');
const title = await page.title();
console.log('Page title:', title);
await expect(page).toHaveTitle(/Playwright/);
});
The { page } object is provided automatically by the Playwright Test Runner, it’s your tab for that test.
4. Locator
A locator is how Playwright finds elements on a web page. It’s the equivalent of document.querySelector() in the browser, but smarter and more reliable.
Instead of acting immediately, locators wait for elements to appear, become stable, and be ready for interaction, this is one reason Playwright tests are so stable.
Example:
import { test, expect } from '@playwright/test';
test('locator example', async ({ page }) => {
await page.goto('https://playwright.dev/');
// Create a locator
const getStartedButton = page.locator('text=Get started');
// Click it
await getStartedButton.click();
// Verify navigation
await expect(page).toHaveURL(/.*docs/);
});
Here, page.locator('text=Get started') finds the button by its visible text and clicks it. You can also use CSS or role-based selectors.
Locator strategies
Playwright supports many ways to find elements:
| Selector Type | Example | Description |
| Text | page.locator('text=Login') | Finds element by visible text |
| CSS | page.locator('#username') | Finds element by CSS selector |
| Role | page.getByRole('button', { name: 'Submit' }) | Finds by accessible role (great for accessibility tests) |
| Test ID | page.locator('[data-testid="login-button"]') | Best for stable automation tests |
| Chained | page.locator('#form').locator('button') | Finds nested elements |
Putting it all together
Here’s a complete example showing all four building blocks working together:
const { chromium } = require('playwright');
(async () => {
// Launch browser
const browser = await chromium.launch({ headless: false });
// Create two independent sessions
const userContext = await browser.newContext();
const adminContext = await browser.newContext();
// Create pages (tabs)
const userPage = await userContext.newPage();
const adminPage = await adminContext.newPage();
// Navigate to different sites
await userPage.goto('https://example.com');
await adminPage.goto('https://playwright.dev');
// Locate and interact
const getStarted = adminPage.locator('text=Get started');
await getStarted.click();
// Print titles
console.log('User page title:', await userPage.title());
console.log('Admin page title:', await adminPage.title());
await browser.close();
})();
This small script launches a browser, creates two isolated sessions, opens different pages, interacts with elements, and prints titles showing Playwright’s versatility.
Common pitfalls to avoid
| Problem | Cause | Solution |
TimeoutError: Element not found | Locator too early | Use await page.waitForSelector() or rely on auto-waiting |
Browser closed unexpectedly | Forgot to await async actions | Add await before Playwright methods |
| Elements overlap or move | Animations or transitions | Use await expect(locator).toBeVisible() before clicking |
Exercise
Task 1: Create a script that opens two different pages (e.g., Google and GitHub) and logs their titles.
Task 2: Use locators to click a visible link on one of the pages.
Bonus: Add a short delay between actions using slowMo when launching the browser to visualize your test.
Cheat Sheet
| Concept | Code Example |
| Launch browser | const browser = await chromium.launch({ headless: false }) |
| Create context | const context = await browser.newContext() |
| Create page | const page = await context.newPage() |
| Go to site | await page.goto('https://example.com') |
| Create locator | const button = page.locator('text=Login') |
| Click element | await button.click() |
| Close browser | await browser.close() |
Summary
In this chapter, you learned Playwright’s four essential building blocks:
- Browser: The engine that runs everything.
- Context: Isolated sessions for different users.
- Page: Your working tab.
- Locator: How you find and interact with elements.
These concepts form the foundation of every Playwright test you’ll ever write.
