Playwright E2E Testing Framework

Comprehensive Playwright testing examples including cross-browser automation, API testing, mobile testing, visual regression, and advanced E2E patterns for modern web applications

Key Facts

Category
Testing
Items
4
Format Families
text

Sample Overview

Comprehensive Playwright testing examples including cross-browser automation, API testing, mobile testing, visual regression, and advanced E2E patterns for modern web applications This sample set belongs to Testing and can be used to test related workflows inside Elysia Tools.

💻 Playwright Project Setup and Configuration text

🟢 simple ⭐⭐

Complete Playwright project setup with TypeScript configuration, test structure, fixtures, and best practices for reliable E2E testing

⏱️ 25 min 🏷️ playwright, setup, configuration, typescript, e2e testing
Prerequisites: TypeScript, JavaScript ES6+, Web development basics, npm/yarn
// Playwright Project Setup and Configuration

// 1. playwright.config.ts - Main configuration
import { defineConfig, devices } from '@playwright/test';
import path from 'path';

const playwrightMainConfig = defineConfig({
  // Test directory
  testDir: './tests',

  // Run tests in files in parallel
  fullyParallel: true,

  // Fail the build on CI if you accidentally left test.only in the source code
  forbidOnly: !!process.env.CI,

  // Retry on CI only
  retries: process.env.CI ? 2 : 0,

  // Opt out of parallel tests on CI
  workers: process.env.CI ? 1 : undefined,

  // Reporter to use
  reporter: [
    ['html'],
    ['json', { outputFile: 'test-results.json' }],
    ['junit', { outputFile: 'test-results.xml' }],
    ['allure-playwright']
  ],

  // Global setup and teardown
  globalSetup: require.resolve('./tests/global-setup.ts'),
  globalTeardown: require.resolve('./tests/global-teardown.ts'),

  // Use shared folder for test assets
  use: {
    // Base URL to use in actions like `await page.goto('/')`
    baseURL: process.env.BASE_URL || 'http://localhost:3000',

    // Collect trace when retrying the failed test
    trace: 'on-first-retry',

    // Record video only when retrying a test for the first time
    video: 'retain-on-failure',

    // Take screenshot on failure
    screenshot: 'only-on-failure',

    // Global timeout for each action
    actionTimeout: 10000,

    // Global timeout for navigation
    navigationTimeout: 30000,

    // Viewport size
    viewport: { width: 1280, height: 720 },

    // Ignore HTTPS errors
    ignoreHTTPSErrors: true,

    // User agent
    userAgent: 'Playwright Test Bot'
  },

  // Configure projects for major browsers
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },

    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },

    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },

    // Test against mobile viewports
    {
      name: 'Mobile Chrome',
      use: { ...devices['Pixel 5'] },
    },
    {
      name: 'Mobile Safari',
      use: { ...devices['iPhone 12'] },
    },

    // Test against branded browsers
    {
      name: 'Microsoft Edge',
      use: { ...devices['Desktop Edge'], channel: 'msedge' },
    },
    {
      name: 'Google Chrome',
      use: { ...devices['Desktop Chrome'], channel: 'chrome' },
    },
  ],

  // Run your local dev server before starting the tests
  webServer: {
    command: 'npm run start',
    url: 'http://localhost:3000',
    reuseExistingServer: !process.env.CI,
    timeout: 120 * 1000,
  },

  // Global test timeout
  timeout: 30000,

  // Expect timeout
  expect: {
    timeout: 5000
  }
});

// 2. package.json dependencies
{
  "devDependencies": {
    "@playwright/test": "^1.40.0",
    "@types/node": "^20.8.0",
    "typescript": "^5.2.0",
    "allure-playwright": "^2.1.0",
    "@playwright/test-expect": "^1.40.0",
    "playwright-lighthouse": "^2.2.0",
    "axe-playwright": "^2.0.0"
  },
  "scripts": {
    "test": "playwright test",
    "test:ui": "playwright test --ui",
    "test:debug": "playwright test --debug",
    "test:codegen": "playwright codegen",
    "test:report": "playwright show-report",
    "test:install": "playwright install",
    "test:headed": "playwright test --headed",
    "test:project": "playwright test --project=chromium",
    "test:mobile": "playwright test --project="Mobile Chrome"",
    "test:api": "playwright test --config=playwright.api.config.ts"
  }
}

// 3. tests/fixtures/base.fixture.ts - Base test fixture
import { test as base, expect } from '@playwright/test';
import { HomePage } from '../pages/HomePage';
import { LoginPage } from '../pages/LoginPage';
import { ApiHelper } from '../utils/ApiHelper';

// Define custom fixture types
type TestFixtures = {
  homePage: HomePage;
  loginPage: LoginPage;
  apiHelper: ApiHelper;
  authenticatedPage: {
    page: Page;
    token: string;
  };
};

// Extend base test with custom fixtures
export const test = base.extend<TestFixtures>({
  // Custom home page fixture
  homePage: async ({ page }, use) => {
    const homePage = new HomePage(page);
    await use(homePage);
  },

  // Custom login page fixture
  loginPage: async ({ page }, use) => {
    const loginPage = new LoginPage(page);
    await use(loginPage);
  },

  // API helper fixture
  apiHelper: async ({ request }, use) => {
    const apiHelper = new ApiHelper(request);
    await use(apiHelper);
  },

  // Authenticated page fixture
  authenticatedPage: async ({ page, apiHelper }, use) => {
    // Login via API and set auth token
    const loginResponse = await apiHelper.login('testuser', 'testpass');
    const token = loginResponse.token;

    // Set token in localStorage
    await page.goto('/');
    await page.evaluate(([authToken]) => {
      localStorage.setItem('authToken', authToken);
    }, [token]);

    await use({ page, token });

    // Cleanup: remove auth token
    await page.evaluate(() => {
      localStorage.removeItem('authToken');
    });
  }
});

// Export expect for use in tests
export { expect };

// Export test types
export type { TestFixtures };

// 4. tests/global-setup.ts - Global test setup
import { chromium, FullConfig } from '@playwright/test';
import { ApiHelper } from './utils/ApiHelper';

async function globalSetup(config: FullConfig) {
  console.log('🚀 Starting global setup...');

  const browser = await chromium.launch();
  const context = await browser.newContext();
  const apiHelper = new ApiHelper(context.request);

  try {
    // Setup test data
    console.log('📊 Setting up test data...');
    await apiHelper.setupTestData();

    // Create test users
    console.log('👤 Creating test users...');
    await apiHelper.createUser({
      username: 'testuser',
      email: '[email protected]',
      password: 'testpass',
      role: 'user'
    });

    await apiHelper.createUser({
      username: 'admin',
      email: '[email protected]',
      password: 'adminpass',
      role: 'admin'
    });

    // Setup test database state
    console.log('🗄️ Setting up database...');
    await apiHelper.resetDatabase();

    console.log('✅ Global setup completed successfully');
  } catch (error) {
    console.error('❌ Global setup failed:', error);
    throw error;
  } finally {
    await context.close();
    await browser.close();
  }
}

export { globalSetup };

// 5. tests/global-teardown.ts - Global test cleanup
import { chromium, FullConfig } from '@playwright/test';
import { ApiHelper } from './utils/ApiHelper';

async function globalTeardown(config: FullConfig) {
  console.log('🧹 Starting global teardown...');

  const browser = await chromium.launch();
  const context = await browser.newContext();
  const apiHelper = new ApiHelper(context.request);

  try {
    // Cleanup test data
    console.log('🗑️ Cleaning up test data...');
    await apiHelper.cleanupTestData();

    // Remove test users
    console.log('👥 Removing test users...');
    await apiHelper.deleteUser('testuser');
    await apiHelper.deleteUser('admin');

    // Close database connections
    console.log('🔌 Closing database connections...');
    await apiHelper.closeDatabase();

    console.log('✅ Global teardown completed successfully');
  } catch (error) {
    console.error('❌ Global teardown failed:', error);
    throw error;
  } finally {
    await context.close();
    await browser.close();
  }
}

export { globalTeardown };

// 6. Basic test structure
// tests/basic/homepage.spec.ts
import { test, expect } from '../fixtures/base.fixture';

test.describe('Homepage', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/');
  });

  test('should display correct title', async ({ page }) => {
    await expect(page).toHaveTitle(/Playwright Testing Demo/);
  });

  test('should display main navigation', async ({ page }) => {
    const navigation = page.locator('[data-testid=main-navigation]');
    await expect(navigation).toBeVisible();

    // Check navigation links
    await expect(page.getByRole('link', { name: 'Home' })).toBeVisible();
    await expect(page.getByRole('link', { name: 'About' })).toBeVisible();
    await expect(page.getByRole('link', { name: 'Contact' })).toBeVisible();
  });

  test('should display hero section', async ({ page }) => {
    const heroSection = page.locator('[data-testid=hero-section]');
    await expect(heroSection).toBeVisible();

    await expect(heroSection.getByRole('heading', { level: 1 }))
      .toContainText('Welcome to Playwright Testing');

    await expect(page.getByRole('button', { name: 'Get Started' }))
      .toBeVisible();
  });

  test('should be responsive on different viewports', async ({ page }) => {
    // Test mobile viewport
    await page.setViewportSize({ width: 375, height: 667 });
    await expect(page.locator('[data-testid=mobile-menu]')).toBeVisible();

    // Test tablet viewport
    await page.setViewportSize({ width: 768, height: 1024 });
    await expect(page.locator('[data-testid=tablet-layout]')).toBeVisible();

    // Test desktop viewport
    await page.setViewportSize({ width: 1280, height: 720 });
    await expect(page.locator('[data-testid=desktop-layout]')).toBeVisible();
  });
});

// 7. Page Object Model example
// tests/pages/BasePage.ts
import { Page, Locator } from '@playwright/test';

export class BasePage {
  readonly page: Page;

  constructor(page: Page) {
    this.page = page;
  }

  // Common locators
  readonly header = this.page.locator('[data-testid=header]');
  readonly footer = this.page.locator('[data-testid=footer]');
  readonly navigation = this.page.locator('[data-testid=main-navigation]');
  readonly loadingSpinner = this.page.locator('[data-testid=loading-spinner]');

  // Common methods
  async navigateTo(url: string) {
    await this.page.goto(url);
  }

  async waitForPageLoad() {
    await this.page.waitForLoadState('networkidle');
  }

  async waitForElementToDisappear(locator: Locator, timeout = 5000) {
    await expect(locator).not.toBeVisible({ timeout });
  }

  async takeScreenshot(name: string) {
    await this.page.screenshot({ path: `screenshots/${name}.png` });
  }

  async scrollToElement(locator: Locator) {
    await locator.scrollIntoViewIfNeeded();
  }

  async waitForApiCall(apiPattern: string, timeout = 30000) {
    return await this.page.waitForResponse(response =>
      response.url().includes(apiPattern),
      { timeout }
    );
  }

  // Helper method to wait for animations
  async waitForAnimations() {
    await this.page.waitForFunction(() => {
      return document.getAnimations().length === 0;
    });
  }

  // Method to check accessibility
  async checkAccessibility() {
    // This would integrate with axe-core or similar
    console.log('Checking accessibility...');
  }
}

// tests/pages/HomePage.ts
import { Page, Locator } from '@playwright/test';
import { BasePage } from './BasePage';

export class HomePage extends BasePage {
  // Locators
  readonly heroTitle = this.page.locator('[data-testid=hero-title]');
  readonly heroSubtitle = this.page.locator('[data-testid=hero-subtitle]');
  readonly getStartedButton = this.page.getByRole('button', { name: 'Get Started' });
  readonly featuresSection = this.page.locator('[data-testid=features-section]');
  readonly testimonialSection = this.page.locator('[data-testid=testimonial-section]');

  // Feature cards
  readonly featureCards = this.page.locator('[data-testid=feature-card]');
  readonly firstFeatureCard = this.featureCards.first();

