Module A-1·9 min read

Playwright (Primary), Cypress (Comparison), authentication flows, network interception, flaky test prevention, and CI-friendly test architecture.

Written by Jatin Jain Saraf · Architect of SupraScan

Module 5 — End-to-End Testing

Who this is for: Full-stack and QA engineers responsible for the final safety net of an application. End-to-End (E2E) testing guarantees that all your distinct components—frontend, backend, database, and third-party APIs—work together seamlessly from the user's perspective.


Learning Objectives

By the end of this module, you will be able to:

  • Understand the dominance of Playwright in the modern E2E landscape
  • Write resilient Playwright tests that simulate user flows
  • Handle asynchronous network behavior and intercept requests
  • Prevent flaky tests using auto-waiting strategies
  • Execute tests across multiple browsers seamlessly
  • Structure an enterprise-grade, CI-friendly test architecture

Playwright (Primary) vs. Cypress

Historically, Selenium dominated E2E testing. Then Cypress revolutionized the developer experience. Today, Playwright (built by Microsoft) is the industry standard due to its speed, true multi-browser support, and lack of iframe-based architectural limitations.

While Cypress remains excellent (and you will see it in many legacy codebases), we focus on Playwright because it represents the modern, future-proof approach to E2E testing.


Playwright Fundamentals

Playwright scripts look remarkably like standard async JavaScript. It launches an actual browser (Chromium, Firefox, or WebKit), navigates to your app, and interacts with the DOM.

Setup

npm init playwright@latest

A Basic Playwright Test

Notice how clean the async/await syntax is. Playwright features built-in auto-waiting. It waits for elements to become visible, enabled, and stable before clicking them—virtually eliminating the need for setTimeout() hacks that plague older E2E frameworks.

import { test, expect } from '@playwright/test';
 
test('user can add item to cart', async ({ page }) => {
  // Navigate to the app
  await page.goto('https://mystore.com');
 
  // Find an element and interact
  await page.locator('text=Smartphone').click();
  await page.locator('button:has-text("Add to Cart")').click();
 
  // Assert the cart badge updated
  const cartBadge = page.locator('.cart-count');
  await expect(cartBadge).toHaveText('1');
});

Authentication Flows & Bypass

Logging in via the UI before every E2E test is a massive anti-pattern. It makes your test suite painfully slow.

Instead, log in once via the UI, save the authentication state (cookies/local storage), and reuse it across all subsequent tests. Playwright supports this natively.

// global-setup.js
import { chromium } from '@playwright/test';
 
export default async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage();
 
  await page.goto('https://mystore.com/login');
  await page.fill('#user', 'admin');
  await page.fill('#pass', 'secret');
  await page.click('#submit');
 
  // Save the auth state to a JSON file!
  await page.context().storageState({ path: 'state.json' });
  await browser.close();
};

You then configure Playwright to load state.json before every test, bypassing the login screen entirely.


Network Interception

E2E tests interact with real APIs. But what if you want to test how your UI handles a 500 Internal Server Error from Stripe? You can't ask Stripe to fail.

Sign in to keep reading

The rest of this module is free — sign in with Google to unlock it and track your progress.

Sign in & Register

Discussion

0

Join the discussion

Loading comments...