Fun with Learning Technology
LearnCoursesQuestionsTracksToolsNewsExplorePractice
Fun with Learning Technology

A new problem, explained clearly, every day.

Subscribe
Learn
  • Lessons
  • Topics
  • News
  • Tools
  • Courses
  • Career tracks
  • Everything
Site
  • About
  • Contact
  • Support
  • Privacy
  • Terms
Get the daily one

One email per new problem. No spam.

Request a tutorial

Requests shape what gets made next.

© 2026 Fun with Learning TechnologyRSS
Home›Courses›Playwright›Explain the key differences between Playwright and Selenium

Interview Prep

Explain the key differences between Playwright and Selenium

Playwright is a modern automation framework designed for contemporary web architecture by utilizing native browser protocols instead of legacy drivers. Understanding the architectural divergence from traditional methods allows engineers to build resilient, high-performance testing suites that minimize flaky behavior. This lesson clarifies the fundamental shifts in how browser interaction occurs under the hood.

The Evolution of Protocol Communication

To understand the difference, one must look at how the test runner communicates with the browser. Legacy automation relies on an intermediary bridge, which translates commands into HTTP requests, introducing network latency and potential inconsistencies in message delivery. In contrast, modern frameworks use a persistent WebSocket connection to the browser's DevTools Protocol. This enables a bidirectional channel where the browser can push state changes, errors, or events to the test runner immediately. Because the connection is established once upon browser launch, the overhead is significantly lower than repeatedly sending discrete HTTP requests. This architectural change allows for instant reaction to page events, as the framework is essentially 'plugged in' to the browser engine's internal event loop, eliminating the need for brittle, polling-based synchronization mechanisms that traditionally plagued automated test suites.

const { chromium } = require('playwright');

(async () => {
  // Launching creates a direct WebSocket connection to the browser process
  const browser = await chromium.launch();
  const page = await browser.newPage();
  // Commands are sent over this persistent socket immediately
  await page.goto('https://example.com');
  await browser.close();
})();

Native Automatic Waiting Mechanisms

The most significant friction point in testing is the time disparity between when a command is issued and when the browser's DOM is actually ready to receive it. Traditional tools require developers to manually inject 'wait' statements, which are notoriously unreliable; wait too long and your tests are slow, wait too short and they fail. Modern frameworks solve this by baking the concept of actionability directly into the core engine. Before any action—like clicking or typing—is executed, the framework performs a series of checks: is the element attached to the DOM? Is it visible? Is it stable (not animating)? Is it receiving events? This 'actionability' logic runs entirely within the browser process, ensuring that the test runner only proceeds once the element is truly interactive. This eliminates the need for manual sleep timers and makes the test suite inherently self-healing during minor UI transitions.

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com');
  // No manual 'sleep' needed: click() waits for visibility and stability automatically
  await page.click('text=More information');
  await browser.close();
})();

Handling Multiple Contexts and Isolation

Traditional automation models treat every browser instance as a heavy, standalone process, which makes running tests in parallel a resource-intensive challenge. If you want to test multiple user sessions simultaneously, you would typically need to launch multiple independent browser instances, consuming massive amounts of RAM and CPU. Modern architecture introduces the 'Browser Context,' which is essentially an isolated incognito session within a single browser instance. Because contexts are ephemeral and share the same underlying engine process but have separate local storage, cookies, and indexedDB, you can spin up dozens of isolated environments in milliseconds. This allows for massive parallelization within a single test runner, drastically reducing the feedback loop time. This design shift moves the burden of isolation from the operating system to the framework, allowing for extremely lightweight, fast, and clean test environments.

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  // Contexts share the same browser engine but maintain total storage isolation
  const context1 = await browser.newContext();
  const context2 = await browser.newContext();
  const page1 = await context1.newPage();
  const page2 = await context2.newPage();
  await browser.close();
})();

Full Network Interception and Control

Testing often involves complex interactions with backend APIs or third-party services that may not be available during the test cycle. Older frameworks treat the network as a black box, making it difficult to stub responses or manipulate outgoing requests without third-party proxy tools. Modern frameworks treat the network as a first-class citizen, allowing you to intercept, modify, or block any network request directly through the browser protocol. You can simulate slow connections, mock entire API responses, or force specific HTTP status codes to test error handling logic. Because this interception happens at the browser level, it is invisible to the application code, creating an incredibly powerful way to test edge cases, authentication flows, or failure conditions without needing to orchestrate external mock servers or modify your application's production infrastructure.

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  // Intercepting network requests to mock API data before the browser processes it
  await page.route('**/api/data', route => route.fulfill({
    status: 200,
    body: JSON.stringify({ items: ['mocked', 'data'] })
  }));
  await page.goto('https://example.com');
  await browser.close();
})();