  constructor(page: Page) {
    super(page);
  }

  async visit() {
    await this.navigateTo('/');
    await this.waitForPageLoad();
  }

  async getStarted() {
    await this.getStartedButton.click();
  }

  async verifyHeroSection() {
    await expect(this.heroTitle).toBeVisible();
    await expect(this.heroTitle).toContainText('Welcome');
    await expect(this.heroSubtitle).toBeVisible();
    await expect(this.getStartedButton).toBeVisible();
  }

  async verifyFeaturesSection() {
    await expect(this.featuresSection).toBeVisible();
    await expect(this.featureCards).toHaveCount(3);
  }

  async verifyTestimonialSection() {
    await expect(this.testimonialSection).toBeVisible();
  }

  async navigateToFeature(featureName: string) {
    const featureCard = this.featureCards.filter({ hasText: featureName }).first();
    await featureCard.click();
  }
}

// tests/pages/LoginPage.ts
import { Page, Locator } from '@playwright/test';
import { BasePage } from './BasePage';

export class LoginPage extends BasePage {
  // Locators
  readonly usernameInput = this.page.locator('[data-testid=username-input]');
  readonly passwordInput = this.page.locator('[data-testid=password-input]');
  readonly loginButton = this.page.getByRole('button', { name: 'Login' });
  readonly errorMessage = this.page.locator('[data-testid=error-message]');
  readonly forgotPasswordLink = this.page.getByRole('link', { name: 'Forgot Password?' });
  readonly rememberMeCheckbox = this.page.locator('[data-testid=remember-me]');

  constructor(page: Page) {
    super(page);
  }

  async visit() {
    await this.navigateTo('/login');
    await this.waitForPageLoad();
  }

  async login(username: string, password: string, rememberMe = false) {
    await this.usernameInput.fill(username);
    await this.passwordInput.fill(password);

    if (rememberMe) {
      await this.rememberMeCheckbox.check();
    }

    await this.loginButton.click();
  }

  async verifyLoginPage() {
    await expect(this.usernameInput).toBeVisible();
    await expect(this.passwordInput).toBeVisible();
    await expect(this.loginButton).toBeVisible();
    await expect(this.forgotPasswordLink).toBeVisible();
    await expect(this.rememberMeCheckbox).toBeVisible();
  }

  async getErrorMessage(): Promise<string> {
    await expect(this.errorMessage).toBeVisible();
    return await this.errorMessage.textContent() || '';
  }

  async verifySuccessfulLogin() {
    // Check that we've redirected away from login page
    await expect(this.page).not.toHaveURL('/login');

    // Check for user menu or other logged-in indicators
    const userMenu = this.page.locator('[data-testid=user-menu]');
    await expect(userMenu).toBeVisible();
  }

  async forgotPassword() {
    await this.forgotPasswordLink.click();
  }
}

// 8. Using Page Objects in tests
// tests/authentication/login.spec.ts
import { test, expect } from '../fixtures/base.fixture';

test.describe('Authentication', () => {
  test('should login successfully with valid credentials', async ({ loginPage }) => {
    await loginPage.visit();
    await loginPage.verifyLoginPage();

    await loginPage.login('testuser', 'testpass');
    await loginPage.verifySuccessfulLogin();
  });

  test('should show error with invalid credentials', async ({ loginPage }) => {
    await loginPage.visit();
    await loginPage.verifyLoginPage();

    await loginPage.login('invaliduser', 'invalidpass');

    const errorMessage = await loginPage.getErrorMessage();
    expect(errorMessage).toContain('Invalid credentials');
  });

  test('should remember user when checkbox is checked', async ({ loginPage, page }) => {
    await loginPage.visit();
    await loginPage.login('testuser', 'testpass', true);
    await loginPage.verifySuccessfulLogin();

    // Check that remember me cookie is set
    const cookies = await page.context().cookies();
    const rememberMeCookie = cookies.find(cookie => cookie.name === 'remember_me');
    expect(rememberMeCookie).toBeDefined();
  });
});

// 9. Test data management
// tests/fixtures/testData.json
{
  "users": {
    "validUser": {
      "username": "testuser",
      "password": "testpass",
      "email": "[email protected]"
    },
    "invalidUser": {
      "username": "invaliduser",
      "password": "wrongpass"
    },
    "adminUser": {
      "username": "admin",
      "password": "adminpass",
      "role": "administrator"
    }
  },
  "products": [
    {
      "id": 1,
      "name": "Test Product 1",
      "price": 99.99,
      "category": "Electronics"
    },
    {
      "id": 2,
      "name": "Test Product 2",
      "price": 49.99,
      "category": "Books"
    }
  ],
  "testUrls": {
    "homepage": "/",
    "login": "/login",
    "dashboard": "/dashboard",
    "products": "/products"
  }
}

// tests/utils/TestDataLoader.ts
import testData from '../fixtures/testData.json';

export class TestDataLoader {
  static getUser(userType: keyof typeof testData.users) {
    return testData.users[userType];
  }

  static getProduct(productId: number) {
    return testData.products.find(p => p.id === productId);
  }

  static getAllProducts() {
    return testData.products;
  }

  static getUrl(urlName: keyof typeof testData.testUrls) {
    return testData.testUrls[urlName];
  }
}

// 10. Using test data in tests
// tests/product-management/products.spec.ts
import { test, expect } from '../fixtures/base.fixture';
import { TestDataLoader } from '../utils/TestDataLoader';

test.describe('Product Management', () => {
  test('should display product list', async ({ page }) => {
    await page.goto(TestDataLoader.getUrl('products'));

    const products = TestDataLoader.getAllProducts();

    for (const product of products) {
      await expect(page.locator(`[data-testid=product-${product.id}]`))
        .toContainText(product.name);

      await expect(page.locator(`[data-testid=price-${product.id}]`))
        .toContainText(`$${product.price}`);
    }
  });

  test('should search for products', async ({ page }) => {
    await page.goto(TestDataLoader.getUrl('products'));

    const searchInput = page.locator('[data-testid=search-input]');
    const searchButton = page.getByRole('button', { name: 'Search' });

    const product = TestDataLoader.getProduct(1);

    await searchInput.fill(product.name);
    await searchButton.click();

    await expect(page.locator('[data-testid=product-1]'))
      .toContainText(product.name);

    await expect(page.locator('[data-testid=product-2]'))
      .not.toBeVisible();
  });
});

💻 Cross-Browser and Cross-Platform Testing text

🟡 intermediate ⭐⭐⭐

Comprehensive cross-browser testing examples including mobile testing, responsive design verification, and platform-specific testing strategies

⏱️ 30 min 🏷️ playwright, cross-browser, mobile, responsive, accessibility
Prerequisites: Playwright basics, Responsive design, Mobile testing concepts, Web accessibility
// Cross-Browser and Cross-Platform Testing with Playwright

// 1. Cross-browser test configuration
// playwright.config.ts - Cross-browser projects
import { defineConfig, devices } from '@playwright/test';

const playwrightApiConfig = defineConfig({
  projects: [
    // Desktop browsers
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
      testIgnore: '**/mobile-only.spec.ts',
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
      testIgnore: '**/mobile-only.spec.ts',
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
      testIgnore: '**/mobile-only.spec.ts',
    },
    {
      name: 'edge',
      use: { ...devices['Desktop Edge'], channel: 'msedge' },
      testIgnore: '**/mobile-only.spec.ts',
    },

    // Mobile browsers
    {
      name: 'mobile-chrome',
      use: { ...devices['Pixel 5'] },
      testMatch: '**/mobile-only.spec.ts',
    },
    {
      name: 'mobile-safari',
      use: { ...devices['iPhone 12'] },
      testMatch: '**/mobile-only.spec.ts',
    },

    // Tablet browsers
    {
      name: 'tablet-chrome',
      use: { ...devices['iPad Pro'] },
    },

    // Custom viewport testing
    {
      name: 'custom-viewport',
      use: {
        ...devices['Desktop Chrome'],
        viewport: { width: 800, height: 600 }
      },
    }
  ]
});

// 2. Cross-browser compatibility tests
// tests/cross-browser/browser-compatibility.spec.ts
import { test, expect } from '../fixtures/base.fixture';

test.describe.configure({ mode: 'parallel' });

test.describe('Cross-Browser Compatibility', () => {
  test('should render consistently across all browsers', async ({ page }) => {
    await page.goto('/');

    // Basic layout checks
    await expect(page.locator('h1')).toBeVisible();
    await expect(page.locator('nav')).toBeVisible();
    await expect(page.locator('main')).toBeVisible();
    await expect(page.locator('footer')).toBeVisible();
  });

  test('should handle CSS Grid consistently', async ({ page }) => {
    await page.goto('/grid-layout');

    // Test grid container
    const gridContainer = page.locator('[data-testid=grid-container]');
    await expect(gridContainer).toBeVisible();

    // Test grid items
    const gridItems = gridContainer.locator('[data-testid=grid-item]');
    await expect(gridItems).toHaveCount(6);

    // Verify grid layout with bounding box checks
    const firstItem = gridItems.first();
    const firstItemBox = await firstItem.boundingBox();
    expect(firstItemBox).toBeTruthy();

    const lastItem = gridItems.last();
    const lastItemBox = await lastItem.boundingBox();
    expect(lastItemBox).toBeTruthy();

    // Verify items don't overlap
    expect(lastItemBox!.y).toBeGreaterThan(firstItemBox!.y);
  });

  test('should handle Flexbox consistently', async ({ page }) => {
    await page.goto('/flexbox-layout');

    const flexContainer = page.locator('[data-testid=flex-container]');
    await expect(flexContainer).toBeVisible();

    // Test flex items alignment
    const flexItems = flexContainer.locator('[data-testid=flex-item]');
    await expect(flexItems).toHaveCount(4);

    // Check vertical alignment
    const firstItemBox = await flexItems.first().boundingBox();
    const lastItemBox = await flexItems.last().boundingBox();

    expect(Math.abs(firstItemBox!.y - lastItemBox!.y)).toBeLessThan(5);
  });

  test('should handle forms consistently', async ({ page }) => {
    await page.goto('/forms');

    // Test form elements
    await expect(page.locator('input[type="text"]')).toBeVisible();
    await expect(page.locator('input[type="email"]')).toBeVisible();
    await expect(page.locator('input[type="password"]')).toBeVisible();
    await expect(page.locator('select')).toBeVisible();
    await expect(page.locator('textarea')).toBeVisible();
    await expect(page.locator('input[type="checkbox"]')).toBeVisible();
    await expect(page.locator('input[type="radio"]')).toBeVisible();

    // Test form submission
    await page.fill('input[name="name"]', 'Test User');
    await page.fill('input[name="email"]', '[email protected]');
    await page.selectOption('select[name="country"]', 'US');

    // Checkbox interaction
    await page.check('input[type="checkbox"]');
    await expect(page.locator('input[type="checkbox"]')).toBeChecked();

    // Radio button interaction
    await page.check('input[type="radio"][value="option1"]');
    await expect(page.locator('input[type="radio"][value="option1"]')).toBeChecked();
  });

  test('should handle JavaScript features consistently', async ({ page }) => {
    await page.goto('/javascript-features');

    // Test event listeners
    const button = page.locator('[data-testid=click-button"]');
    await button.click();
    await expect(page.locator('[data-testid=click-count]')).toContainText('1');

    // Test async operations
    const asyncButton = page.locator('[data-testid=async-button]');
    await asyncButton.click();
    await expect(page.locator('[data-testid=async-result]')).toBeVisible({ timeout: 5000 });

    // Test dynamic content
    const dynamicButton = page.locator('[data-testid=dynamic-button]');
    await dynamicButton.click();
    await expect(page.locator('[data-testid=dynamic-content]')).toContainText('Dynamic Content');
  });
});

