What is Playwright
Playwright enables reliable end-to-end testing for modern
web apps.
Why playwight is important
Playwright is a powerful, modern test automation framework
developed by Microsoft.
Playwright offers:
Any browser β’ Any platform β’ One API
Cross-browser. Playwright supports all modern rendering engines including Chromium, WebKit, and Firefox.
Cross-platform. Test on Windows, Linux, and macOS, locally or on CI, headless or headed.
Cross-language. Use the Playwright API in TypeScript, JavaScript, Python, .NET, Java.
Test Mobile Web. Native mobile emulation of Google Chrome for Android and Mobile Safari. The same rendering engine works on your Desktop and in the Cloud.
It is designed to provide a unified API for automated
testing across multiple web browsers, offering robust and comprehensive testing
capabilities. Hereβs a detailed explanation of what makes Playwright stand out:
### Chromium, Firefox, and WebKit
1. **Chromium**:
- **Definition**:
Chromium is the open-source project that forms the basis for Google Chrome. It
includes the same rendering engine (Blink) and JavaScript engine (V8) as
Chrome.
- **Usage in
Playwright**: Playwright allows testing on browsers built on Chromium, such as
Google Chrome and Microsoft Edge, ensuring compatibility with the most widely
used browsers.
2. **Firefox**:
- **Definition**:
Firefox is an open-source web browser developed by Mozilla. It uses the Gecko
engine for rendering web pages.
- **Usage in
Playwright**: Playwright supports automated testing on Firefox, enabling
testers to ensure their web applications work correctly on a browser with
significant market share and unique features.
3. **WebKit**:
- **Definition**:
WebKit is a browser engine used primarily by Appleβs Safari. It powers the
rendering of web content on Safari and other browsers on macOS and iOS.
- **Usage in
Playwright**: Playwrightβs support for WebKit allows for testing on Safari,
which is essential for ensuring compatibility on Apple devices.
### Robustness of Playwright
1. **Cross-Browser Testing**: Playwrightβs ability to run
tests across Chromium, Firefox, and WebKit means that developers can ensure
their applications work consistently across all major browsers. This is crucial
for providing a seamless user experience regardless of the browser used.
2. **Headless Mode**: Playwright supports headless mode,
where browsers run without a graphical user interface. This mode is
particularly useful for CI/CD pipelines, allowing for fast and efficient
automated testing.
3. **Interception and Automation**: Playwright provides
extensive APIs for network interception, allowing testers to mock network
responses, modify request headers, and simulate various network conditions.
This helps in creating comprehensive test scenarios that mimic real-world
conditions.
4. **Handling of Modern Web Features**: Playwright is
designed to handle modern web applications with ease, supporting features like
single-page applications (SPAs), iframes, and web components. It provides
capabilities to deal with dynamic content and asynchronous operations, ensuring
that tests remain reliable and accurate.
5. **Auto-Waiting Mechanism**: Playwright automatically
waits for elements to be ready before performing actions, reducing the need for
explicit waits in test scripts. This makes tests more robust and less flaky.
6. **Screenshots and Videos**: Playwright can capture
screenshots and record videos of test runs, providing visual evidence of test
outcomes. This is particularly useful for debugging and reporting.
7. **Multi-Browser Contexts**: Playwright allows the
creation of multiple browser contexts within a single browser instance,
enabling isolated testing environments. This is useful for testing multi-user
scenarios and improving test isolation.
In summary, Playwrightβs robustness stems from its
comprehensive cross-browser support, advanced automation capabilities, and
features designed to handle modern web applications effectively. By supporting
Chromium, Firefox, and WebKit, Playwright ensures that developers can
confidently test their applications across all major browsers, providing a
reliable and consistent user experience.
Playwright advatnages over selenium
Resilient β’ No flaky tests
Auto-wait. Playwright waits for elements to be actionable prior to performing actions. It also has a rich set of introspection events. The combination of the two eliminates the need for artificial timeouts - the primary cause of flaky tests.
Web-first assertions. Playwright assertions are created specifically for the dynamic web. Checks are automatically retried until the necessary conditions are met.
You're spot on β Playwright's web-first assertions are built to handle the inherently asynchronous and dynamic nature of web apps. Instead of checking conditions just once and failing immediately (like many traditional test frameworks), Playwright automatically retries assertions until they pass or a timeout is reached.
Hereβs a quick breakdown:
β
What are Web-First Assertions?
Theyβre assertions optimized for web testing, meaning they:
-
Wait for elements to be ready (attached to the DOM, visible, stable, etc.).
-
Retry automatically until the assertion passes or the timeout hits.
-
Eliminate the need for manual waitFor
logic in many cases.
π Example:
import { test, expect } from '@playwright/test';
test('web-first assertion example', async ({ page }) => {
await page.goto('https://example.com');
// Waits until the button is visible and contains the expected text
await expect(page.locator('button#submit')).toHaveText('Submit');
});
In this case, toHaveText('Submit')
will automatically retry until the button has the correct text.
π Behind the scenes:
Assertions like:
-
toBeVisible()
-
toHaveText()
-
toBeHidden()
-
toHaveAttribute()
β¦all use built-in retry logic, making your tests more stable and readable without needing manual waits.
more webfirst assertion:
Awesome! Let's dive in and look at web-first assertions vs manual wait logic, and why the former is so much cleaner and more reliable.
π§ͺ Scenario: Waiting for a button to appear and be enabled
β
Web-First Assertion (Playwright way)
import { test, expect } from '@playwright/test';
test('web-first assertion for enabled button', async ({ page }) => {
await page.goto('https://example.com');
// Automatically waits for the button to appear and be enabled
await expect(page.locator('#start-button')).toBeEnabled();
});
-
βοΈ No need to check visibility, presence, or polling manually.
-
βοΈ Retries under the hood until the button is enabled or times out.
π οΈ Manual Wait Logic (Traditional way)
import { test } from '@playwright/test';
test('manual wait for enabled button', async ({ page }) => {
await page.goto('https://example.com');
const button = page.locator('#start-button');
// Wait until the element is visible
await button.waitFor({ state: 'visible' });
// Poll the elementβs state
await page.waitForFunction(el => !el.disabled, button);
});
π More Web-First Assertion Examples
π Text content
await expect(page.locator('.message')).toHaveText('Success!');
π¨ Class or attribute
await expect(page.locator('input')).toHaveAttribute('placeholder', 'Enter your name');
β
Checked checkbox
await expect(page.locator('input[type="checkbox"]')).toBeChecked();
π Count of elements
await expect(page.locator('.item')).toHaveCount(3);
βοΈ Bonus: Customize Timeouts
You can tweak how long Playwright waits before timing out:
await expect(page.locator('.item')).toHaveCount(3, { timeout: 10000 }); // waits up to 10s
Tracing. Configure test retry strategy, capture execution trace, videos, screenshots to eliminate flakes.
You're diving into one of Playwrightβs power tools now β Tracing and Retries! These features are π for debugging flaky tests and making your suite rock solid.
π¦ Tracing, Videos, Screenshots + Retry Strategy
Playwright makes it easy to capture detailed context when a test fails, helping you eliminate flakiness fast.
π Retry Strategy
You can configure test retries in your playwright.config.ts
:
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
retries: 2, // Automatically retries failing tests up to 2 times
});
-
β
Retries only failed tests
-
π Each retry can record trace/video/screenshot for debugging
-
π Great for dealing with non-deterministic behavior (network issues, animations, etc.)
π Tracing
Tracing captures:
Enable tracing in your test:
test('my flaky test', async ({ page, context }, testInfo) => {
// Start tracing before test actions
await context.tracing.start({ screenshots: true, snapshots: true });
await page.goto('https://example.com');
await page.click('#submit');
// Stop tracing and save if test failed
await context.tracing.stop({
path: `trace-${testInfo.title}.zip`,
});
});
Or better β use it automatically on retries via config:
// playwright.config.ts
export default defineConfig({
use: {
trace: 'on-first-retry', // 'on', 'off', 'retain-on-failure', or 'on-first-retry'
},
});
π₯ Videos + πΈ Screenshots
Capture video and screenshots for deeper visibility:
export default defineConfig({
use: {
video: 'on', // or 'retain-on-failure'
screenshot: 'only-on-failure', // 'on', 'off', or 'only-on-failure'
},
});
Now, when a test fails, youβll get:
π Real-World Setup Example
// playwright.config.ts
export default defineConfig({
retries: 1,
use: {
trace: 'on-first-retry',
video: 'retain-on-failure',
screenshot: 'only-on-failure',
},
});
π§ͺ Bonus Tip: Analyze Failing Tests
After a failed test run:
npx playwright show-trace trace-xyz.zip
You get an interactive UI to explore:
-
Actions
-
DOM snapshots
-
Network requests
-
Console logs
Perfect! Here's how we can break it down:
π§° Sample Playwright Setup for Tracing, Videos, Screenshots & Retries
Let me give you a mini project setup that you can drop into your project or tweak as needed.
π Project Structure
my-playwright-tests/
βββ tests/
β βββ example.spec.ts
βββ playwright.config.ts
βββ package.json
βββ traces/ <-- where traces will go
π§Ύ playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
retries: 1,
use: {
baseURL: 'https://example.com',
trace: 'on-first-retry', // capture trace on first retry
video: 'retain-on-failure', // record video if test fails
screenshot: 'only-on-failure', // take screenshot if test fails
},
reporter: [['html', { open: 'never' }]], // pretty reports
});
π§ͺ tests/example.spec.ts
import { test, expect } from '@playwright/test';
test('flaky demo test', async ({ page }) => {
await page.goto('/');
await page.click('text=More information'); // this might flake depending on content
await expect(page).toHaveURL(/iana.org/);
});
This test goes to https://example.com
and clicks a link. If the site layout changes, the trace + video + screenshot will help you figure out why it broke.
π¦ Install & Run
npm init playwright@latest
# or manually:
npm install --save-dev @playwright/test
npx playwright install
Then run your test:
npx playwright test
π Where are my traces?
If a test fails on the first try, you'll get:
-
π₯ test-results/<test-name>/video.webm
-
πΈ test-results/<test-name>/screenshot.png
-
π¦ test-results/<test-name>/trace.zip
To explore the trace:
npx playwright show-trace test-results/**/trace.zip
Interactive GUI will open β like a time machine for your test:
π‘ Optional: Auto-Open Failed Traces (dev script)
If you want to automatically open the trace viewer on failure:
// package.json
"scripts": {
"test": "npx playwright test || npx playwright show-trace test-results/**/trace.zip"
}
Now just run:
npm run test
Want me to zip up a working example or help troubleshoot a flaky test you already have?
No trade-offs β’ No limits
Browsers run web content belonging to different origins in different processes. Playwright is aligned with the modern browsers architecture and runs tests out-of-process. This makes Playwright free of the typical in-process test runner limitations.
Multiple everything. Test scenarios that span multiple tabs, multiple origins and multiple users. Create scenarios with different contexts for different users and run them against your server, all in one test.
Yes! You're now tapping into one of Playwrightβs superpowers β handling multiple tabs, origins, and users in a single test β which is often super messy in other frameworks.
Letβs break down how to do multiple everything:
π 1. Multiple Users (Contexts)
Each browser context is like an isolated user session (cookies, localStorage, etc. are separate).
β
Example: Two users chatting
import { test, expect } from '@playwright/test';
test('chat between two users', async ({ browser }) => {
// User A context
const userAContext = await browser.newContext();
const userAPage = await userAContext.newPage();
await userAPage.goto('https://your-chat-app.com');
await userAPage.fill('#message', 'Hello from A');
await userAPage.click('#send');
// User B context
const userBContext = await browser.newContext();
const userBPage = await userBContext.newPage();
await userBPage.goto('https://your-chat-app.com');
// Expect B to see the message
await expect(userBPage.locator('.message')).toContainText('Hello from A');
});
π§Ύ 2. Multiple Tabs (Pages)
Playwright makes it easy to open and switch between multiple tabs:
test('multiple tabs interaction', async ({ context }) => {
const page1 = await context.newPage();
const page2 = await context.newPage();
await page1.goto('https://example.com');
await page2.goto('https://news.ycombinator.com');
await expect(page1).toHaveTitle(/Example Domain/);
await expect(page2).toHaveTitle(/Hacker News/);
});
-
π Each newPage()
is a separate tab
-
You can even communicate between tabs if your app supports it (e.g., via localStorage events or shared service workers)
π 3. Multiple Origins (Cross-origin testing)
Playwright handles cross-origin scenarios automatically by keeping all contexts/pages secure but accessible from your test.
test('cross-origin scenario', async ({ page }) => {
await page.goto('https://example.com');
// This link opens a different origin in a new tab
const [newPage] = await Promise.all([
page.context().waitForEvent('page'),
page.click('a[target="_blank"]'),
]);
await newPage.waitForLoadState();
await expect(newPage).toHaveURL(/iana.org/);
});
π§ͺ Combined: Users + Tabs + Origins = π₯
test('multi-user, multi-tab scenario', async ({ browser }) => {
const user1 = await browser.newContext();
const user2 = await browser.newContext();
const user1Page = await user1.newPage();
const user2Page = await user2.newPage();
await user1Page.goto('https://app.com/login');
await user2Page.goto('https://app.com/login');
await user1Page.fill('#username', 'alice');
await user1Page.click('#login');
await user2Page.fill('#username', 'bob');
await user2Page.click('#login');
// Alice opens a new tab to a different origin
const aliceTab2 = await user1.newPage();
await aliceTab2.goto('https://support.app.com');
// Bob sends Alice a message
await user2Page.fill('#message', 'Yo Alice!');
await user2Page.click('#send');
// Alice sees the message in her first tab
await expect(user1Page.locator('.chat')).toContainText('Yo Alice!');
});
π§ Pro Tips:
-
Use browser.newContext()
for each user/session.
-
Use context.newPage()
for each tab within a user.
-
Use Promise.all
with waitForEvent('page')
to handle popups or new tabs.
-
Manage user state using storageState
if needed.
-----------------------
Questions: same test case with admin and non admin user
Ah, thatβs a great interview question β and itβs a classic real-world scenario too. Running an admin and a non-admin user in parallel in a single test is exactly what Playwrightβs multiple browser contexts are made for.
β
Goal:
Run a test where both:
-
An admin user performs an action (e.g. approving something)
-
A non-admin user observes the effect (e.g. sees status updated)
-
Both flows run simultaneously in the same test
π οΈ Approach: Use browser.newContext()
for each user
Each context = isolated session (cookies, localStorage, auth, etc.)
β
Sample Code: Admin + Non-Admin Parallel Flow
import { test, expect } from '@playwright/test';
test('admin and user in parallel', async ({ browser }) => {
// Create isolated sessions
const adminContext = await browser.newContext();
const userContext = await browser.newContext();
// Admin and User pages
const adminPage = await adminContext.newPage();
const userPage = await userContext.newPage();
// Log in both users
await adminPage.goto('https://your-app.com/login');
await adminPage.fill('#username', 'admin');
await adminPage.fill('#password', 'admin123');
await adminPage.click('button[type="submit"]');
await userPage.goto('https://your-app.com/login');
await userPage.fill('#username', 'user');
await userPage.fill('#password', 'user123');
await userPage.click('button[type="submit"]');
// Admin performs an action
await adminPage.goto('https://your-app.com/admin/requests');
await adminPage.click('text=Approve');
// User checks for update
await userPage.goto('https://your-app.com/requests');
await expect(userPage.locator('text=Approved')).toBeVisible();
});
π§ Interview Talking Points (what you could explain):
-
"Iβd use separate browser contexts to isolate sessions for admin and user."
-
"Both can interact in the same test without sharing state like cookies."
-
"Itβs important to simulate real-world flows where users interact indirectly (e.g. admin approves, user sees change)."
-
"This pattern also works with parallel tabs or even multiple devices."
π§ͺ Bonus: Using Promise.all
to run concurrent actions
await Promise.all([
adminPage.click('text=Approve'),
userPage.waitForSelector('text=Approved'),
]);
This ensures both actions happen nearly in sync β great for simulating real-time systems.
---------------------------
E-commerce question:
Perfect! Letβs build this around an e-commerce dashboard where:
π Scenario:
-
The Admin logs into the dashboard and marks an order as "Shipped".
-
The Customer (non-admin user) logs into their account and sees the order status update to "Shipped" in their orders page.
-
Both actions happen in the same test, in parallel using separate contexts.
β
Real-World Test Case: Admin + Customer Parallel Test
import { test, expect } from '@playwright/test';
test('admin ships order, customer sees status update', async ({ browser }) => {
// π§ββοΈ Admin context
const adminContext = await browser.newContext();
const adminPage = await adminContext.newPage();
// π§ββοΈ Customer context
const customerContext = await browser.newContext();
const customerPage = await customerContext.newPage();
// 1οΈβ£ Admin logs in
await adminPage.goto('https://ecommerce.com/admin/login');
await adminPage.fill('#username', 'admin');
await adminPage.fill('#password', 'admin123');
await adminPage.click('button[type="submit"]');
// 2οΈβ£ Customer logs in
await customerPage.goto('https://ecommerce.com/login');
await customerPage.fill('#email', 'jane@example.com');
await customerPage.fill('#password', 'user123');
await customerPage.click('button[type="submit"]');
// 3οΈβ£ Admin ships an order
await adminPage.goto('https://ecommerce.com/admin/orders');
await adminPage.click('text=#12345'); // Click order ID
await adminPage.click('button:has-text("Mark as Shipped")');
// 4οΈβ£ Customer checks order status
await customerPage.goto('https://ecommerce.com/my-orders');
const orderRow = customerPage.locator('tr:has-text("#12345")');
await expect(orderRow).toContainText('Shipped');
});
π¬ Bonus Explainer (for interviews):
You could say:
βIn this test, I used two isolated browser contexts to simulate both an admin and a customer user. The admin marks the order as shipped, and in the same test, the customer logs in and confirms the status change. This simulates a real-time interaction across roles.β
π§ Real-World Enhancements You Can Mention
-
Use storageState
to skip login steps (for speed)
-
Add Promise.all()
if you need both sides to sync live
-
Hook it into CI with screenshots/video for flaky test tracking
Storage Stage concepts in login:
Perfect! Letβs build this around an e-commerce dashboard where:
π Scenario:
-
The Admin logs into the dashboard and marks an order as "Shipped".
-
The Customer (non-admin user) logs into their account and sees the order status update to "Shipped" in their orders page.
-
Both actions happen in the same test, in parallel using separate contexts.
β
Real-World Test Case: Admin + Customer Parallel Test
import { test, expect } from '@playwright/test';
test('admin ships order, customer sees status update', async ({ browser }) => {
// π§ββοΈ Admin context
const adminContext = await browser.newContext();
const adminPage = await adminContext.newPage();
// π§ββοΈ Customer context
const customerContext = await browser.newContext();
const customerPage = await customerContext.newPage();
// 1οΈβ£ Admin logs in
await adminPage.goto('https://ecommerce.com/admin/login');
await adminPage.fill('#username', 'admin');
await adminPage.fill('#password', 'admin123');
await adminPage.click('button[type="submit"]');
// 2οΈβ£ Customer logs in
await customerPage.goto('https://ecommerce.com/login');
await customerPage.fill('#email', 'jane@example.com');
await customerPage.fill('#password', 'user123');
await customerPage.click('button[type="submit"]');
// 3οΈβ£ Admin ships an order
await adminPage.goto('https://ecommerce.com/admin/orders');
await adminPage.click('text=#12345'); // Click order ID
await adminPage.click('button:has-text("Mark as Shipped")');
// 4οΈβ£ Customer checks order status
await customerPage.goto('https://ecommerce.com/my-orders');
const orderRow = customerPage.locator('tr:has-text("#12345")');
await expect(orderRow).toContainText('Shipped');
});
π¬ Bonus Explainer (for interviews):
You could say:
βIn this test, I used two isolated browser contexts to simulate both an admin and a customer user. The admin marks the order as shipped, and in the same test, the customer logs in and confirms the status change. This simulates a real-time interaction across roles.β
π§ Real-World Enhancements You Can Mention
-
Use storageState
to skip login steps (for speed)
-
Add Promise.all()
if you need both sides to sync live
-
Hook it into CI with screenshots/video for flaky test tracking
-------------------
What is storgeState?
Perfect! Letβs level up your test with storageState
β it saves the login session so you donβt need to log in every time. This dramatically speeds up your tests and reduces flakiness. π¨
πΎ What is storageState
?
It stores the browser's cookies and localStorage after login β kind of like a "remember me" snapshot.
π οΈ Step 1: Create Login Script for Each User
Letβs create login scripts for both the admin and the customer:
π tests/setup/admin-login.setup.ts
import { chromium } from '@playwright/test';
(async () => {
const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('https://ecommerce.com/admin/login');
await page.fill('#username', 'admin');
await page.fill('#password', 'admin123');
await page.click('button[type="submit"]');
// Save session
await context.storageState({ path: 'storage/admin.json' });
await browser.close();
})();
π tests/setup/customer-login.setup.ts
import { chromium } from '@playwright/test';
(async () => {
const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('https://ecommerce.com/login');
await page.fill('#email', 'jane@example.com');
await page.fill('#password', 'user123');
await page.click('button[type="submit"]');
// Save session
await context.storageState({ path: 'storage/customer.json' });
await browser.close();
})();
π Run these once:
npx tsx tests/setup/admin-login.setup.ts
npx tsx tests/setup/customer-login.setup.ts
π§ͺ Step 2: Use Saved Sessions in Your Test
Now update your test to load the stored login states:
import { test, expect } from '@playwright/test';
test('admin ships order, customer sees update', async ({ browser }) => {
const adminContext = await browser.newContext({ storageState: 'storage/admin.json' });
const adminPage = await adminContext.newPage();
const customerContext = await browser.newContext({ storageState: 'storage/customer.json' });
const customerPage = await customerContext.newPage();
await adminPage.goto('https://ecommerce.com/admin/orders');
await adminPage.click('text=#12345');
await adminPage.click('button:has-text("Mark as Shipped")');
await customerPage.goto('https://ecommerce.com/my-orders');
const orderRow = customerPage.locator('tr:has-text("#12345")');
await expect(orderRow).toContainText('Shipped');
});
π Pro Tips:
-
Store JSON files like storage/admin.json
in .gitignore
-
Rerun the setup script if cookies/session expires
-
You can set storageState
globally in playwright.config.ts
too
Want me to send you the full folder structure or a template repo you can clone?
Trusted events. Hover elements, interact with dynamic controls, produce trusted events. Playwright uses real browser input pipeline indistinguishable from the real user.
Absolutely π₯ β you're touching on one of Playwright's biggest flexes:
β
Trusted Events in Playwright
Playwright uses real browser input β mouse, keyboard, and touch β through the browserβs native automation protocol, not synthetic JS events.
This means:
-
The events are trusted, like a real user interaction
-
You can interact with dynamic controls, modals, sliders, file uploads, canvas elements, etc.
-
You avoid issues like Element.click()
being blocked because it's "not from a user"
π±οΈ Examples of Trusted Interactions
1. Hovering over elements (shows dropdown/tooltips/etc.)
await page.hover('button#settings');
await expect(page.locator('text=Logout')).toBeVisible();
π‘ Unlike element.dispatchEvent('mouseover')
, this is a real hover.
2. Drag and Drop (canvas, sliders, etc.)
const slider = page.locator('#slider');
await slider.hover();
await page.mouse.down();
await page.mouse.move(200, 0); // Drag right
await page.mouse.up();
This mimics real user drag-and-drop behavior β the browser sees it as a genuine event.
3. Typing and Pressing Keys
await page.fill('#search', '');
await page.keyboard.type('Playwright rocks!');
await page.keyboard.press('Enter');
-
Keys go through the actual input pipeline
-
You can simulate key combinations: Shift+Tab
, Ctrl+C
, etc.
4. Clicking Elements
await page.click('text=Submit');
β‘οΈ This generates a trusted click event β some apps (like reCAPTCHA, file uploads, native modals) reject synthetic clicks, but Playwright passes.
π₯ Bonus: Simulating Real User Timing
await page.keyboard.type('slow typing...', { delay: 200 }); // 200ms per key
π Interview One-Liner
βPlaywright uses the browserβs native input pipeline, which means actions like click, hover, type, and drag are trusted and indistinguishable from real user interactions β this is critical for testing UI elements that reject synthetic events.β
Want a quick demo test that clicks a tricky element like a tooltip, a canvas, or a slider?
Test frames, pierce Shadow DOM. Playwright selectors pierce shadow DOM and allow entering frames seamlessly.
Yesss β this is another one of Playwrightβs superpowers πͺ
π― Goal:
You want to:
-
Select elements inside Shadow DOMs π
-
Interact with elements inside <iframe>
s πΌοΈ
-
Do it all seamlessly without pulling your hair out
𧩠1. Working with Shadow DOM
Playwright selectors can pierce shadow DOMs automatically using >>
or :shadow
.
β
Example:
HTML with shadow DOM:
<custom-card>
#shadow-root
<button id="buy">Buy now</button>
</custom-card>
π Selector:
await page.click('custom-card >> shadow=#buy');
// OR
await page.click('custom-card:shadow(#buy)');
Both will enter the shadow root and click the button.
πΌοΈ 2. Working with <iframe>
s
Playwright makes it simple by letting you get the frame and operate inside it like a normal page.
β
Example:
const frame = page.frame({ name: 'payment-frame' }); // or use .frameLocator() now
await frame?.fill('#card-number', '4242 4242 4242 4242');
π‘ Modern way: frameLocator
await page.frameLocator('iframe[name="payment-frame"]')
.locator('#card-number')
.fill('4242 4242 4242 4242');
π₯ Combine Both: Shadow DOM Inside an iFrame? No Problem
await page.frameLocator('iframe[name="widget-frame"]')
.locator('custom-widget:shadow(button.submit)')
.click();
π§ͺ Interview One-Liner (if you're asked):
βPlaywright lets me pierce Shadow DOMs and navigate into iframes natively using smart selectors. It simplifies accessing deeply nested UI elements β no ugly hacks needed.β
Want a small demo example using both iframe and shadow DOM together in a test file?
Full isolation β’ Fast execution
Browser contexts. Playwright creates a browser context for each test. Browser context is equivalent to a brand new browser profile. This delivers full test isolation with zero overhead. Creating a new browser context only takes a handful of milliseconds.
Log in once. Save the authentication state of the context and reuse it in all the tests. This bypasses repetitive log-in operations in each test, yet delivers full isolation of independent tests.
Powerful Tooling
Codegen. Generate tests by recording your actions. Save them into any language.
Playwright inspector. Inspect page, generate selectors, step through the test execution, see click points, explore execution logs.
Trace Viewer. Capture all the information to investigate the test failure. Playwright trace contains test execution screencast, live DOM snapshots, action explorer, test source, and many more.
Installation
Introduction
Test
on Windows, Linux, and macOS, locally or on CI, headless
or headed with
native mobile emulation of Google Chrome for Android and Mobile
Safari.
Installing
Playwright
Before installing playwright kindly install node. If thats not installed directly add as a extension in vs code.
Get started by installing Playwright using npm or yarn.
Alternatively you can also get started and run your tests using the VS Code Extension.
npm init playwright@latest
or
yarn create playwright
or
pnpm create playwright
Run the install command and select the following to get
started:
- Choose between
TypeScript or JavaScript (default is TypeScript)
- Name of your
Tests folder (default is tests or e2e if you already have a tests folder
in your project)
- Add a GitHub
Actions workflow to easily run tests on CI
- Install
Playwright browsers (default is true)
System
requirementsβ
- Node.js 18+
- Windows 10+, Windows Server 2016+
or Windows Subsystem for Linux (WSL).
- MacOS 12 Monterey, MacOS 13
Ventura, or MacOS 14 Sonoma.
- Debian 11, Debian 12, Ubuntu
20.04 or Ubuntu 22.04, with x86-64 or arm64 architecture.
What's
Installedβ
Playwright
will download the browsers needed as well as create the following files.
playwright.config.ts
package.json
package-lock.json
tests/
example.spec.ts
tests-examples/
demo-todo-app.spec.ts
The playwright.config is where you can add configuration for Playwright
including modifying which browsers you would like to run Playwright on. If you
are running tests inside an already existing project then dependencies will be
added directly to your package.json
.
The tests
folder contains a basic example test to help you get
started with testing. For a more detailed example check out the tests-examples
folder which
contains tests written to test a todo app.
After the
installation complete this will display on the powershell/command prompt
Inside that
directory, you can run several commands:
yarn playwright test
Runs the end-to-end tests.
yarn playwright test --ui
Starts the interactive UI mode.
yarn playwright test --project=chromium
Runs the tests only on Desktop Chrome.
yarn playwright test example
Runs the tests in a specific file.
yarn playwright test --debug
Runs the tests in debug mode.
yarn playwright codegen
Auto generate tests with Codegen.
We suggest
that you begin by typing:
yarn playwright test
also if test is running in headless (when you run the single test class file) you can use --headed, so browser will launch. sequentially test cases will run.
npx playwright test example.spec.ts --headed
Steps to start in
intellij
1.
Open intellij and click on create new project or empty
project.
2.
Now on terminal write this command npm init playwright@latest --yes -- --quiet
--browser=chromium --browser=firefox --browser=webkit βgha
3.
If playwright is already installed click on no and wait for
1-2 min and playwright will be installed.
4.
Also check commands will be there and these will be useful
for running the tests.
5.
Let it finish.
Folder structure of playwrithgt will be there.
If you donβt have
intellij, you can run the playwright code on vs code as well.
1.
Open vs code
2.
Open folder (where you have to create project)
3.
Open plugin and write playwright and add the plugin.
4.
Open command palate (command+shift+p) and run test: install
playwright.
5.
After that brower selection checkbox will be displayed.
6.
Let it finish. Folder structure of playwrithgt will be
there.
========================================================
SETUP IS COMPLETED NOW WE WILL RUN THE TEST
1.
Open
vs code and open the folder which you created in above.
2.
Now
go to Testing section and you will see that below.
3.
You
will see the browser option and checkbox.
4.
Show
browser will open the browser else it will open in the headless mode.
5.
Now
we will run the code some more. (Please see the command section like how to run the code. ).
P
Locate the
element: {This is heart of
playwright}
1.
https://playwright.dev/docs/locators, https://playwright.dev/docs/writing-tests
These are the
recommended built in locators.
- page.getByRole() to locate by explicit and
implicit accessibility attributes.
- page.getByText() to locate by text content.
- page.getByLabel() to locate a form control
by associated label's text.
- page.getByPlaceholder() to locate an input by
placeholder.
- page.getByAltText() to locate an element,
usually image, by its text alternative.
- page.getByTitle() to locate an element by
its title attribute.
- page.getByTestId() to locate an element based
on its
data-testid
attribute (other
attributes can be configured).
9.
10.
//The supported method of any locator
11.
// buttonLocator.check();
12.
// buttonLocator.click();
13.
//
buttonLocator.uncheck();
14.
//
buttonLocator.fill("abcd");
15.
//
buttonLocator.focus();
16.
//
buttonLocator.press("a");
17.
//
buttonLocator.setInputFiles("C:/anyfile.txt");
18.
//
buttonLocator.selectOption('blue');