Framework de Testes E2E Playwright

Exemplos abrangentes de testes Playwright incluindo automação multi-navegador, testes de API, testes móveis, testes de regressão visual e padrões E2E avançados para aplicações web modernas

💻 Configuração de Projeto Playwright typescript

🟢 simple ⭐⭐

Configuração completa de projeto Playwright com configuração TypeScript, estrutura de testes, fixtures e melhores práticas de testes E2E confiáveis

⏱️ 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';

export default 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 default 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 default 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();
  });
});

💻 Testes Multi-Navegador e Multi-Plataforma typescript

🟡 intermediate ⭐⭐⭐

Exemplos compreensivos de testes multi-navegador incluindo testes móveis, verificação de design responsivo e estratégias de testes específicas de plataforma

⏱️ 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';

export default 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()
    };
  }
}

💻 Testes de API e Interceptação de Rede typescript

🟡 intermediate ⭐⭐⭐⭐

Testes avançados de API com Playwright incluindo mocking de request/response, testes de API REST, suporte GraphQL e estratégias de testes de performance

⏱️ 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';

export default 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 };

💻 Padrões Avançados de Testes e Melhores Práticas typescript

🔴 complex ⭐⭐⭐⭐⭐

Estratégias sofisticadas de testes incluindo Page Object Models, gestão de dados de testes, relatórios, integração CI/CD e fluxos de trabalho de testes corporativos

⏱️ 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 });
  });
});