// 3. Mobile-specific tests
// tests/cross-browser/mobile-only.spec.ts
import { test, expect, devices } from '@playwright/test';

// These tests only run on mobile projects
test.describe('Mobile-Specific Features', () => {
  test('should handle touch gestures', async ({ page }) => {
    await page.goto('/touch-gestures');

    const touchArea = page.locator('[data-testid=touch-area]');

    // Test tap
    await touchArea.tap();
    await expect(page.locator('[data-testid=gesture-result]')).toContainText('tapped');

    // Test swipe (using touch events)
    await touchArea.tap();
    await page.touch.move(touchArea, { position: { x: 100, y: 100 } });
    await page.touch.move(touchArea, { position: { x: 200, y: 100 } });
    await page.touch.end();

    await expect(page.locator('[data-testid=gesture-result]')).toContainText('swiped');
  });

  test('should handle device orientation', async ({ page }) => {
    await page.goto('/orientation-test');

    // Test portrait mode
    await page.setViewportSize({ width: 375, height: 667 });
    await expect(page.locator('[data-testid=orientation-indicator]')).toContainText('portrait');

    // Test landscape mode
    await page.setViewportSize({ width: 667, height: 375 });
    await expect(page.locator('[data-testid=orientation-indicator]')).toContainText('landscape');
  });

  test('should handle mobile navigation', async ({ page }) => {
    await page.goto('/');

    // Check for mobile menu button
    const menuButton = page.locator('[data-testid=mobile-menu-button]');
    await expect(menuButton).toBeVisible();

    // Open mobile menu
    await menuButton.tap();
    const mobileMenu = page.locator('[data-testid=mobile-menu]');
    await expect(mobileMenu).toBeVisible();

    // Test menu items
    await expect(page.locator('[data-testid=menu-item]').first()).toBeVisible();

    // Close menu
    const closeButton = page.locator('[data-testid=mobile-menu-close]');
    await closeButton.tap();
    await expect(mobileMenu).not.toBeVisible();
  });

  test('should handle pull-to-refresh', async ({ page }) => {
    await page.goto('/pull-to-refresh');

    // Simulate pull to refresh gesture
    await page.touch.start(200, 50);
    await page.touch.move(200, 250);
    await page.touch.end();

    await expect(page.locator('[data-testid=refresh-indicator]')).toBeVisible();
    await expect(page.locator('[data-testid=refresh-result]')).toContainText('refreshed');
  });

  test('should handle virtual keyboard', async ({ page }) => {
    await page.goto('/form-test');

    const inputField = page.locator('input[type="text"]');
    await inputField.tap();

    // Check if keyboard appears (test for viewport shift)
    const initialViewport = page.viewportSize();
    await page.waitForTimeout(500); // Wait for keyboard to appear

    // In a real test, you might check for viewport changes or keyboard-specific classes
    await expect(inputField).toBeFocused();

    // Test keyboard dismissal
    await page.keyboard.press('Escape');
    await expect(inputField).not.toBeFocused();
  });
});

// 4. Responsive design tests
// tests/cross-browser/responsive-design.spec.ts
import { test, expect } from '../fixtures/base.fixture';

test.describe('Responsive Design Testing', () => {
  const viewports = [
    { name: 'mobile', width: 375, height: 667 },
    { name: 'mobile-large', width: 414, height: 896 },
    { name: 'tablet', width: 768, height: 1024 },
    { name: 'tablet-large', width: 1024, height: 1366 },
    { name: 'desktop-small', width: 1280, height: 720 },
    { name: 'desktop-large', width: 1920, height: 1080 }
  ];

  viewports.forEach(({ name, width, height }) => {
    test.describe(`${name} viewport (${width}x${height})`, () => {
      test.beforeEach(async ({ page }) => {
        await page.setViewportSize({ width, height });
      });

      test('should adapt navigation layout', async ({ page }) => {
        await page.goto('/');

        if (width < 768) {
          // Mobile layout
          await expect(page.locator('[data-testid=mobile-menu-button]')).toBeVisible();
          await expect(page.locator('[data-testid=desktop-nav]')).not.toBeVisible();
        } else {
          // Desktop/tablet layout
          await expect(page.locator('[data-testid=desktop-nav]')).toBeVisible();
          await expect(page.locator('[data-testid=mobile-menu-button]')).not.toBeVisible();
        }
      });

      test('should adapt content layout', async ({ page }) => {
        await page.goto('/content-grid');

        const gridContainer = page.locator('[data-testid=content-grid]');
        await expect(gridContainer).toBeVisible();

        // Check number of columns based on viewport
        const gridItems = gridContainer.locator('[data-testid=content-item]');
        const itemCount = await gridItems.count();

        if (width < 768) {
          // Mobile: single column
          // Check that items are stacked vertically
          const firstItem = gridItems.first();
          const secondItem = gridItems.nth(1);

          const firstBox = await firstItem.boundingBox();
          const secondBox = await secondItem.boundingBox();

          expect(secondBox!.y).toBeGreaterThan(firstBox!.y);
          expect(Math.abs(secondBox!.x - firstBox!.x)).toBeLessThan(50);
        } else if (width < 1024) {
          // Tablet: 2 columns
          // Check grid layout
          const containerBox = await gridContainer.boundingBox();
          expect(containerBox!.width).toBeGreaterThan(width * 0.9);
        } else {
          // Desktop: 3+ columns
          const containerBox = await gridContainer.boundingBox();
          expect(containerBox!.width).toBeGreaterThan(width * 0.8);
        }
      });

      test('should adapt typography', async ({ page }) => {
        await page.goto('/typography');

        const heading = page.locator('[data-testid=main-heading]');
        await expect(heading).toBeVisible();

        // Check font sizes (using computed styles)
        const fontSize = await heading.evaluate(el =>
          window.getComputedStyle(el).fontSize
        );

        if (width < 768) {
          expect(parseInt(fontSize)).toBeLessThan(32); // Smaller font on mobile
        } else {
          expect(parseInt(fontSize)).toBeGreaterThanOrEqual(24); // Larger font on desktop
        }
      });

      test('should handle images responsively', async ({ page }) => {
        await page.goto('/image-gallery');

        const images = page.locator('[data-testid=gallery-image]');
        await expect(images.first()).toBeVisible();

        // Check image dimensions
        const firstImage = images.first();
        const imageBox = await firstImage.boundingBox();

        expect(imageBox!.width).toBeLessThanOrEqual(width * 0.9); // Should not overflow viewport

        // Test image loading
        await expect(firstImage).toHaveAttribute('src');
        await expect(firstImage).toBeVisible();
      });
    });
  });
});

// 5. Browser-specific feature tests
// tests/cross-browser/browser-features.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Browser-Specific Features', () => {
  test.describe('Chrome-specific features', () => {
    test.use({ browserName: 'chromium' });

    test('should handle Chrome DevTools Protocol', async ({ page }) => {
      await page.goto('/');

      // Test Chrome-specific features
      const client = await page.context().newCDPSession(page);

      // Enable performance domain
      await client.send('Performance.enable');

      // Get performance metrics
      const metrics = await client.send('Performance.getMetrics');
      expect(metrics.metrics).toBeDefined();

      // Test memory usage
      const memoryMetrics = metrics.metrics.filter(m => m.name.includes('Memory'));
      expect(memoryMetrics.length).toBeGreaterThan(0);
    });

    test('should handle Chrome extensions', async ({ page, context }) => {
      // Load test extension (if available)
      try {
        await context.addInitScript(() => {
          // Simulate extension injection
          window.chromeExtensionLoaded = true;
        });

        await page.goto('/');
        await expect(page.evaluate(() => window.chromeExtensionLoaded)).toBeTruthy();
      } catch (error) {
        console.log('Extension test skipped:', error);
      }
    });
  });

  test.describe('Firefox-specific features', () => {
    test.use({ browserName: 'firefox' });

    test('should handle Firefox preferences', async ({ page }) => {
      await page.goto('/');

      // Test Firefox-specific CSS
      await page.addStyleTag({
        content: '@-moz-document url-prefix() { .firefox-only { color: red; } }'
      });

      const firefoxElement = page.locator('.firefox-only');
      if (await firefoxElement.count() > 0) {
        await expect(firefoxElement).toHaveCSS('color', 'rgb(255, 0, 0)');
      }
    });
  });

  test.describe('Safari/WebKit-specific features', () => {
    test.use({ browserName: 'webkit' });

    test('should handle WebKit-specific CSS', async ({ page }) => {
      await page.goto('/webkit-features');

      // Test WebKit-specific features
      await page.addStyleTag({
        content: '.safari-only { -webkit-appearance: none; -webkit-user-select: none; }'
      });

      const safariElement = page.locator('.safari-only');
      if (await safariElement.count() > 0) {
        await expect(safariElement).toBeVisible();
      }
    });

    test('should handle iOS Safari features', async ({ page }) => {
      // Test iOS-specific touch handling
      await page.goto('/touch-test');

      const touchElement = page.locator('[data-testid=touch-element]');
      await touchElement.tap();

      await expect(page.locator('[data-testid=touch-result]')).toContainText('touched');
    });
  });
});

// 6. Performance testing across browsers
// tests/cross-browser/performance.spec.ts
import { test, expect } from '../fixtures/base.fixture';

test.describe('Cross-Browser Performance Testing', () => {
  test('should load within reasonable time across browsers', async ({ page }) => {
    const startTime = Date.now();

    await page.goto('/', { waitUntil: 'networkidle' });

    const loadTime = Date.now() - startTime;

    // Should load within 5 seconds on any browser
    expect(loadTime).toBeLessThan(5000);

    console.log(`Page load time: ${loadTime}ms`);
  });

  test('should handle resource loading efficiently', async ({ page }) => {
    const responses: any[] = [];

    page.on('response', response => {
      responses.push({
        url: response.url(),
        status: response.status(),
        timing: response.request().timing()
      });
    });

    await page.goto('/');

    // Check for failed requests
    const failedRequests = responses.filter(r => r.status >= 400);
    expect(failedRequests.length).toBe(0);

    // Check for slow resources (> 2 seconds)
    const slowResources = responses.filter(r =>
      r.timing.responseEnd - r.timing.requestStart > 2000
    );

    console.log(`Slow resources: ${slowResources.length}`);

    // Allow some slow resources but not too many
    expect(slowResources.length).toBeLessThan(3);
  });

  test('should handle JavaScript execution performance', async ({ page }) => {
    await page.goto('/performance-test');

    // Test complex JavaScript operations
    const executionTime = await page.evaluate(() => {
      const start = performance.now();

      // Simulate heavy computation
      let result = 0;
      for (let i = 0; i < 1000000; i++) {
        result += Math.sqrt(i);
      }

      const end = performance.now();
      return end - start;
    });

    // Should complete within reasonable time
    expect(executionTime).toBeLessThan(1000);

    console.log(`JS execution time: ${executionTime.toFixed(2)}ms`);
  });
});

// 7. Accessibility testing across browsers
// tests/cross-browser/accessibility.spec.ts
import { test, expect } from '../fixtures/base.fixture';

