Prerequisites
- Basic TypeScript knowledge π
- Understanding of E2E testing concepts β‘
- Familiarity with web applications π»
- Experience with DOM interactions π
What you'll learn
- Set up Playwright with TypeScript for modern E2E testing π―
- Write cross-browser tests that work everywhere π
- Handle advanced scenarios like file uploads and mobile testing π±
- Implement parallel testing and advanced patterns π
- Debug and optimize Playwright tests effectively π
π― Introduction
Welcome to the future of E2E testing with Playwright! π If Cypress is the rockstar of E2E testing, then Playwright is the superhero with superpowers - it can test across all browsers simultaneously, handle the most complex scenarios, and do it all blazingly fast!
Playwright is Microsoftβs modern testing framework that brings together the best of all worlds: cross-browser support, mobile testing, network interception, and TypeScript-first design. Itβs like having a testing Swiss Army knife that just happens to be powered by rockets! π
By the end of this tutorial, youβll be writing robust, fast, and reliable E2E tests that work across Chrome, Firefox, Safari, and Edge. Letβs dive into this amazing journey! πββοΈ
π Understanding Playwright
π€ What Makes Playwright Special?
Playwright is like having a team of expert testers, each specialized in different browsers! πͺ Itβs designed from the ground up for modern web applications with features that other testing frameworks can only dream of.
Playwrightβs superpowers include:
- β¨ True Cross-Browser Testing: Chrome, Firefox, Safari, Edge
- π Lightning Fast: Parallel execution by default
- π± Mobile Testing: Real device emulation
- π Network Control: Mock APIs, offline testing
- π― Auto-Waiting: Smart waits for elements
- πΈ Visual Testing: Screenshot comparisons
- π Retry Logic: Built-in flaky test handling
π‘ Playwright vs Cypress: When to Choose What?
Hereβs when each framework shines:
Choose Playwright when: π
- Cross-Browser Support π: Need to test on multiple browsers
- Complex Scenarios π§©: File uploads, multiple tabs, frames
- Performance Testing β‘: Need parallel execution
- Mobile Testing π±: Testing responsive designs
- Enterprise Features π’: Advanced reporting and CI/CD
Choose Cypress when: π²
- Developer Experience π: Learning curve and debugging
- Real-Time Testing π: Interactive test development
- Simple Scenarios π: Standard web app testing
- Team Familiarity π₯: Team already knows Cypress
Real-world example: Your TypeScript app needs to work perfectly on Safari for iOS users? Playwright has you covered! π
π§ Playwright + TypeScript Setup
π Installation and Configuration
Letβs get Playwright running with TypeScript superpowers:
# π Install Playwright
npm init playwright@latest
# Or install manually with TypeScript
npm install --save-dev @playwright/test
npm install --save-dev typescript
# π¦ Install browsers
npx playwright install
TypeScript Configuration:
// π playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
// π Test directory
testDir: './tests',
// π― Test file patterns
testMatch: '**/*.spec.ts',
// β‘ Global test settings
timeout: 30000,
expect: {
timeout: 10000
},
// π Retry configuration
retries: process.env.CI ? 2 : 0,
// π Parallel execution
workers: process.env.CI ? 1 : undefined,
// π Reporter configuration
reporter: [
['html', { outputFolder: 'playwright-report' }],
['json', { outputFile: 'test-results.json' }],
['junit', { outputFile: 'results.xml' }]
],
// π¬ Global setup and teardown
globalSetup: require.resolve('./tests/global-setup'),
globalTeardown: require.resolve('./tests/global-teardown'),
// π§ Shared settings for all tests
use: {
// π Base URL
baseURL: 'http://localhost:3000',
// π₯ Tracing and debugging
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
// π― Default timeouts
actionTimeout: 10000,
navigationTimeout: 30000,
},
// π± Browser and device configuration
projects: [
// π₯οΈ Desktop browsers
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
// π± Mobile devices
{
name: 'Mobile Chrome',
use: { ...devices['Pixel 5'] },
},
{
name: 'Mobile Safari',
use: { ...devices['iPhone 12'] },
},
// π₯οΈ Tablets
{
name: 'iPad',
use: { ...devices['iPad Pro'] },
},
],
// π Development server
webServer: {
command: 'npm run dev',
port: 3000,
reuseExistingServer: !process.env.CI,
},
});
TypeScript Types and Utilities:
// π tests/types/test-types.ts
export interface TestUser {
id: string;
email: string;
password: string;
name: string;
role: 'user' | 'admin' | 'moderator';
emoji: string;
}
export interface TestProduct {
id: string;
name: string;
price: number;
category: string;
emoji: string;
inStock: boolean;
}
export interface APIResponse<T> {
success: boolean;
data: T;
message?: string;
error?: string;
}
// π― Page Object Model base class
export abstract class BasePage {
constructor(public page: Page) {}
abstract navigate(): Promise<void>;
abstract isLoaded(): Promise<boolean>;
// π§ Common utilities
async waitForLoadingComplete(): Promise<void> {
await this.page.waitForLoadState('networkidle');
await this.page.locator('[data-testid=loading]').waitFor({ state: 'hidden' });
}
async takeScreenshot(name: string): Promise<void> {
await this.page.screenshot({ path: `screenshots/${name}.png` });
}
}
// π‘οΈ Test fixtures
export interface TestFixtures {
authenticatedUser: TestUser;
testProducts: TestProduct[];
adminUser: TestUser;
}
π¨ Custom Fixtures and Setup
Create powerful test fixtures:
// π tests/fixtures/test-fixtures.ts
import { test as base, Page } from '@playwright/test';
import { TestUser, TestProduct } from '../types/test-types';
// ποΈ Extended test with custom fixtures
export const test = base.extend<{
authenticatedPage: Page;
testUser: TestUser;
testProducts: TestProduct[];
}>({
// π€ Authenticated user fixture
testUser: async ({}, use) => {
const user: TestUser = {
id: 'test-user-123',
email: '[email protected]',
password: 'PlaywrightTest123!',
name: 'Playwright Tester π',
role: 'user',
emoji: 'π'
};
// π± Create user in database
await setupTestUser(user);
await use(user);
// π§Ή Cleanup
await cleanupTestUser(user.id);
},
// π Pre-authenticated page
authenticatedPage: async ({ page, testUser }, use) => {
// π― Login process
await page.goto('/login');
await page.fill('[data-testid=email]', testUser.email);
await page.fill('[data-testid=password]', testUser.password);
await page.click('[data-testid=login-button]');
// β
Wait for successful login
await page.waitForURL('**/dashboard');
await page.waitForSelector('[data-testid=user-menu]');
await use(page);
},
// π Test products fixture
testProducts: async ({}, use) => {
const products: TestProduct[] = [
{
id: 'typescript-mug',
name: 'TypeScript Coffee Mug β',
price: 19.99,
category: 'drinkware',
emoji: 'β',
inStock: true
},
{
id: 'playwright-shirt',
name: 'Playwright T-Shirt π',
price: 24.99,
category: 'clothing',
emoji: 'π',
inStock: true
}
];
// π± Seed products
await seedTestProducts(products);
await use(products);
// π§Ή Cleanup
await cleanupTestProducts();
}
});
// π§ Helper functions
async function setupTestUser(user: TestUser): Promise<void> {
// API call to create test user
console.log(`π± Setting up test user: ${user.name}`);
}
async function cleanupTestUser(userId: string): Promise<void> {
// API call to cleanup test user
console.log(`π§Ή Cleaning up test user: ${userId}`);
}
π‘ Practical Playwright Test Examples
π Example 1: Advanced E-commerce Testing
Letβs test a sophisticated shopping experience:
// π§ͺ tests/ecommerce/advanced-shopping.spec.ts
import { test, expect } from '../fixtures/test-fixtures';
import { ShoppingCartPage } from '../pages/ShoppingCartPage';
import { CheckoutPage } from '../pages/CheckoutPage';
test.describe('π Advanced E-commerce Features', () => {
test('should handle complex shopping workflow across browsers', async ({
authenticatedPage: page,
testProducts
}) => {
const cartPage = new ShoppingCartPage(page);
const checkoutPage = new CheckoutPage(page);
// ποΈ Start shopping journey
await page.goto('/products');
// π Search with autocomplete
const searchBox = page.locator('[data-testid=search-input]');
await searchBox.fill('TypeScript');
// β³ Wait for autocomplete suggestions
await page.waitForSelector('[data-testid=search-suggestions]');
// π Click on suggestion
await page.click('[data-testid=suggestion-typescript-mug]');
// π Verify product page
await expect(page.locator('[data-testid=product-title]')).toContainText('TypeScript Coffee Mug');
// π¨ Test product image gallery
const imageGallery = page.locator('[data-testid=product-images]');
await expect(imageGallery.locator('img')).toHaveCount(4);
// π Click through images
for (let i = 1; i <= 3; i++) {
await page.click(`[data-testid=image-thumbnail-${i}]`);
await expect(page.locator('[data-testid=main-image]')).toHaveAttribute(
'src',
new RegExp(`image-${i}`)
);
}
// π Test quantity selector
const quantitySelector = page.locator('[data-testid=quantity-select]');
await quantitySelector.selectOption('3');
// β¨ Test size/color options
await page.click('[data-testid=size-large]');
await page.click('[data-testid=color-blue]');
// π Add to cart with options
await page.click('[data-testid=add-to-cart]');
// β
Verify cart notification
const cartNotification = page.locator('[data-testid=cart-notification]');
await expect(cartNotification).toBeVisible();
await expect(cartNotification).toContainText('3 items added to cart! π');
// π View cart
await page.click('[data-testid=cart-icon]');
await cartPage.verifyCartItems();
// π Apply coupon code
await cartPage.applyCoupon('PLAYWRIGHT20');
await expect(page.locator('[data-testid=discount-applied]')).toContainText('20% off applied! π');
// π Proceed to checkout
await cartPage.proceedToCheckout();
// π¦ Fill shipping information with autocomplete
await checkoutPage.fillShippingInfo({
address: '123 Playwright Street',
city: 'Test City',
zipCode: '12345',
country: 'United States πΊπΈ'
});
// π Select shipping method
await checkoutPage.selectShippingMethod('express');
// π³ Fill payment information
await checkoutPage.fillPaymentInfo({
cardNumber: '4242424242424242',
expiryDate: '12/25',
cvc: '123',
name: 'Playwright Tester'
});
// π Enable save payment method
await page.check('[data-testid=save-payment-method]');
// π Review order summary
await expect(page.locator('[data-testid=order-total]')).toContainText('$63.97'); // After discount
// π― Place order
await checkoutPage.placeOrder();
// β
Verify order confirmation
await expect(page.locator('[data-testid=order-confirmation]')).toBeVisible();
await expect(page.locator('[data-testid=order-number]')).toHaveText(/ORD-\d+/);
// π§ Verify email confirmation notice
await expect(page.locator('[data-testid=email-notice]')).toContainText(
'Confirmation email sent to [email protected]'
);
console.log('π Advanced shopping workflow completed!');
});
test('should handle wishlist and comparison features', async ({
authenticatedPage: page,
testProducts
}) => {
await page.goto('/products');
// β€οΈ Add items to wishlist
await page.hover('[data-testid=product-typescript-mug]');
await page.click('[data-testid=wishlist-btn-typescript-mug]');
await page.hover('[data-testid=product-playwright-shirt]');
await page.click('[data-testid=wishlist-btn-playwright-shirt]');
// β
Verify wishlist counter
await expect(page.locator('[data-testid=wishlist-count]')).toHaveText('2');
// π View wishlist
await page.click('[data-testid=wishlist-icon]');
await expect(page.locator('[data-testid=wishlist-item]')).toHaveCount(2);
// π Add to comparison
await page.check('[data-testid=compare-typescript-mug]');
await page.check('[data-testid=compare-playwright-shirt]');
// π View comparison
await page.click('[data-testid=compare-button]');
// β
Verify comparison table
const comparisonTable = page.locator('[data-testid=comparison-table]');
await expect(comparisonTable.locator('[data-testid=product-row]')).toHaveCount(2);
await expect(comparisonTable.locator('[data-testid=price-column]')).toHaveCount(2);
console.log('β€οΈ Wishlist and comparison features working!');
});
});
π± Example 2: Mobile-First Testing
Test mobile-specific features:
// π§ͺ tests/mobile/mobile-experience.spec.ts
import { test, expect, devices } from '@playwright/test';
// π± Mobile-specific test configuration
test.use({ ...devices['iPhone 12 Pro'] });
test.describe('π± Mobile User Experience', () => {
test('should have touch-friendly navigation', async ({ page }) => {
await page.goto('/');
// π Test hamburger menu
const hamburgerMenu = page.locator('[data-testid=hamburger-menu]');
await expect(hamburgerMenu).toBeVisible();
// π Verify touch target size (minimum 44px)
const buttonSize = await hamburgerMenu.boundingBox();
expect(buttonSize?.height).toBeGreaterThanOrEqual(44);
expect(buttonSize?.width).toBeGreaterThanOrEqual(44);
// π Test menu interaction
await hamburgerMenu.tap();
// π― Verify mobile menu appears
const mobileMenu = page.locator('[data-testid=mobile-menu]');
await expect(mobileMenu).toBeVisible();
// π Test menu items
const menuItems = mobileMenu.locator('[data-testid=menu-item]');
await expect(menuItems).toHaveCount(5);
// π Test navigation
await menuItems.nth(1).tap();
await expect(page).toHaveURL(/.*\/products/);
console.log('π± Mobile navigation test passed!');
});
test('should handle mobile gestures and interactions', async ({ page }) => {
await page.goto('/products');
// π Test pull-to-refresh
await page.touchscreen.swipe(640, 100, 640, 400, 10);
// β³ Wait for refresh animation
await page.waitForSelector('[data-testid=refresh-indicator]');
await page.waitForSelector('[data-testid=refresh-indicator]', { state: 'hidden' });
// π Test product card interactions
const productCard = page.locator('[data-testid=product-card]').first();
// π― Long press for quick actions
await productCard.hover();
await page.mouse.down();
await page.waitForTimeout(1000); // Long press
await page.mouse.up();
// β
Verify quick actions menu
await expect(page.locator('[data-testid=quick-actions]')).toBeVisible();
// β€οΈ Quick add to wishlist
await page.tap('[data-testid=quick-wishlist]');
await expect(page.locator('[data-testid=wishlist-success]')).toBeVisible();
console.log('π Mobile gestures test passed!');
});
test('should handle mobile forms and keyboards', async ({ page }) => {
await page.goto('/contact');
// π Test form with mobile keyboard
const emailInput = page.locator('[data-testid=email-input]');
await emailInput.tap();
// β¨οΈ Verify email keyboard appears (input type=email)
await expect(emailInput).toHaveAttribute('type', 'email');
// π± Fill form with mobile-friendly inputs
await emailInput.fill('[email protected]');
const phoneInput = page.locator('[data-testid=phone-input]');
await phoneInput.tap();
await expect(phoneInput).toHaveAttribute('type', 'tel');
await phoneInput.fill('+1-555-123-4567');
// π¬ Test textarea on mobile
const messageInput = page.locator('[data-testid=message-input]');
await messageInput.tap();
await messageInput.fill('Testing mobile form experience! π±');
// π― Submit form
await page.tap('[data-testid=submit-button]');
// β
Verify success message
await expect(page.locator('[data-testid=form-success]')).toBeVisible();
console.log('π Mobile form test passed!');
});
});
π Example 3: Cross-Browser API Testing
Test API interactions across browsers:
// π§ͺ tests/api/cross-browser-api.spec.ts
import { test, expect } from '@playwright/test';
test.describe('π Cross-Browser API Integration', () => {
test('should handle GraphQL queries consistently', async ({ page, browserName }) => {
console.log(`π Testing GraphQL on ${browserName}`);
// π Intercept GraphQL requests
await page.route('**/graphql', async (route) => {
const request = route.request();
const postData = request.postDataJSON();
// π Log the query for debugging
console.log(`π GraphQL Query on ${browserName}:`, postData.query);
// π Mock different responses based on browser
if (postData.query.includes('getProducts')) {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
data: {
products: [
{
id: 'test-product',
name: `${browserName} Test Product π§ͺ`,
price: 29.99,
emoji: 'π§ͺ'
}
]
}
})
});
} else {
await route.continue();
}
});
await page.goto('/products');
// β
Verify product loaded with browser-specific data
await expect(page.locator('[data-testid=product-name]')).toContainText(
`${browserName} Test Product`
);
console.log(`β
GraphQL test passed on ${browserName}!`);
});
test('should handle WebSocket connections', async ({ page, browserName }) => {
console.log(`π Testing WebSocket on ${browserName}`);
// π― Track WebSocket messages
const wsMessages: string[] = [];
page.on('websocket', ws => {
console.log(`π WebSocket opened on ${browserName}: ${ws.url()}`);
ws.on('framereceived', event => {
wsMessages.push(event.payload as string);
console.log(`π¨ Received: ${event.payload}`);
});
ws.on('framesent', event => {
console.log(`π€ Sent: ${event.payload}`);
});
});
await page.goto('/chat');
// π¬ Send a message through WebSocket
await page.fill('[data-testid=message-input]', 'Hello from Playwright! π');
await page.click('[data-testid=send-button]');
// β³ Wait for WebSocket response
await page.waitForFunction(() =>
document.querySelector('[data-testid=message-list]')?.children.length ?? 0 > 0
);
// β
Verify message appears
await expect(page.locator('[data-testid=message-list] .message')).toHaveCount(1);
await expect(page.locator('[data-testid=message-list] .message')).toContainText(
'Hello from Playwright!'
);
// π Verify WebSocket communication
expect(wsMessages.length).toBeGreaterThan(0);
console.log(`π WebSocket test passed on ${browserName}!`);
});
test('should handle file uploads with different MIME types', async ({ page, browserName }) => {
console.log(`π Testing file upload on ${browserName}`);
await page.goto('/upload');
// πΈ Test image upload
const fileInput = page.locator('[data-testid=file-input]');
await fileInput.setInputFiles({
name: 'test-image.png',
mimeType: 'image/png',
buffer: Buffer.from('fake-image-data')
});
// β
Verify file preview
await expect(page.locator('[data-testid=file-preview]')).toBeVisible();
await expect(page.locator('[data-testid=file-name]')).toContainText('test-image.png');
// π Upload file
await page.click('[data-testid=upload-button]');
// β³ Wait for upload progress
await expect(page.locator('[data-testid=upload-progress]')).toBeVisible();
await expect(page.locator('[data-testid=upload-success]')).toBeVisible();
console.log(`π File upload test passed on ${browserName}!`);
});
});
π Advanced Playwright Features
π§ββοΈ Visual Testing and Screenshots
Implement visual regression testing:
// π¨ Visual testing configuration
import { test, expect } from '@playwright/test';
test.describe('π¨ Visual Regression Testing', () => {
test('should match homepage design across browsers', async ({ page }) => {
await page.goto('/');
// πΈ Full page screenshot
await expect(page).toHaveScreenshot('homepage-full.png');
// π― Component-specific screenshots
const header = page.locator('[data-testid=header]');
await expect(header).toHaveScreenshot('header-component.png');
const productGrid = page.locator('[data-testid=product-grid]');
await expect(productGrid).toHaveScreenshot('product-grid.png');
console.log('πΈ Visual regression tests passed!');
});
test('should handle different viewport sizes', async ({ page }) => {
const viewports = [
{ width: 1920, height: 1080, name: 'desktop' },
{ width: 768, height: 1024, name: 'tablet' },
{ width: 375, height: 667, name: 'mobile' }
];
for (const viewport of viewports) {
await page.setViewportSize(viewport);
await page.goto('/');
// π± Viewport-specific screenshots
await expect(page).toHaveScreenshot(`homepage-${viewport.name}.png`);
}
console.log('π± Responsive design tests passed!');
});
});
π Parallel Testing and Performance
Optimize test execution:
// β‘ Performance testing utilities
import { test, expect } from '@playwright/test';
test.describe.configure({ mode: 'parallel' });
test.describe('β‘ Performance Testing', () => {
test('should load pages within performance budgets', async ({ page }) => {
// π Start performance monitoring
await page.goto('/', { waitUntil: 'networkidle' });
// β±οΈ Measure Core Web Vitals
const webVitals = await page.evaluate(() => {
return new Promise(resolve => {
new PerformanceObserver(list => {
const entries = list.getEntries();
const vitals = {
lcp: 0, // Largest Contentful Paint
fid: 0, // First Input Delay
cls: 0 // Cumulative Layout Shift
};
entries.forEach(entry => {
if (entry.entryType === 'largest-contentful-paint') {
vitals.lcp = entry.startTime;
}
if (entry.entryType === 'first-input') {
vitals.fid = entry.processingStart - entry.startTime;
}
if (entry.entryType === 'layout-shift') {
vitals.cls += (entry as any).value;
}
});
resolve(vitals);
}).observe({ entryTypes: ['largest-contentful-paint', 'first-input', 'layout-shift'] });
});
});
// β
Assert performance budgets
expect((webVitals as any).lcp).toBeLessThan(2500); // 2.5s
expect((webVitals as any).cls).toBeLessThan(0.1); // 0.1
console.log('β‘ Performance tests passed!', webVitals);
});
test('should handle concurrent user load', async ({ browser }) => {
// π₯ Create multiple browser contexts
const contexts = await Promise.all(
Array.from({ length: 10 }, () => browser.newContext())
);
const pages = await Promise.all(
contexts.map(context => context.newPage())
);
// π Simulate concurrent users
const startTime = Date.now();
await Promise.all(
pages.map((page, index) =>
page.goto('/', {
waitUntil: 'networkidle',
timeout: 30000
}).then(() => {
console.log(`π€ User ${index + 1} loaded page`);
})
)
);
const endTime = Date.now();
const totalTime = endTime - startTime;
// β
Verify all pages loaded within reasonable time
expect(totalTime).toBeLessThan(15000); // 15 seconds for 10 concurrent users
// π§Ή Cleanup
await Promise.all(contexts.map(context => context.close()));
console.log(`π₯ Concurrent load test passed! Total time: ${totalTime}ms`);
});
});
β οΈ Common Playwright Pitfalls
π± Pitfall 1: Not Waiting Properly
// β Wrong - race conditions and flaky tests!
test('flaky test example', async ({ page }) => {
await page.goto('/dashboard');
await page.click('[data-testid=load-data-btn]');
// π₯ This might fail if data hasn't loaded yet!
await expect(page.locator('[data-testid=data-table]')).toBeVisible();
});
// β
Correct - proper waiting strategies!
test('stable test example', async ({ page }) => {
await page.goto('/dashboard');
await page.click('[data-testid=load-data-btn]');
// β³ Wait for loading to complete
await page.waitForSelector('[data-testid=loading-spinner]', { state: 'hidden' });
// β
Or use built-in auto-waiting
await expect(page.locator('[data-testid=data-table]')).toBeVisible();
// π― Or wait for specific content
await expect(page.locator('[data-testid=data-table] tbody tr')).toHaveCount.toBeGreaterThan(0);
});
π€― Pitfall 2: Browser Context Pollution
// β Wrong - shared state between tests!
test.describe.configure({ mode: 'serial' }); // Wrong approach
test('first test modifies state', async ({ page }) => {
await page.goto('/login');
// This test sets cookies/localStorage
});
test('second test assumes clean state', async ({ page }) => {
await page.goto('/');
// π₯ Might fail due to leftover state!
});
// β
Correct - isolated test contexts!
test.describe('Isolated Tests', () => {
test('each test gets fresh context', async ({ page }) => {
// Each test gets a fresh browser context automatically
await page.goto('/');
// Test runs with clean state
});
test('another independent test', async ({ page }) => {
// Fresh context again!
await page.goto('/login');
});
});
π οΈ Playwright Best Practices
- π― Use Data Attributes: Rely on
data-testid
for stable selectors - β‘ Leverage Auto-Waiting: Trust Playwrightβs built-in waiting mechanisms
- π§Ή Clean Test Data: Use fixtures for consistent test state
- π± Test Cross-Browser: Donβt assume all browsers behave the same
- π¨ Visual Testing: Use screenshots for UI regression testing
- π Parallel Execution: Take advantage of Playwrightβs speed
- π Monitor Performance: Include performance assertions in tests
π§ͺ Hands-On Exercise
π― Challenge: Build a Modern Social Platform Test Suite
Create a comprehensive Playwright test suite for a social media platform:
π Requirements:
- β Cross-browser testing (Chrome, Firefox, Safari)
- π± Mobile and desktop responsive testing
- π Authentication flows with social login
- π Content creation (text, images, videos)
- π¬ Real-time messaging and notifications
- π Dark/light theme switching
- π Multi-language support testing
- π Analytics and performance monitoring
π Bonus Points:
- Visual regression testing
- Performance budget enforcement
- Accessibility testing integration
- Load testing simulation
π‘ Solution Starter
π Click to see solution starter
// π― Social platform test starter
import { test, expect, devices } from '@playwright/test';
// π Cross-browser configuration
const browsers = ['chromium', 'firefox', 'webkit'];
const devices_list = ['Desktop Chrome', 'iPhone 12', 'iPad Pro'];
test.describe('π Social Platform - Cross Browser', () => {
for (const browserName of browsers) {
test.describe(`${browserName} browser tests`, () => {
test.use({ browserName: browserName as any });
test('should handle complete social workflow', async ({ page }) => {
// π Authentication
await page.goto('/login');
await page.fill('[data-testid=email]', '[email protected]');
await page.fill('[data-testid=password]', 'SocialPass123!');
await page.click('[data-testid=login-btn]');
// β
Verify dashboard
await expect(page.locator('[data-testid=user-feed]')).toBeVisible();
// π Create post
await page.click('[data-testid=new-post-btn]');
await page.fill('[data-testid=post-content]', `Testing on ${browserName}! π`);
// πΈ Upload image
await page.setInputFiles('[data-testid=image-upload]', 'tests/fixtures/test-image.jpg');
// π― Publish post
await page.click('[data-testid=publish-btn]');
// β
Verify post appears
await expect(page.locator('[data-testid=post-feed] .post').first())
.toContainText(`Testing on ${browserName}!`);
console.log(`β
Social workflow test passed on ${browserName}!`);
});
});
}
// π± Device-specific tests
for (const deviceName of devices_list) {
test.describe(`${deviceName} device tests`, () => {
test.use({ ...devices[deviceName] });
test('should have responsive design', async ({ page }) => {
await page.goto('/');
// π± Test mobile navigation
if (deviceName.includes('iPhone') || deviceName.includes('iPad')) {
await expect(page.locator('[data-testid=mobile-nav]')).toBeVisible();
} else {
await expect(page.locator('[data-testid=desktop-nav]')).toBeVisible();
}
console.log(`π± Responsive test passed on ${deviceName}!`);
});
});
}
});
π Key Takeaways
Youβve mastered modern E2E testing with Playwright! Hereβs what you can now do:
- β Set up Playwright with TypeScript for comprehensive testing π§
- β Write cross-browser tests that work on all platforms π
- β Handle complex scenarios like file uploads and real-time features π±
- β Implement visual regression testing for UI consistency π¨
- β Optimize test performance with parallel execution π
- β Apply best practices for maintainable test suites π οΈ
Remember: Playwright gives you superpowers to test like never before - use them wisely! π
π€ Next Steps
Congratulations! π Youβve mastered modern E2E testing with Playwright and TypeScript!
Hereβs what to explore next:
- π» Practice with the social platform exercise above
- ποΈ Migrate existing tests from other frameworks to Playwright
- π Explore Playwrightβs API testing capabilities
- π Set up CI/CD pipelines with Playwright
- π¨ Dive deeper into visual testing and accessibility
Remember: With great testing power comes great responsibility. Build tests that give you confidence and make your users happy! π
Happy testing with Playwright! ππβ¨