Tuesday, March 4, 2025

Playwright

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 TypeScriptJavaScriptPython.NETJava.

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 verbose.

  • ❌ Easier to get wrong or miss edge cases.

  • ❌ Harder to maintain.


๐Ÿ” 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:

  • DOM snapshots

  • Network requests

  • Console logs

  • Screenshots during interactions

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:

  • ๐Ÿ“ผ A video of the test

  • ๐Ÿ“ธ Screenshots at the point of failure

  • ๐Ÿ“ฆ A full .zip trace you can view with npx playwright show-trace trace.zip


๐Ÿš€ 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:

  • DOM snapshots at every step

  • Console logs

  • Network activity

  • Clicks and assertions


๐Ÿ’ก 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');
});
  • ๐Ÿง Context = separate user

  • ๐Ÿ”’ Cookie/session storage is isolated


๐Ÿงพ 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/);
});
  • ๐Ÿ’ฅ No hacks needed for CORS

  • Playwright handles context management and security


๐Ÿงช 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');
  • frameLocator() is now the recommended approach — it automatically waits for the iframe and avoids null checks.


๐Ÿ”ฅ 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.

  1. page.getByRole() to locate by explicit and implicit accessibility attributes.
  2. page.getByText() to locate by text content.
  3. page.getByLabel() to locate a form control by associated label's text.
  4. page.getByPlaceholder() to locate an input by placeholder.
  5. page.getByAltText() to locate an element, usually image, by its text alternative.
  6. page.getByTitle() to locate an element by its title attribute.
  7. 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');

No comments:

Post a Comment