test.describe('Cross-Browser Accessibility Testing', () => {
  test('should maintain accessibility across all browsers', async ({ page }) => {
    await page.goto('/accessibility-test');

    // Test keyboard navigation
    await page.keyboard.press('Tab');
    let focusedElement = await page.evaluate(() => document.activeElement?.tagName);
    expect(['BUTTON', 'INPUT', 'A', 'SELECT']).toContain(focusedElement);

    // Continue tabbing through focusable elements
    for (let i = 0; i < 5; i++) {
      await page.keyboard.press('Tab');
      focusedElement = await page.evaluate(() => document.activeElement?.tagName);
      expect(focusedElement).toBeTruthy();
    }
  });

  test('should handle screen readers consistently', async ({ page }) => {
    await page.goto('/accessibility-test');

    // Check ARIA labels
    const buttons = page.locator('button[aria-label]');
    const buttonCount = await buttons.count();

    if (buttonCount > 0) {
      for (let i = 0; i < buttonCount; i++) {
        const button = buttons.nth(i);
        const ariaLabel = await button.getAttribute('aria-label');
        expect(ariaLabel).toBeTruthy();
        expect(ariaLabel!.length).toBeGreaterThan(0);
      }
    }

    // Check ARIA roles
    const landmarks = page.locator('[role]');
    const landmarkCount = await landmarks.count();

    if (landmarkCount > 0) {
      const validRoles = ['navigation', 'main', 'complementary', 'contentinfo', 'banner'];

      for (let i = 0; i < landmarkCount; i++) {
        const landmark = landmarks.nth(i);
        const role = await landmark.getAttribute('role');
        expect(validRoles).toContain(role);
      }
    }
  });

  test('should handle color contrast consistently', async ({ page }) => {
    await page.goto('/accessibility-test');

    // Test color contrast using browser DevTools
    const contrastResults = await page.evaluate(() => {
      const results: any[] = [];
      const elements = document.querySelectorAll('[data-contrast-test]');

      elements.forEach(element => {
        const styles = window.getComputedStyle(element);
        const color = styles.color;
        const backgroundColor = styles.backgroundColor;

        results.push({
          element: element.tagName,
          color,
          backgroundColor
        });
      });

      return results;
    });

    // In a real test, you'd use a contrast calculation library
    expect(contrastResults.length).toBeGreaterThan(0);

    contrastResults.forEach(result => {
      expect(result.color).not.toBe('rgba(0, 0, 0, 0)');
      expect(result.backgroundColor).not.toBe('rgba(0, 0, 0, 0)');
    });
  });
});

// 8. Cross-browser debugging utilities
// tests/utils/CrossBrowserHelper.ts
import { Page } from '@playwright/test';

export class CrossBrowserHelper {
  constructor(private page: Page) {}

  async getBrowserInfo() {
    return {
      userAgent: await this.page.evaluate(() => navigator.userAgent),
      viewport: this.page.viewportSize(),
      browserName: this.page.context().browser()?.browserType().name()
    };
  }

  async captureConsoleLogs() {
    const logs: string[] = [];

    this.page.on('console', msg => {
      logs.push(`[${msg.type()}] ${msg.text()}`);
    });

    return logs;
  }

  async captureNetworkRequests() {
    const requests: any[] = [];

    this.page.on('request', request => {
      requests.push({
        url: request.url(),
        method: request.method(),
        headers: request.headers()
      });
    });

    return requests;
  }

  async capturePerformanceMetrics() {
    return await this.page.evaluate(() => {
      if ('performance' in window) {
        return {
          navigation: performance.getEntriesByType('navigation')[0],
          resources: performance.getEntriesByType('resource'),
          timing: performance.timing
        };
      }
      return null;
    });
  }

  async debugLayoutIssues() {
    return await this.page.evaluate(() => {
      const issues: string[] = [];

      // Check for overflow issues
      const elements = document.querySelectorAll('*');
      elements.forEach(el => {
        const rect = el.getBoundingClientRect();
        if (rect.right > window.innerWidth || rect.bottom > window.innerHeight) {
          issues.push(`Element ${el.tagName}#${el.id || el.className} overflows viewport`);
        }
      });

      // Check for overlapping elements
      const visibleElements = Array.from(elements).filter(el => {
        const rect = el.getBoundingClientRect();
        return rect.width > 0 && rect.height > 0;
      });

      for (let i = 0; i < visibleElements.length; i++) {
        for (let j = i + 1; j < visibleElements.length; j++) {
          const rect1 = visibleElements[i].getBoundingClientRect();
          const rect2 = visibleElements[j].getBoundingClientRect();

          if (this.overlaps(rect1, rect2)) {
            issues.push(`Potential overlap: ${visibleElements[i].tagName} and ${visibleElements[j].tagName}`);
          }
        }
      }

      return issues;
    });
  }

  private overlaps(rect1: DOMRect, rect2: DOMRect): boolean {
    return !(rect1.right < rect2.left ||
             rect1.left > rect2.right ||
             rect1.bottom < rect2.top ||
             rect1.top > rect2.bottom);
  }

  async generateCrossBrowserReport() {
    const browserInfo = await this.getBrowserInfo();
    const performanceMetrics = await this.capturePerformanceMetrics();
    const layoutIssues = await this.debugLayoutIssues();

    return {
      browser: browserInfo,
      performance: performanceMetrics,
      layoutIssues,
      timestamp: new Date().toISOString()
    };
  }
}

💻 API Testing and Network Interception text

🟡 intermediate ⭐⭐⭐⭐

Advanced API testing with Playwright including request/response mocking, REST API testing, GraphQL support, and performance testing strategies

⏱️ 35 min 🏷️ playwright, api testing, network, performance, graphql
Prerequisites: TypeScript advanced, REST APIs, GraphQL basics, HTTP protocols, Async/await
// API Testing and Network Interception with Playwright

// 1. Basic API Testing Setup
// tests/api/api.config.ts
import { defineConfig } from '@playwright/test';

const playwrightAdvancedConfig = defineConfig({
  testDir: './tests/api',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,

  reporter: [
    ['json', { outputFile: 'test-results/api-test-results.json' }],
    ['html', { outputFolder: 'playwright-report-api' }]
  ],

  use: {
    baseURL: process.env.API_BASE_URL || 'http://localhost:4000/api',
    extraHTTPHeaders: {
      'Content-Type': 'application/json',
      'Accept': 'application/json'
    },
    trace: 'on-first-retry'
  },

  projects: [
    {
      name: 'api-tests',
      testMatch: '**/*.api.spec.ts'
    }
  ]
});

// 2. REST API Testing
// tests/api/rest-api.spec.ts
import { test, expect, request } from '@playwright/test';

test.describe('REST API Testing', () => {
  const apiBase = process.env.API_BASE_URL || 'http://localhost:4000/api';
  let authHeaders: Record<string, string>;

  test.beforeAll(async () => {
    // Setup authentication headers
    const loginResponse = await request.post(`${apiBase}/auth/login`, {
      data: {
        username: 'testuser',
        password: 'testpass'
      }
    });

    expect(loginResponse.ok()).toBeTruthy();
    const { token } = await loginResponse.json();

    authHeaders = {
      'Authorization': `Bearer ${token}`
    };
  });

  test.describe('User Management API', () => {
    test('should create a new user', async ({ request }) => {
      const newUser = {
        username: 'newuser',
        email: '[email protected]',
        password: 'password123',
        role: 'user'
      };

      const response = await request.post(`${apiBase}/users`, {
        data: newUser,
        headers: authHeaders
      });

      expect(response.ok()).toBeTruthy();
      expect(response.status()).toBe(201);

      const createdUser = await response.json();
      expect(createdUser).toMatchObject({
        username: newUser.username,
        email: newUser.email,
        role: newUser.role
      });
      expect(createdUser).toHaveProperty('id');
      expect(createdUser).toHaveProperty('createdAt');
    });

    test('should get user by ID', async ({ request }) => {
      const response = await request.get(`${apiBase}/users/1`, {
        headers: authHeaders
      });

      expect(response.ok()).toBeTruthy();
      expect(response.status()).toBe(200);

      const user = await response.json();
      expect(user).toHaveProperty('id', 1);
      expect(user).toHaveProperty('username');
      expect(user).toHaveProperty('email');
    });

    test('should update user information', async ({ request }) => {
      const updateData = {
        email: '[email protected]',
        role: 'admin'
      };

      const response = await request.put(`${apiBase}/users/1`, {
        data: updateData,
        headers: authHeaders
      });

      expect(response.ok()).toBeTruthy();
      expect(response.status()).toBe(200);

      const updatedUser = await response.json();
      expect(updatedUser).toMatchObject(updateData);
    });

    test('should delete a user', async ({ request }) => {
      const response = await request.delete(`${apiBase}/users/2`, {
        headers: authHeaders
      });

      expect(response.ok()).toBeTruthy();
      expect(response.status()).toBe(204);

      // Verify deletion
      const verifyResponse = await request.get(`${apiBase}/users/2`, {
        headers: authHeaders
      });

      expect(verifyResponse.status()).toBe(404);
    });

    test('should handle validation errors', async ({ request }) => {
      const invalidUser = {
        username: '', // Invalid: empty username
        email: 'invalid-email', // Invalid: malformed email
        password: '123' // Invalid: too short
      };

      const response = await request.post(`${apiBase}/users`, {
        data: invalidUser,
        headers: authHeaders
      });

      expect(response.status()).toBe(400);

      const errorResponse = await response.json();
      expect(errorResponse).toHaveProperty('error');
      expect(errorResponse).toHaveProperty('details');

      // Check for specific validation messages
      const details = errorResponse.details;
      expect(details.some((detail: any) => detail.field === 'username')).toBeTruthy();
      expect(details.some((detail: any) => detail.field === 'email')).toBeTruthy();
      expect(details.some((detail: any) => detail.field === 'password')).toBeTruthy();
    });
  });

  test.describe('Product Management API', () => {
    test('should get all products with pagination', async ({ request }) => {
      const response = await request.get(`${apiBase}/products?page=1&limit=10`, {
        headers: authHeaders
      });

      expect(response.ok()).toBeTruthy();
      expect(response.status()).toBe(200);

      const productsResponse = await response.json();
      expect(productsResponse).toHaveProperty('products');
      expect(productsResponse).toHaveProperty('pagination');

      expect(productsResponse.products).toBeInstanceOf(Array);
      expect(productsResponse.products.length).toBeLessThanOrEqual(10);

      const pagination = productsResponse.pagination;
      expect(pagination).toMatchObject({
        page: 1,
        limit: 10,
        total: expect.any(Number),
        totalPages: expect.any(Number)
      });
    });

    test('should search products', async ({ request }) => {
      const searchTerm = 'laptop';
      const response = await request.get(`${apiBase}/products/search?q=${searchTerm}`, {
        headers: authHeaders
      });

      expect(response.ok()).toBeTruthy();

      const searchResults = await response.json();
      expect(searchResults).toHaveProperty('products');

      // All results should contain the search term
      searchResults.products.forEach((product: any) => {
        expect(
          product.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
          product.description.toLowerCase().includes(searchTerm.toLowerCase())
        ).toBeTruthy();
      });
    });

    test('should filter products by category', async ({ request }) => {
      const category = 'electronics';
      const response = await request.get(`${apiBase}/products?category=${category}`, {
        headers: authHeaders
      });

      expect(response.ok()).toBeTruthy();

      const filteredProducts = await response.json();
      expect(filteredProducts.products).toBeInstanceOf(Array);

      filteredProducts.products.forEach((product: any) => {
        expect(product.category).toBe(category);
      });
    });
  });

  test.describe('Error Handling', () => {
    test('should handle 404 errors', async ({ request }) => {
      const response = await request.get(`${apiBase}/nonexistent`, {
        headers: authHeaders
      });

      expect(response.status()).toBe(404);

      const errorResponse = await response.json();
      expect(errorResponse).toHaveProperty('error');
      expect(errorResponse.error).toContain('Not Found');
    });

    test('should handle 401 unauthorized errors', async ({ request }) => {
      const response = await request.get(`${apiBase}/users/protected`, {
        headers: {} // No auth headers
      });

      expect(response.status()).toBe(401);

      const errorResponse = await response.json();
      expect(errorResponse).toHaveProperty('error');
      expect(errorResponse.error).toContain('Unauthorized');
    });

    test('should handle rate limiting', async ({ request }) => {
      const responses = [];

      // Make multiple requests quickly to trigger rate limiting
      for (let i = 0; i < 10; i++) {
        const response = await request.get(`${apiBase}/users`, {
          headers: authHeaders
        });
        responses.push(response);
      }

      // Check if any response was rate limited
      const rateLimitedResponse = responses.find(res => res.status() === 429);
      if (rateLimitedResponse) {
        const errorResponse = await rateLimitedResponse.json();
        expect(errorResponse).toHaveProperty('error');
        expect(errorResponse.error).toContain('Rate limit exceeded');
      }
    });
  });
});