Advanced Execution Trace and Observability

Debugging tests is frequently cited as the most difficult aspect of automated quality assurance. In legacy systems, debugging often relies on taking screenshots or watching a slow-motion video after the fact, providing little insight into the 'why' behind a failure. Modern approaches utilize deep integration with the browser's internal engine to record execution traces. A trace captures everything: a full DOM snapshot at every action, console logs, network requests, and even the actionability state of elements. This allows a developer to perform a 'post-mortem' analysis, where they can rewind the state of the browser, inspect the network response that led to a specific error, and see the exact DOM structure at the moment of failure. This level of observability effectively eliminates the need for 'reproducibility' guesswork, as the trace file contains the entire context required to understand exactly what occurred during the execution.

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const context = await browser.newContext();
  // Enabling tracing records full execution logs and DOM states for post-mortem debugging
  await context.tracing.start({ screenshots: true, snapshots: true });
  const page = await context.newPage();
  await page.goto('https://example.com');
  await context.tracing.stop({ path: 'trace.zip' });
  await browser.close();
})();

Key points

  • Playwright uses the native browser DevTools Protocol for fast, reliable communication.
  • The framework provides automatic waiting mechanisms to eliminate brittle sleep commands.
  • Browser contexts allow for lightweight, isolated test sessions within a single engine process.
  • Network interception enables developers to mock API responses and test failure modes effectively.
  • Execution traces provide full observability into the browser state for rapid root cause analysis.
  • The architecture minimizes overhead by avoiding legacy drivers and HTTP-based bridging.
  • Parallelization is achieved through ephemeral browser contexts rather than entire browser launches.
  • Modern frameworks prioritize actionability by verifying element stability before executing user events.

Common mistakes

  • Mistake: Manually adding 'sleep' commands. Why it's wrong: It makes tests brittle and slow. Fix: Use Playwright's auto-waiting features where it waits for elements to be actionable.
  • Mistake: Over-reliance on XPath selectors. Why it's wrong: They are fragile and break when the DOM structure shifts. Fix: Use user-facing locators like getByRole or getByLabel that resemble how a real user interacts with the page.
  • Mistake: Treating multiple pages as separate browser instances. Why it's wrong: It consumes excessive memory and slows down execution. Fix: Utilize the BrowserContext feature to isolate sessions within a single browser instance.
  • Mistake: Trying to share state between test files. Why it's wrong: Playwright runs tests in parallel by default, causing race conditions. Fix: Use fixtures or serial mode if specific dependencies must be maintained.
  • Mistake: Assuming execution is strictly synchronous. Why it's wrong: Playwright API calls are asynchronous and return Promises. Fix: Always use the 'await' keyword to ensure actions complete before proceeding to the next step.

Interview questions

What is the primary architectural difference between Playwright and older web automation tools?

Playwright utilizes a modern WebSocket connection to communicate with the browser, which allows for persistent, bi-directional communication. Older automation frameworks typically rely on an HTTP-based protocol, which is inherently slower and more prone to connection issues. Because Playwright manages browser contexts internally, it can command the browser engine directly without waiting for a separate driver executable to interpret commands, leading to significantly faster and more stable test executions.

How does Playwright handle element synchronization compared to traditional implicit waiting mechanisms?

Playwright uses 'auto-waiting' out of the box, meaning it performs a range of actionability checks—like visibility, stability, and receiving events—before executing any action. In traditional frameworks, you often had to write custom 'wait for element' logic or configure global implicit waits, which frequently led to flaky tests. In Playwright, if you call `await page.click('button')`, the framework automatically ensures the button is present and actionable, removing the need for manual sleep statements or complex polling logic.

Compare the approach of using 'Browser Contexts' versus 'New Browser Instances' for test isolation.

Browser Contexts are a unique feature of Playwright that creates isolated 'incognito-like' sessions within a single browser instance. This is far more efficient than launching a new browser process for every test case. While launching a new browser is heavy and time-consuming, Browser Contexts allow you to run dozens of tests in parallel within the same process. This significantly reduces memory footprint and startup time while maintaining perfect isolation for cookies, cache, and session storage.

Explain how Playwright manages multi-tab and multi-window scenarios compared to traditional automation tools.