// 3. GraphQL API Testing
// tests/api/graphql-api.spec.ts
import { test, expect, request } from '@playwright/test';

test.describe('GraphQL API Testing', () => {
  const graphqlEndpoint = process.env.GRAPHQL_ENDPOINT || 'http://localhost:4000/graphql';
  let authHeaders: Record<string, string>;

  test.beforeAll(async () => {
    // Setup authentication
    const loginResponse = await request.post(graphqlEndpoint, {
      data: {
        query: `
          mutation Login($username: String!, $password: String!) {
            login(username: $username, password: $password) {
              token
              user {
                id
                username
                role
              }
            }
          }
        `,
        variables: {
          username: 'testuser',
          password: 'testpass'
        }
      }
    });

    expect(loginResponse.ok()).toBeTruthy();
    const { data, errors } = await loginResponse.json();
    expect(errors).toBeUndefined();
    expect(data).toHaveProperty('login.token');

    const { token } = data.login;
    authHeaders = {
      'Authorization': `Bearer ${token}`
    };
  });

  test('should execute GraphQL query', async ({ request }) => {
    const query = {
      query: `
        query GetUsers($limit: Int, $offset: Int) {
          users(limit: $limit, offset: $offset) {
            id
            username
            email
            role
            createdAt
            posts {
              id
              title
              publishedAt
            }
          }
        }
      `,
      variables: {
        limit: 10,
        offset: 0
      }
    };

    const response = await request.post(graphqlEndpoint, {
      data: query,
      headers: authHeaders
    });

    expect(response.ok()).toBeTruthy();

    const result = await response.json();
    expect(result).toHaveProperty('data');
    expect(result).not.toHaveProperty('errors');

    const { users } = result.data;
    expect(users).toBeInstanceOf(Array);
    expect(users.length).toBeLessThanOrEqual(10);

    if (users.length > 0) {
      const user = users[0];
      expect(user).toHaveProperty('id');
      expect(user).toHaveProperty('username');
      expect(user).toHaveProperty('email');
      expect(user).toHaveProperty('role');
      expect(user).toHaveProperty('posts');
      expect(user.posts).toBeInstanceOf(Array);
    }
  });

  test('should execute GraphQL mutation', async ({ request }) => {
    const mutation = {
      query: `
        mutation CreateUser($input: CreateUserInput!) {
          createUser(input: $input) {
            id
            username
            email
            role
            createdAt
          }
        }
      `,
      variables: {
        input: {
          username: 'graphqluser',
          email: '[email protected]',
          password: 'password123',
          role: 'user'
        }
      }
    };

    const response = await request.post(graphqlEndpoint, {
      data: mutation,
      headers: authHeaders
    });

    expect(response.ok()).toBeTruthy();

    const result = await response.json();
    expect(result).toHaveProperty('data');
    expect(result).not.toHaveProperty('errors');

    const { createUser } = result.data;
    expect(createUser).toMatchObject({
      username: 'graphqluser',
      email: '[email protected]',
      role: 'user'
    });
    expect(createUser).toHaveProperty('id');
    expect(createUser).toHaveProperty('createdAt');
  });

  test('should handle GraphQL errors', async ({ request }) => {
    const invalidQuery = {
      query: `
        query GetInvalidField {
          users {
            invalidField
          }
        }
      `
    };

    const response = await request.post(graphqlEndpoint, {
      data: invalidQuery,
      headers: authHeaders
    });

    expect(response.ok()).toBeTruthy();

    const result = await response.json();
    expect(result).toHaveProperty('errors');
    expect(result.errors).toBeInstanceOf(Array);
    expect(result.errors[0]).toHaveProperty('message');
    expect(result.errors[0].message).toContain('invalidField');
  });

  test('should handle GraphQL subscriptions (WebSocket)', async ({ page }) => {
    await page.goto('/graphql-subscription-test');

    // Test subscription via WebSocket
    const subscriptionResult = await page.evaluate(async (endpoint: string) => {
      return new Promise((resolve) => {
        const ws = new WebSocket(endpoint.replace('http', 'ws'));

        ws.onopen = () => {
          // Send subscription query
          ws.send(JSON.stringify({
            type: 'start',
            payload: {
              query: `
                subscription OnUserCreated {
                  userCreated {
                    id
                    username
                    email
                    createdAt
                  }
                }
              `
            }
          }));
        };

        ws.onmessage = (event) => {
          const data = JSON.parse(event.data);
          if (data.type === 'data') {
            resolve(data.payload.data.userCreated);
          }
        };
      });
    }, graphqlEndpoint);

    // Trigger user creation to test subscription
    const mutation = {
      query: `
        mutation CreateUser($input: CreateUserInput!) {
          createUser(input: $input) {
            id
            username
          }
        }
      `,
      variables: {
        input: {
          username: 'subscriptiontest',
          email: '[email protected]',
          password: 'password123',
          role: 'user'
        }
      }
    };

    await request.post(graphqlEndpoint, {
      data: mutation,
      headers: authHeaders
    });

    // Wait for subscription result
    expect(subscriptionResult).toBeTruthy();
    expect(subscriptionResult.username).toBe('subscriptiontest');
  });
});

// 4. Network Interception and Mocking
// tests/api/network-interception.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Network Interception', () => {
  test('should intercept and mock API responses', async ({ page }) => {
    await page.goto('/dashboard');

    // Intercept GET users request
    await page.route('**/api/users', async (route) => {
      await route.fulfill({
        status: 200,
        contentType: 'application/json',
        body: JSON.stringify([
          { id: 1, name: 'Mock User 1', email: '[email protected]' },
          { id: 2, name: 'Mock User 2', email: '[email protected]' }
        ])
      });
    });

    // Load users
    await page.click('[data-testid=load-users-button]');

    // Verify mocked data is displayed
    await expect(page.locator('[data-testid=user-list]')).toContainText('Mock User 1');
    await expect(page.locator('[data-testid=user-list]')).toContainText('Mock User 2');
  });

  test('should simulate API failures', async ({ page }) => {
    await page.goto('/dashboard');

    // Intercept API call and return error
    await page.route('**/api/users', async (route) => {
      await route.fulfill({
        status: 500,
        contentType: 'application/json',
        body: JSON.stringify({ error: 'Internal Server Error' })
      });
    });

    await page.click('[data-testid=load-users-button]');

    // Verify error handling
    await expect(page.locator('[data-testid=error-message]')).toBeVisible();
    await expect(page.locator('[data-testid=error-message]')).toContainText('Failed to load users');
  });

  test('should modify API requests', async ({ page }) => {
    await page.goto('/profile');

    // Intercept and modify request
    await page.route('**/api/users/profile', async (route) => {
      const request = route.request();
      const postData = request.postDataJSON();

      // Add authentication header
      postData.authToken = 'mock-auth-token';

      // Continue with modified request
      await route.continue({
        postData: JSON.stringify(postData)
      });
    });

    // Mock the response
    await page.route('**/api/users/profile', async (route) => {
      await route.fulfill({
        status: 200,
        contentType: 'application/json',
        body: JSON.stringify({
          id: 1,
          name: 'Test User',
          email: '[email protected]',
          preferences: {
            theme: 'dark',
            notifications: true
          }
        })
      });
    });

    await page.click('[data-testid=save-profile-button]');

    await expect(page.locator('[data-testid=success-message]')).toBeVisible();
  });

  test('should handle network delays', async ({ page }) => {
    await page.goto('/slow-loading-page');

    // Intercept with delay
    await page.route('**/api/slow-data', async (route) => {
      await new Promise(resolve => setTimeout(resolve, 2000)); // 2 second delay

      await route.fulfill({
        status: 200,
        contentType: 'application/json',
        body: JSON.stringify({ data: 'slow response data' })
      });
    });

    // Show loading indicator
    await expect(page.locator('[data-testid=loading-spinner]')).toBeVisible();

    // Click to load slow data
    await page.click('[data-testid=load-slow-data]');

    // Wait for loading to complete
    await expect(page.locator('[data-testid=loading-spinner]')).not.toBeVisible();
    await expect(page.locator('[data-testid=data-display]')).toContainText('slow response data');
  });
});

// 5. File Upload API Testing
// tests/api/file-upload.spec.ts
import { test, expect, request } from '@playwright/test';

test.describe('File Upload API Testing', () => {
  const apiBase = process.env.API_BASE_URL || 'http://localhost:4000/api';

  test('should upload single file', async ({ request }) => {
    // Create a test file buffer
    const fileContent = 'This is a test file content';
    const fileBuffer = Buffer.from(fileContent);

    const formData = new FormData();
    formData.append('file', fileBuffer, 'test.txt');
    formData.append('description', 'Test file upload');

    const response = await request.post(`${apiBase}/upload`, {
      multipart: {
        file: {
          name: 'test.txt',
          mimeType: 'text/plain',
          buffer: fileBuffer
        },
        description: 'Test file upload'
      }
    });

    expect(response.ok()).toBeTruthy();
    expect(response.status()).toBe(201);

    const result = await response.json();
    expect(result).toMatchObject({
      filename: 'test.txt',
      size: fileContent.length,
      mimeType: 'text/plain',
      description: 'Test file upload'
    });
    expect(result).toHaveProperty('id');
    expect(result).toHaveProperty('url');
  });

  test('should upload multiple files', async ({ request }) => {
    const files = [
      { name: 'file1.txt', content: 'First file content', mimeType: 'text/plain' },
      { name: 'file2.json', content: '{"key": "value"}', mimeType: 'application/json' }
    ];

    const formData = new FormData();

    files.forEach(file => {
      const fileBuffer = Buffer.from(file.content);
      formData.append('files', fileBuffer, file.name);
    });

    const response = await request.post(`${apiBase}/upload/multiple`, {
      multipart: {
        files: files.map(file => ({
          name: file.name,
          mimeType: file.mimeType,
          buffer: Buffer.from(file.content)
        }))
      }
    });

    expect(response.ok()).toBeTruthy();

    const result = await response.json();
    expect(result).toHaveProperty('files');
    expect(result.files).toHaveLength(2);

    result.files.forEach((uploadedFile: any, index: number) => {
      expect(uploadedFile.filename).toBe(files[index].name);
      expect(uploadedFile.mimeType).toBe(files[index].mimeType);
    });
  });

  test('should validate file upload constraints', async ({ request }) => {
    // Test file size limit
    const largeFileContent = 'x'.repeat(11 * 1024 * 1024); // 11MB file
    const largeFileBuffer = Buffer.from(largeFileContent);

    const response = await request.post(`${apiBase}/upload`, {
      multipart: {
        file: {
          name: 'large.txt',
          mimeType: 'text/plain',
          buffer: largeFileBuffer
        }
      }
    });

    expect(response.status()).toBe(400);

    const errorResponse = await response.json();
    expect(errorResponse).toHaveProperty('error');
    expect(errorResponse.error).toContain('File size too large');
  });
});

// 6. API Performance Testing
// tests/api/api-performance.spec.ts
import { test, expect, request } from '@playwright/test';

test.describe('API Performance Testing', () => {
  const apiBase = process.env.API_BASE_URL || 'http://localhost:4000/api';

  test('should measure API response times', async ({ request }) => {
    const endpoints = [
      '/users',
      '/products',
      '/orders'
    ];

    const performanceResults = [];

    for (const endpoint of endpoints) {
      const startTime = Date.now();

      const response = await request.get(`${apiBase}${endpoint}`);

      const endTime = Date.now();
      const responseTime = endTime - startTime;

      performanceResults.push({
        endpoint,
        responseTime,
        status: response.status(),
        contentLength: (await response.body()).length
      });

      expect(responseTime).toBeLessThan(2000); // Should respond within 2 seconds
    }

    console.table(performanceResults);

    // Average response time should be reasonable
    const avgResponseTime = performanceResults.reduce(
      (sum, result) => sum + result.responseTime, 0
    ) / performanceResults.length;

    expect(avgResponseTime).toBeLessThan(1000);
  });

  test('should handle concurrent requests', async ({ request }) => {
    const numRequests = 20;
    const endpoint = `${apiBase}/products`;

    // Make concurrent requests
    const promises = Array.from({ length: numRequests }, () =>
      request.get(endpoint)
    );

    const startTime = Date.now();
    const responses = await Promise.all(promises);
    const endTime = Date.now();

    const totalTime = endTime - startTime;

    // All requests should succeed
    responses.forEach(response => {
      expect(response.ok()).toBeTruthy();
    });

    console.log(`${numRequests} concurrent requests completed in ${totalTime}ms`);

    // Should complete within reasonable time
    expect(totalTime).toBeLessThan(5000);

    // Calculate average response time
    const avgResponseTime = totalTime / numRequests;
    expect(avgResponseTime).toBeLessThan(250);
  });

  test('should test API load handling', async ({ request }) => {
    const loadTestDuration = 10000; // 10 seconds
    const requestInterval = 100; // Request every 100ms
    const endpoint = `${apiBase}/users`;

    const results = [];
    const startTime = Date.now();

    while (Date.now() - startTime < loadTestDuration) {
      const requestStartTime = Date.now();

      try {
        const response = await request.get(endpoint);

        const requestEndTime = Date.now();
        const responseTime = requestEndTime - requestStartTime;

        results.push({
          success: true,
          responseTime,
          status: response.status()
        });
      } catch (error) {
        results.push({
          success: false,
          error: error.message
        });
      }

      // Wait before next request
      await new Promise(resolve => setTimeout(resolve, requestInterval));
    }

    const successfulRequests = results.filter(r => r.success);
    const failedRequests = results.filter(r => !r.success);

    console.log(`Load test completed:`);
    console.log(`  Total requests: ${results.length}`);
    console.log(`  Successful: ${successfulRequests.length}`);
    console.log(`  Failed: ${failedRequests.length}`);

    // Success rate should be high
    const successRate = (successfulRequests.length / results.length) * 100;
    expect(successRate).toBeGreaterThan(95);

    // Average response time should be reasonable
    if (successfulRequests.length > 0) {
      const avgResponseTime = successfulRequests.reduce(
        (sum, r) => sum + r.responseTime, 0
      ) / successfulRequests.length;

      expect(avgResponseTime).toBeLessThan(1000);
      console.log(`  Average response time: ${avgResponseTime.toFixed(2)}ms`);
    }
  });
});

// 7. API Testing Utilities
// tests/utils/ApiTestHelper.ts
import { APIRequestContext, APIResponse } from '@playwright/test';

export class ApiTestHelper {
  constructor(private request: APIRequestContext) {}

  async login(username: string, password: string): Promise<{ token: string; user: any }> {
    const response = await this.request.post('/auth/login', {
      data: { username, password }
    });

    if (!response.ok()) {
      throw new Error(`Login failed: ${response.status()}`);
    }

    const result = await response.json();
    return result;
  }

  async createTestUser(userData: Partial<any> = {}): Promise<any> {
    const defaultUserData = {
      username: `testuser_${Date.now()}`,
      email: `test${Date.now()}@example.com`,
      password: 'testpass123',
      role: 'user',
      ...userData
    };

    const response = await this.request.post('/users', {
      data: defaultUserData
    });

    if (!response.ok()) {
      throw new Error(`User creation failed: ${response.status()}`);
    }

    return await response.json();
  }

  async cleanupTestUser(userId: number): Promise<void> {
    const response = await this.request.delete(`/users/${userId}`);

    // Don't throw error if user doesn't exist
    if (response.status() !== 404 && !response.ok()) {
      throw new Error(`User cleanup failed: ${response.status()}`);
    }
  }

  async measureApiPerformance(endpoint: string, method = 'GET', data?: any): Promise<PerformanceResult> {
    const startTime = performance.now();

    let response: APIResponse;
    if (method === 'GET') {
      response = await this.request.get(endpoint);
    } else if (method === 'POST') {
      response = await this.request.post(endpoint, { data });
    } else if (method === 'PUT') {
      response = await this.request.put(endpoint, { data });
    } else if (method === 'DELETE') {
      response = await this.request.delete(endpoint);
    } else {
      throw new Error(`Unsupported HTTP method: ${method}`);
    }

    const endTime = performance.now();
    const responseTime = endTime - startTime;

    return {
      endpoint,
      method,
      responseTime,
      status: response.status(),
      success: response.ok(),
      contentLength: (await response.body()).length
    };
  }

  async validateApiResponse(response: APIResponse, expectedStatus = 200): Promise<void> {
    expect(response.status()).toBe(expectedStatus);

    if (expectedStatus === 200 || expectedStatus === 201) {
      const contentType = response.headers()['content-type'];
      expect(contentType).toContain('application/json');
    }
  }

  async setupTestData(): Promise<void> {
    // Create test data required for API tests
    await this.createTestUser({ role: 'admin' });
    await this.createTestUser({ role: 'moderator' });
  }

  async cleanupTestData(): Promise<void> {
    // Clean up test data
    // This would depend on your specific API cleanup requirements
    console.log('Cleaning up test data...');
  }
}

interface PerformanceResult {
  endpoint: string;
  method: string;
  responseTime: number;
  status: number;
  success: boolean;
  contentLength: number;
}

// 8. API Test Fixtures
// tests/fixtures/api.fixture.ts
import { test as base, expect } from '@playwright/test';
import { ApiTestHelper } from '../utils/ApiTestHelper';

type ApiFixtures = {
  apiHelper: ApiTestHelper;
  authenticatedRequest: {
    request: APIRequestContext;
    authToken: string;
  };
};

export const test = base.extend<ApiFixtures>({
  apiHelper: async ({ request }, use) => {
    const helper = new ApiTestHelper(request);
    await use(helper);
  },

  authenticatedRequest: async ({ request, apiHelper }, use) => {
    const { token } = await apiHelper.login('testuser', 'testpass');

    // Create authenticated request context
    const authRequest = request;

    await use({ request: authRequest, authToken: token });
  }
});

export { expect };

💻 Advanced Testing Patterns and Best Practices text

🔴 complex ⭐⭐⭐⭐⭐

Sophisticated testing strategies including Page Object Models, test data management, reporting, CI/CD integration, and enterprise-level testing workflows

⏱️ 45 min 🏷️ playwright, advanced, patterns, enterprise, architecture
Prerequisites: TypeScript expert, Design patterns, Enterprise testing, CI/CD concepts, Performance testing
// Advanced Testing Patterns and Best Practices with Playwright

// 1. Advanced Page Object Model with Factory Pattern
// tests/pages/PageFactory.ts
import { Page } from '@playwright/test';
import { HomePage } from './HomePage';
import { LoginPage } from './LoginPage';
import { DashboardPage } from './DashboardPage';
import { ProductPage } from './ProductPage';

export class PageFactory {
  private page: Page;

  constructor(page: Page) {
    this.page = page;
  }

  // Factory methods for creating page objects
  getHomePage(): HomePage {
    return new HomePage(this.page);
  }

  getLoginPage(): LoginPage {
    return new LoginPage(this.page);
  }

  getDashboardPage(): DashboardPage {
    return new DashboardPage(this.page);
  }

  getProductPage(): ProductPage {
    return new ProductPage(this.page);
  }

  // Context-aware page creation
  async getPageFor<T extends PageObject>(pageClass: new (page: Page) => T): Promise<T> {
    const pageObject = new pageClass(this.page) as T;

    // Wait for page-specific elements
    if (pageObject.getIdentifier) {
      await this.page.waitForSelector(pageObject.getIdentifier());
    }

    return pageObject;
  }
}

// tests/pages/BasePage.ts - Enhanced base page
import { Page, Locator } from '@playwright/test';
import { TestDataLoader } from '../utils/TestDataLoader';
import { WaitHelper } from '../utils/WaitHelper';

export abstract class BasePage {
  protected page: Page;
  protected testDataLoader: TestDataLoader;
  protected waitHelper: WaitHelper;

  constructor(page: Page) {
    this.page = page;
    this.testDataLoader = new TestDataLoader();
    this.waitHelper = new WaitHelper(page);
  }

  // Abstract method that child classes must implement
  abstract getIdentifier(): string;

  // Common locators that might be present on many pages
  protected readonly header = this.page.locator('header');
  protected readonly footer = this.page.locator('footer');
  protected readonly mainNavigation = this.page.locator('[data-testid=main-nav]');
  protected readonly userMenu = this.page.locator('[data-testid=user-menu]');
  protected readonly notifications = this.page.locator('[data-testid=notifications]');
  protected readonly loadingIndicator = this.page.locator('[data-testid=loading]');

  // Enhanced navigation methods
  async navigateTo(path: string, options?: { waitUntil?: 'load' | 'domcontentloaded' | 'networkidle' }): Promise<void> {
    await this.page.goto(path, {
      waitUntil: options?.waitUntil || 'domcontentloaded',
      timeout: 30000
    });

    // Wait for page identifier
    await this.waitHelper.waitForElement(this.getIdentifier());
  }

  // Enhanced wait methods
  async waitForPageLoad(): Promise<void> {
    await this.page.waitForLoadState('networkidle');
    await this.waitHelper.waitForElement(this.getIdentifier());
  }

  async waitForContentToLoad(selector: string): Promise<void> {
    await this.waitHelper.waitForElement(selector);
  }

  async waitForElementToDisappear(selector: string): Promise<void> {
    await this.waitHelper.waitForElementToDisappear(selector);
  }

  // Screenshot and debugging utilities
  async takeScreenshot(name: string, options?: { fullPage?: boolean }): Promise<void> {
    const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
    const fileName = `screenshots/${name}_${timestamp}.png`;

    await this.page.screenshot({
      path: fileName,
      fullPage: options?.fullPage || true
    });
  }

  async takeScreenshotOfElement(selector: string, name: string): Promise<void> {
    const element = this.page.locator(selector);
    const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
    const fileName = `screenshots/${name}_${timestamp}.png`;

    await element.screenshot({ path: fileName });
  }

  // Accessibility testing
  async checkAccessibility(): Promise<void> {
    // Integration with axe-core or similar accessibility testing
    const violations = await this.page.evaluate(() => {
      // This would integrate with axe-core
      return []; // Return accessibility violations
    });

    if (violations.length > 0) {
      console.warn('Accessibility violations found:', violations);
    }
  }

  // Form interaction utilities
  async fillForm(formData: Record<string, string>): Promise<void> {
    for (const [field, value] of Object.entries(formData)) {
      const element = this.page.locator(`[data-testid="${field}"], [name="${field}"], #${field}`);
      await element.fill(value);
    }
  }

  async fillFormWithValidation(formData: Record<string, string>): Promise<void> {
    await this.fillForm(formData);

    // Wait for validation to complete
    await this.page.waitForTimeout(500);

    // Check for validation errors
    const errorElements = this.page.locator('[data-testid*="error"], .error, .invalid-feedback');
    const errorCount = await errorElements.count();

    if (errorCount > 0) {
      const errors: string[] = [];
      for (let i = 0; i < errorCount; i++) {
        const errorText = await errorElements.nth(i).textContent();
        if (errorText) errors.push(errorText);
      }
      throw new Error(`Form validation failed: ${errors.join(', ')}`);
    }
  }