Traditional tools often struggle with switching context between windows, requiring manual focus commands that are notoriously brittle. Playwright handles this natively through its event-driven architecture. For example, to handle a popup, you simply use the code: `const [newPage] = await Promise.all([context.waitForEvent('page'), page.click('target')])`. Playwright monitors the browser state internally, capturing the new page object automatically when the event triggers, ensuring that your test flow remains uninterrupted regardless of how many tabs or windows open.

Why is Playwright's ability to intercept network requests considered a game-changer for automated testing?

Playwright allows you to intercept, modify, and mock network traffic at the protocol level, which is much more reliable than trying to manipulate UI state to trigger specific data responses. Using `page.route('**/api/data', route => route.fulfill({ status: 200, body: 'mocked' }))` lets you test UI components in total isolation from unstable backend services. Unlike tools that require external proxies or complex configurations to inspect traffic, this capability is baked directly into the Playwright engine for every single test.

Discuss the trade-offs between Playwright's execution model and the traditional 'driver-based' automation model.

The traditional driver-based model acts as an external orchestrator, sending requests through a middleman, which introduces latency and makes browser lifecycle management external to the test script. Playwright, however, communicates directly with the browser's developer tools protocol. This allows for features like trace viewers, screen recording, and 'time-travel' debugging, where you can inspect the exact state of the DOM at any point in the execution. By keeping the automation intelligence inside the browser process, Playwright eliminates the 'session timeout' and 'orphan process' errors that often plague traditional driver-dependent automation suites.

All Playwright interview questions →

Check yourself

1. Why is Playwright considered more resilient than older automation frameworks when interacting with elements?

  • A.It uses a centralized server for all browser communications.
  • B.It performs automatic actionability checks before executing commands.
  • C.It ignores CSS transitions to speed up interactions.
  • D.It requires all elements to have a unique ID attribute.
Show answer

B. It performs automatic actionability checks before executing commands.
Playwright is resilient because it auto-waits for elements to be visible, stable, and enabled. Option 0 is wrong as it is architectural; Option 2 is wrong because it handles animations well; Option 3 is wrong as it works with various selectors.

2. How does BrowserContext improve test suite efficiency?

  • A.It automatically records all test activity to a video file.
  • B.It allows tests to run without loading any network resources.
  • C.It creates isolated storage and cookies for every test without restarting the browser.
  • D.It compiles the code into machine language for faster execution.
Show answer

C. It creates isolated storage and cookies for every test without restarting the browser.
BrowserContext creates a fresh, isolated state for each test, making them fast and independent. Option 0 is a feature but not the efficiency gain; Option 1 is incorrect; Option 3 is not how Playwright works.

3. What is the primary advantage of using 'getByRole' over CSS selectors?

  • A.It is the only way to interact with hidden elements.
  • B.It creates tests that reflect the accessibility tree and user experience.
  • C.It executes significantly faster on low-memory devices.
  • D.It prevents the need for any assertions in the code.
Show answer

B. It creates tests that reflect the accessibility tree and user experience.
getByRole forces developers to write accessible, user-centric tests. Option 0 is false; Option 2 is negligible performance-wise; Option 3 is fundamentally wrong as assertions are essential.

4. When should you use the 'page.waitForTimeout' method in a test script?

  • A.Only when debugging, as it is generally an anti-pattern for production tests.
  • B.Every time an element is clicked to ensure the page has updated.
  • C.When you need to wait for a specific backend API to return data.
  • D.To synchronize the speed of the test with the visual animation duration.
Show answer

A. Only when debugging, as it is generally an anti-pattern for production tests.
waitForTimeout causes flaky tests and is bad practice; it should only be used for temporary debugging. The other options suggest using it for logic, which should be handled by web-first assertions.

5. How does Playwright handle multiple tabs within the same test?

  • A.It requires a separate browser process for each tab.
  • B.It limits the test to a single tab at any given time.
  • C.It uses events to track and interact with new pages created by the context.
  • D.It forces all links to open in the current tab by modifying the DOM.
Show answer

C. It uses events to track and interact with new pages created by the context.
Playwright treats tabs as 'pages' within a context, allowing you to await a new page event. Option 0 is inefficient; Option 1 is false; Option 3 is unnecessary manipulation.

Take the full Playwright quiz →

← PreviousExploring Playwright's plugin ecosystemNext →How would you handle dynamic elements in Playwright?

Playwright

37 lessons, free to read.

All lessons →

Track your progress

Sign in to mark lessons done, score quizzes and keep notes.

Open in the app