  // Toast/notification utilities
  async waitForNotification(message?: string, timeout = 5000): Promise<Locator> {
    const notification = this.page.locator('[data-testid="notification"]');
    await notification.waitFor({ state: 'visible', timeout });

    if (message) {
      await expect(notification).toContainText(message);
    }

    return notification;
  }

  async waitForNotificationToDisappear(): Promise<void> {
    const notification = this.page.locator('[data-testid="notification"]');
    await notification.waitFor({ state: 'hidden', timeout: 5000 });
  }

  // Performance monitoring
  async measurePageLoadTime(): Promise<number> {
    const navigationEntry = await this.page.evaluate(() => {
      return performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
    });

    return navigationEntry.loadEventEnd - navigationEntry.loadEventStart;
  }

  // Data testing utilities
  async verifyTableData(expectedData: Record<string, string>[]): Promise<void> {
    const tableRows = this.page.locator('[data-testid="table-row"]');
    const rowCount = await tableRows.count();

    expect(rowCount).toBe(expectedData.length);

    for (let i = 0; i < expectedData.length; i++) {
      const rowData = expectedData[i];
      const row = tableRows.nth(i);

      for (const [key, value] of Object.entries(rowData)) {
        const cell = row.locator(`[data-testid="cell-${key}"]`);
        await expect(cell).toContainText(value);
      }
    }
  }

  // API interaction within page context
  async waitForApiCall(apiPattern: string, timeout = 30000): Promise<any> {
    return await this.page.waitForResponse(response =>
      response.url().includes(apiPattern),
      { timeout }
    );
  }

  async captureApiResponse(apiPattern: string): Promise<any[]> {
    const responses: any[] = [];

    this.page.on('response', response => {
      if (response.url().includes(apiPattern)) {
        responses.push(response);
      }
    });

    return responses;
  }

  // Responsive testing utilities
  async testResponsive(viewports: Array<{ name: string; width: number; height: number }>): Promise<void> {
    for (const viewport of viewports) {
      await this.page.setViewportSize({ width: viewport.width, height: viewport.height });

      // Wait for layout to stabilize
      await this.page.waitForTimeout(1000);

      // Take screenshot for visual comparison
      await this.takeScreenshot(`responsive-${viewport.name}`);

      // Verify key elements are visible and properly positioned
      await this.verifyResponsiveLayout(viewport);
    }
  }

  protected async verifyResponsiveLayout(viewport: { width: number; height: number }): Promise<void> {
    // Override in child classes for specific layout verification
  }

  // Error handling utilities
  async retryOperation<T>(
    operation: () => Promise<T>,
    maxAttempts = 3,
    delay = 1000
  ): Promise<T> {
    let lastError: Error;

    for (let attempt = 1; attempt <= maxAttempts; attempt++) {
      try {
        return await operation();
      } catch (error) {
        lastError = error as Error;
        console.warn(`Operation failed (attempt ${attempt}/${maxAttempts}):`, error);

        if (attempt < maxAttempts) {
          await this.page.waitForTimeout(delay);
        }
      }
    }

    throw lastError!;
  }
}

// tests/pages/HomePage.ts - Enhanced home page
import { BasePage } from './BasePage';

export class HomePage extends BasePage {
  // Enhanced locators with better selectors
  readonly heroSection = this.page.locator('[data-testid="hero-section"]');
  readonly heroTitle = this.page.locator('[data-testid="hero-title"]');
  readonly heroSubtitle = this.page.locator('[data-testid="hero-subtitle"]');
  readonly getStartedButton = this.page.getByRole('button', { name: /get started/i });
  readonly featuredProducts = this.page.locator('[data-testid="featured-products"]');
  readonly productCards = this.page.locator('[data-testid="product-card"]');
  readonly testimonials = this.page.locator('[data-testid="testimonials"]');
  readonly newsletterSection = this.page.locator('[data-testid="newsletter-section"]');

  getIdentifier(): string {
    return '[data-testid="home-page"]';
  }

  // Enhanced page interactions
  async visit(): Promise<void> {
    await this.navigateTo('/');
    await this.waitForPageLoad();
    await this.takeScreenshot('homepage-loaded');
  }

  async verifyHeroSection(): Promise<void> {
    await this.waitHelper.waitForElement(this.heroSection);
    await expect(this.heroTitle).toBeVisible();
    await expect(this.heroSubtitle).toBeVisible();
    await expect(this.getStartedButton).toBeVisible();

    // Verify accessibility
    await this.checkAccessibility();
  }

  async navigateToGetStarted(): Promise<void> {
    await this.waitHelper.waitForElement(this.getStartedButton);
    await this.retryOperation(async () => {
      await this.getStartedButton.click();
    });
  }

  async verifyFeaturedProducts(expectedCount?: number): Promise<void> {
    await this.waitHelper.waitForElement(this.featuredProducts);

    if (expectedCount) {
      await expect(this.productCards).toHaveCount(expectedCount);
    }

    // Verify each product card has required elements
    const productCount = await this.productCards.count();
    for (let i = 0; i < productCount; i++) {
      const card = this.productCards.nth(i);
      await expect(card.locator('[data-testid="product-title"]')).toBeVisible();
      await expect(card.locator('[data-testid="product-price"]')).toBeVisible();
      await expect(card.locator('[data-testid="product-image"]')).toBeVisible();
    }
  }

  async clickProductCard(productName: string): Promise<void> {
    const productCard = this.productCards.filter({ hasText: productName }).first();
    await this.waitHelper.waitForElement(productCard);
    await productCard.click();
  }

  async subscribeToNewsletter(email: string): Promise<void> {
    await this.waitHelper.waitForElement(this.newsletterSection);

    const emailInput = this.page.locator('[data-testid="newsletter-email"]');
    const subscribeButton = this.page.getByRole('button', { name: /subscribe/i });

    await this.retryOperation(async () => {
      await emailInput.fill(email);
      await subscribeButton.click();
    });

    await this.waitForNotification('Successfully subscribed to newsletter');
  }

  // Advanced features search
  async searchFeatures(searchTerm: string): Promise<void> {
    const searchInput = this.page.locator('[data-testid="features-search"]');
    const searchButton = this.page.getByRole('button', { name: /search/i });

    await searchInput.fill(searchTerm);
    await searchButton.click();

    // Wait for search results
    await this.page.waitForSelector('[data-testid="search-results"]');
  }

  // Performance testing
  async measureLoadPerformance(): Promise<PerformanceMetrics> {
    await this.visit();

    const loadTime = await this.measurePageLoadTime();
    const lighthouseScore = await this.runLighthouseAudit();

    return {
      loadTime,
      lighthouseScore,
      timestamp: new Date().toISOString()
    };
  }

  private async runLighthouseAudit(): Promise<number> {
    // Integration with lighthouse
    return 95; // Placeholder for actual lighthouse score
  }
}

interface PerformanceMetrics {
  loadTime: number;
  lighthouseScore: number;
  timestamp: string;
}

// 2. Advanced Test Data Management
// tests/utils/TestDataManager.ts
import { randomBytes } from 'crypto';

export class TestDataManager {
  private testData: Map<string, any> = new Map();

  // Generate unique test data
  generateUser(overrides: Partial<any> = {}): any {
    const timestamp = Date.now();
    const randomString = randomBytes(4).toString('hex');

    return {
      username: `testuser_${timestamp}_${randomString}`,
      email: `test_${timestamp}_${randomString}@example.com`,
      firstName: 'Test',
      lastName: 'User',
      password: `password_${randomString}`,
      role: 'user',
      createdAt: new Date().toISOString(),
      ...overrides
    };
  }

  generateProduct(overrides: Partial<any> = {}): any {
    const timestamp = Date.now();
    const randomString = randomBytes(4).toString('hex');

    return {
      name: `Test Product ${timestamp}_${randomString}`,
      description: 'This is a test product',
      price: Math.floor(Math.random() * 1000) + 1,
      category: 'electronics',
      sku: `SKU-${timestamp}-${randomString}`,
      inStock: true,
      createdAt: new Date().toISOString(),
      ...overrides
    };
  }

  generateOrder(userId: string, overrides: Partial<any> = {}): any {
    const timestamp = Date.now();

    return {
      userId,
      items: this.generateOrderItems(),
      total: Math.floor(Math.random() * 500) + 50,
      status: 'pending',
      orderNumber: `ORD-${timestamp}`,
      createdAt: new Date().toISOString(),
      ...overrides
    };
  }

  private generateOrderItems(count = 2): any[] {
    const items = [];
    for (let i = 0; i < count; i++) {
      items.push({
        productId: Math.floor(Math.random() * 100) + 1,
        quantity: Math.floor(Math.random() * 5) + 1,
        price: Math.floor(Math.random() * 200) + 10
      });
    }
    return items;
  }

  // Store and retrieve test data
  storeData(key: string, data: any): void {
    this.testData.set(key, data);
  }

  getData(key: string): any {
    return this.testData.get(key);
  }

  // Cleanup utilities
  async cleanupTestData(apiHelper: any): Promise<void> {
    const testDataArray = Array.from(this.testData.values());

    // Clean up users
    const users = testDataArray.filter(data => data.username);
    for (const user of users) {
      try {
        await apiHelper.deleteUser(user.id);
      } catch (error) {
        console.warn('Failed to cleanup user:', error);
      }
    }

    // Clean up products
    const products = testDataArray.filter(data => data.sku);
    for (const product of products) {
      try {
        await apiHelper.deleteProduct(product.id);
      } catch (error) {
        console.warn('Failed to cleanup product:', error);
      }
    }

    // Clear test data cache
    this.testData.clear();
  }
}

// 3. Advanced Wait Helper
// tests/utils/WaitHelper.ts
import { Page, Locator } from '@playwright/test';

export class WaitHelper {
  constructor(private page: Page) {}

  async waitForElement(selector: string, options: { timeout?: number; state?: 'visible' | 'hidden' } = {}): Promise<void> {
    const { timeout = 10000, state = 'visible' } = options;

    await this.page.waitForSelector(selector, {
      timeout,
      state
    });
  }

  async waitForElementToDisappear(selector: string, timeout = 10000): Promise<void> {
    await this.waitForElement(selector, { timeout, state: 'hidden' });
  }

  async waitForText(selector: string, text: string, timeout = 10000): Promise<void> {
    const element = this.page.locator(selector);
    await element.waitFor({ state: 'visible', timeout });
    await expect(element).toContainText(text, { timeout });
  }

  async waitForUrl(urlPattern: string, timeout = 10000): Promise<void> {
    await this.page.waitForURL(urlPattern, { timeout });
  }

  async waitForNetworkIdle(timeout = 30000): Promise<void> {
    await this.page.waitForLoadState('networkidle', { timeout });
  }

  async waitForElementToBeEnabled(selector: string, timeout = 10000): Promise<void> {
    const element = this.page.locator(selector);
    await element.waitFor({ state: 'visible', timeout });
    await expect(element).toBeEnabled({ timeout });
  }

  async waitForElementToBeDisabled(selector: string, timeout = 10000): Promise<void> {
    const element = this.page.locator(selector);
    await element.waitFor({ state: 'visible', timeout });
    await expect(element).toBeDisabled({ timeout });
  }

  async waitForElementToHaveCount(selector: string, count: number, timeout = 10000): Promise<void> {
    const element = this.page.locator(selector);
    await expect(element).toHaveCount(count, { timeout });
  }

  async waitForElementToHaveAttribute(selector: string, attribute: string, value: string, timeout = 10000): Promise<void> {
    const element = this.page.locator(selector);
    await expect(element).toHaveAttribute(attribute, value, { timeout });
  }

  async waitForElementToHaveClass(selector: string, className: string, timeout = 10000): Promise<void> {
    const element = this.page.locator(selector);
    await expect(element).toHaveClass(className, { timeout });
  }

  async waitForElementToBeVisible(selector: string, timeout = 10000): Promise<void> {
    await this.waitForElement(selector, { timeout, state: 'visible' });
  }

  async waitForPageLoadComplete(timeout = 30000): Promise<void> {
    await Promise.all([
      this.page.waitForLoadState('domcontentloaded', { timeout }),
      this.page.waitForLoadState('load', { timeout })
    ]);
  }

  async waitForAnimationsToComplete(timeout = 5000): Promise<void> {
    await this.page.waitForFunction(() => {
      return document.getAnimations().length === 0;
    }, { timeout });
  }

  async waitForCustomCondition(condition: () => boolean, timeout = 10000, message = 'Custom condition not met'): Promise<void> {
    await this.page.waitForFunction(
      (cond) => cond(),
      condition,
      { timeout }
    ).catch(() => {
      throw new Error(`${message} within ${timeout}ms`);
    });
  }

  async waitForStableElement(selector: string, stableTime = 1000, timeout = 10000): Promise<void> {
    const element = this.page.locator(selector);
    await element.waitFor({ state: 'visible', timeout });

    let previousPosition: any = null;
    const startTime = Date.now();

    while (Date.now() - startTime < stableTime) {
      const currentPosition = await element.boundingBox();

      if (previousPosition) {
        const isStable =
          Math.abs(currentPosition!.x - previousPosition.x) < 1 &&
          Math.abs(currentPosition!.y - previousPosition.y) < 1;

        if (isStable) {
          break;
        }
      }

      previousPosition = currentPosition;
      await this.page.waitForTimeout(100);
    }
  }
}

// 4. Advanced Test Utilities
// tests/utils/TestUtils.ts
import { Page, Locator } from '@playwright/test';

export class TestUtils {
  constructor(private page: Page) {}

  // Random data generation
  static generateRandomString(length = 8): string {
    const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    let result = '';
    for (let i = 0; i < length; i++) {
      result += chars.charAt(Math.floor(Math.random() * chars.length));
    }
    return result;
  }

  static generateRandomEmail(): string {
    return `test${Date.now()}@${this.generateRandomString(6)}.com`;
  }

  static generateRandomPhoneNumber(): string {
    return `${Math.floor(Math.random() * 9000000000) + 1000000000}`;
  }

  // Form utilities
  async fillFormWithRandomData(fieldSelectors: string[]): Promise<Record<string, string>> {
    const formData: Record<string, string> = {};

    for (const selector of fieldSelectors) {
      const fieldType = await this.page.evaluate((sel) => {
        const element = document.querySelector(sel) as HTMLInputElement;
        return element?.type || 'text';
      }, selector);

      let value: string;

      switch (fieldType) {
        case 'email':
          value = TestUtils.generateRandomEmail();
          break;
        case 'tel':
          value = TestUtils.generateRandomPhoneNumber();
          break;
        case 'password':
          value = TestUtils.generateRandomString(12);
          break;
        default:
          value = TestUtils.generateRandomString(10);
      }

      await this.page.fill(selector, value);
      formData[selector] = value;
    }

    return formData;
  }

  // Validation utilities
  async isElementVisible(selector: string): Promise<boolean> {
    try {
      const element = this.page.locator(selector);
      await element.waitFor({ state: 'visible', timeout: 1000 });
      return true;
    } catch {
      return false;
    }
  }

  async getElementText(selector: string): Promise<string> {
    const element = this.page.locator(selector);
    return await element.textContent() || '';
  }

  async getElementAttribute(selector: string, attribute: string): Promise<string | null> {
    const element = this.page.locator(selector);
    return await element.getAttribute(attribute);
  }

  // Navigation utilities
  async getCurrentUrl(): Promise<string> {
    return this.page.url();
  }

  async waitForNavigation(callback: () => Promise<void>): Promise<void> {
    await Promise.all([
      this.page.waitForNavigation(),
      callback()
    ]);
  }

  // Cookie and storage utilities
  async setCookie(name: string, value: string): Promise<void> {
    await this.page.context().addCookies([{
      name,
      value,
      url: this.page.url(),
      domain: new URL(this.page.url()).hostname
    }]);
  }

  async getCookie(name: string): Promise<string | null> {
    const cookies = await this.page.context().cookies();
    const cookie = cookies.find(c => c.name === name);
    return cookie?.value || null;
  }

  async setLocalStorage(key: string, value: string): Promise<void> {
    await this.page.evaluate(([k, v]) => {
      localStorage.setItem(k, v);
    }, [key, value]);
  }

  async getLocalStorage(key: string): Promise<string | null> {
    return await this.page.evaluate((k) => {
      return localStorage.getItem(k);
    }, [key]);
  }

  // Screenshot utilities
  async takeElementScreenshot(selector: string, fileName: string): Promise<void> {
    const element = this.page.locator(selector);
    await element.screenshot({ path: `screenshots/${fileName}.png` });
  }

  async takeFullPageScreenshot(fileName: string): Promise<void> {
    await this.page.screenshot({
      path: `screenshots/${fileName}.png`,
      fullPage: true
    });
  }

  // Performance utilities
  async measurePerformance(callback: () => Promise<void>): Promise<number> {
    const startTime = performance.now();
    await callback();
    const endTime = performance.now();
    return endTime - startTime;
  }

  async getPerformanceMetrics(): Promise<any> {
    return await this.page.evaluate(() => {
      return {
        navigation: performance.getEntriesByType('navigation')[0],
        resources: performance.getEntriesByType('resource'),
        timing: performance.timing,
        memory: (performance as any).memory
      };
    });
  }
}

// 5. Advanced Test Configuration
// tests/config/test-environments.ts
export interface TestEnvironment {
  name: string;
  baseUrl: string;
  apiBaseUrl: string;
  databaseUrl: string;
  features: string[];
}

export const testEnvironments: Record<string, TestEnvironment> = {
  development: {
    name: 'Development',
    baseUrl: 'http://localhost:3000',
    apiBaseUrl: 'http://localhost:4000/api',
    databaseUrl: 'mongodb://localhost:27017/testdb',
    features: ['debug-mode', 'mock-payments', 'fast-loading']
  },
  staging: {
    name: 'Staging',
    baseUrl: 'https://staging.example.com',
    apiBaseUrl: 'https://api-staging.example.com/api',
    databaseUrl: 'mongodb://staging-db:27017/stagingdb',
    features: ['analytics', 'performance-monitoring']
  },
  production: {
    name: 'Production',
    baseUrl: 'https://example.com',
    apiBaseUrl: 'https://api.example.com/api',
    databaseUrl: 'mongodb://prod-db:27017/proddb',
    features: ['full-features', 'monitoring', 'backups']
  }
};

// 6. Test Reporting and Analytics
// tests/utils/TestReporter.ts
export class TestReporter {
  private testResults: any[] = [];

  addTestResult(result: any): void {
    this.testResults.push({
      ...result,
      timestamp: new Date().toISOString()
    });
  }

  generateTestReport(): TestReport {
    const passed = this.testResults.filter(r => r.status === 'passed').length;
    const failed = this.testResults.filter(r => r.status === 'failed').length;
    const skipped = this.testResults.filter(r => r.status === 'skipped').length;

    return {
      summary: {
        total: this.testResults.length,
        passed,
        failed,
        skipped,
        passRate: (passed / this.testResults.length) * 100
      },
      details: this.testResults,
      generatedAt: new Date().toISOString()
    };
  }

  async generatePerformanceReport(): Promise<PerformanceReport> {
    // This would integrate with performance data collected during tests
    return {
      averageLoadTime: 1500,
      slowestPage: 'checkout',
      fastestPage: 'home',
      recommendations: [
        'Optimize images on product pages',
        'Implement lazy loading for testimonials'
      ],
      generatedAt: new Date().toISOString()
    };
  }
}

interface TestReport {
  summary: {
    total: number;
    passed: number;
    failed: number;
    skipped: number;
    passRate: number;
  };
  details: any[];
  generatedAt: string;
}

interface PerformanceReport {
  averageLoadTime: number;
  slowestPage: string;
  fastestPage: string;
  recommendations: string[];
  generatedAt: string;
}

// 7. Example Advanced Test
// tests/advanced/user-journey.spec.ts
import { test, expect } from '../fixtures/base.fixture';
import { TestDataManager } from '../utils/TestDataManager';
import { TestUtils } from '../utils/TestUtils';

test.describe('Complete User Journey', () => {
  let testDataManager: TestDataManager;
  let testUser: any;
  let testUtils: TestUtils;

  test.beforeAll(async ({ page }) => {
    testDataManager = new TestDataManager();
    testUtils = new TestUtils(page);
    testUser = testDataManager.generateUser();
  });

  test.beforeEach(async ({ page }) => {
    testUtils = new TestUtils(page);
  });

  test('complete user registration and first purchase journey', async ({ page, loginPage, homePage, apiHelper }) => {
    // Step 1: Visit homepage
    await homePage.visit();
    await homePage.verifyHeroSection();

    // Step 2: Navigate to registration
    await homePage.navigateToGetStarted();
    await page.waitForURL('**/register');

    // Step 3: Fill registration form
    const registrationForm = page.locator('[data-testid="registration-form"]');
    await testUtils.fillFormWithRandomData([
      '[data-testid="first-name"]',
      '[data-testid="last-name"]',
      '[data-testid="email"]',
      '[data-testid="password"]',
      '[data-testid="confirm-password"]'
    ]);

    // Complete registration
    await page.click('[data-testid="register-button"]');
    await testUtils.waitForNotification('Registration successful');

    // Step 4: Login with new credentials
    await loginPage.visit();
    await loginPage.login(testUser.username, testUser.password);
    await loginPage.verifySuccessfulLogin();

    // Step 5: Browse products
    await homePage.visit();
    await homePage.verifyFeaturedProducts();

    // Search for specific product
    await homePage.searchFeatures('laptop');
    await homePage.clickProductCard('Premium Laptop');

    // Step 6: Add product to cart
    const productPage = page.locator('[data-testid="product-page"]');
    await testUtils.waitForElement('[data-testid="add-to-cart-button"]');
    await page.click('[data-testid="add-to-cart-button"]');
    await testUtils.waitForNotification('Product added to cart');

    // Step 7: View cart and checkout
    await page.click('[data-testid="cart-button"]');
    await page.waitForURL('**/cart');

    await page.click('[data-testid="checkout-button"]');
    await page.waitForURL('**/checkout');

    // Step 8: Fill shipping information
    const checkoutForm = page.locator('[data-testid="checkout-form"]');
    await checkoutForm.fill({
      '[data-testid="shipping-address"]': '123 Test Street',
      '[data-testid="shipping-city"]': 'Test City',
      '[data-testid="shipping-zip"]': '12345'
    });

    // Step 9: Complete purchase
    await page.click('[data-testid="place-order-button"]');
    await testUtils.waitForNotification('Order placed successfully');

    // Step 10: Verify order confirmation
    await expect(page.locator('[data-testid="order-confirmation"]')).toBeVisible();
    const orderNumber = await testUtils.getElementText('[data-testid="order-number"]');
    expect(orderNumber).toMatch(/^ORD-d+$/);

    // Step 11: Verify order in user dashboard
    await page.click('[data-testid="dashboard-button"]');
    await page.waitForURL('**/dashboard');

    await page.click('[data-testid="orders-tab"]');
    const ordersList = page.locator('[data-testid="orders-list"]');
    await expect(ordersList).toContainText(orderNumber);
  });

  test('should handle error scenarios gracefully', async ({ page, loginPage }) => {
    // Test invalid login
    await loginPage.visit();
    await loginPage.login('invalid', 'credentials');
    await loginPage.getErrorMessage().then(error => {
      expect(error).toContain('Invalid credentials');
    });

    // Test network error handling
    await page.route('**/api/**', route => route.abort());

    await page.goto('/');
    await testUtils.waitForElement('[data-testid="network-error"]', { timeout: 5000 });
  });
});