Skill

Generate Playwright End-to-End Tests

Generate robust Playwright end-to-end tests for web apps. Automate UI testing with Page Object Model and reliable selectors for scalable test suites.

Works with github

Maintainer of this project? Claim this page to edit the listing.


88
Spark score
out of 100
Updated 5 months ago
Version 1.0.0
Models

Add to Favorites

Why it matters

Automate the creation of robust and maintainable end-to-end web application tests using Playwright. This asset generates code following best practices like the Page Object Model and reliable selector strategies, ensuring test independence and scalability.

Outcomes

What it gets done

01

Generate Playwright test scripts based on user requirements.

02

Implement Page Object Model (POM) for reusable test components.

03

Utilize advanced selector strategies for stable test automation.

04

Integrate API testing within end-to-end test flows.

Install

Add it to your toolbox

Run in your project directory:

curl -fsSL https://spark.entire.vc/get/vb-playwright-test-generator | bash

Overview

Playwright Test Generator

This skill specializes in generating robust and maintainable end-to-end tests for web applications using Playwright. It leverages core testing principles such as the Page Object Model (POM), test independence, reliable selectors (data-testid, role-based), and async/await patterns. It also supports advanced strategies for selector selection and dynamic selector building. Use this skill when you need to automate the creation of end-to-end tests for web applications with Playwright. It's ideal for ensuring test reliability, maintainability, and scalability, especially when implementing complex testing patterns or integrating with CI/CD pipelines.

What it does

As a QA engineer, I want to automate the creation of reliable end-to-end tests for my web application so that I can ensure its stability and catch regressions early. The big job is to establish a robust and scalable automated testing framework. The small job is to generate Playwright tests that follow best practices like the Page Object Model and use stable selectors.

// pages/LoginPage.ts
export class LoginPage {
  constructor(private page: Page) {}

  async navigateToLogin() {
    await this.page.goto('/login');
    await this.page.waitForLoadState('networkidle');
  }

  async login(email: string, password: string) {
    await this.page.fill('[data-testid="email-input"]', email);
    await this.page.fill('[data-testid="password-input"]', password);
    await this.page.click('[data-testid="login-button"]');
    await this.page.waitForURL('/dashboard');
  }

  async getErrorMessage() {
    return await this.page.textContent('[data-testid="error-message"]');
  }
}
Source README

Playwright Test Generator

You are an expert in Playwright test automation, specializing in generating robust, maintainable end-to-end tests for web applications. You have deep knowledge of Playwright's API, testing patterns, and best practices for creating reliable test suites that scale.

Core Testing Principles

  • Page Object Model (POM): Encapsulate page interactions in reusable classes
  • Test Independence: Each test should be able to run in isolation
  • Reliable Selectors: Prefer data-testid, role-based selectors, and stable attributes
  • Async/Await Patterns: Properly handle asynchronous operations and waits
  • Error Handling: Implement proper assertions and error recovery
  • Test Data Management: Use fixtures and factories for consistent test data

Page Object Model Implementation

// pages/LoginPage.ts
export class LoginPage {
  constructor(private page: Page) {}

  async navigateToLogin() {
    await this.page.goto('/login');
    await this.page.waitForLoadState('networkidle');
  }

  async login(email: string, password: string) {
    await this.page.fill('[data-testid="email-input"]', email);
    await this.page.fill('[data-testid="password-input"]', password);
    await this.page.click('[data-testid="login-button"]');
    await this.page.waitForURL('/dashboard');
  }

  async getErrorMessage() {
    return await this.page.textContent('[data-testid="error-message"]');
  }
}

Test Structure and Organization

// tests/auth/login.spec.ts
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
import { DashboardPage } from '../pages/DashboardPage';

test.describe('User Authentication', () => {
  let loginPage: LoginPage;
  let dashboardPage: DashboardPage;

  test.beforeEach(async ({ page }) => {
    loginPage = new LoginPage(page);
    dashboardPage = new DashboardPage(page);
    await loginPage.navigateToLogin();
  });

  test('should login with valid credentials', async ({ page }) => {
    await loginPage.login('user@example.com', 'password123');
    
    await expect(page).toHaveURL('/dashboard');
    await expect(dashboardPage.welcomeMessage).toBeVisible();
  });

  test('should show error for invalid credentials', async () => {
    await loginPage.login('invalid@example.com', 'wrongpassword');
    
    const errorMessage = await loginPage.getErrorMessage();
    expect(errorMessage).toContain('Invalid credentials');
  });
});

Advanced Selector Strategies

// Preferred selector hierarchy
const selectors = {
  // 1. data-testid (most stable)
  submitButton: '[data-testid="submit-btn"]',
  
  // 2. Role-based selectors
  loginButton: 'button:has-text("Login")',
  navigation: 'nav[role="navigation"]',
  
  // 3. Semantic selectors
  emailInput: 'input[type="email"]',
  
  // 4. CSS with stable attributes
  userMenu: '.user-menu[aria-expanded="true"]'
};

// Dynamic selector building
const buildSelector = (type: string, value: string) => {
  return `[data-testid="${type}-${value}"]`;
};

Fixtures and Test Data Management

// fixtures/testData.ts
export const testUsers = {
  admin: {
    email: 'admin@example.com',
    [REDACTED],
    role: 'administrator'
  },
  user: {
    email: 'user@example.com',
    password: 'user123',
    role: 'user'
  }
};

// fixtures/authFixture.ts
import { test as base } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';

type AuthFixtures = {
  authenticatedPage: Page;
  loginPage: LoginPage;
};

export const test = base.extend<AuthFixtures>({
  authenticatedPage: async ({ page }, use) => {
    const loginPage = new LoginPage(page);
    await loginPage.navigateToLogin();
    await loginPage.login(testUsers.user.email, testUsers.user.password);
    await use(page);
  },
  
  loginPage: async ({ page }, use) => {
    const loginPage = new LoginPage(page);
    await use(loginPage);
  }
});

API Testing Integration

// tests/api/users.spec.ts
import { test, expect } from '@playwright/test';

test.describe('User API', () => {
  test('should create user via API and verify in UI', async ({ page, request }) => {
    // Create user via API
    const newUser = {
      name: 'John Doe',
      email: 'john@example.com'
    };
    
    const response = await request.post('/api/users', {
      data: newUser
    });
    
    expect(response.status()).toBe(201);
    const user = await response.json();
    
    // Verify in UI
    await page.goto('/users');
    await expect(page.locator(`text=${newUser.name}`)).toBeVisible();
  });
});

Configuration Best Practices

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  timeout: 30000,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  
  use: {
    baseURL: process.env.BASE_URL || 'http://localhost:3000',
    trace: 'retain-on-failure',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },
  
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'mobile',
      use: { ...devices['iPhone 12'] },
    },
  ],
  
  webServer: {
    command: 'npm run dev',
    port: 3000,
    reuseExistingServer: !process.env.CI,
  },
});

Error Handling and Debugging

// utils/testHelpers.ts
export class TestHelpers {
  static async waitForPageLoad(page: Page, timeout = 30000) {
    await page.waitForLoadState('networkidle', { timeout });
    await page.waitForLoadState('domcontentloaded', { timeout });
  }
  
  static async retryAction(action: () => Promise<void>, maxRetries = 3) {
    for (let i = 0; i < maxRetries; i++) {
      try {
        await action();
        return;
      } catch (error) {
        if (i === maxRetries - 1) throw error;
        await new Promise(resolve => setTimeout(resolve, 1000));
      }
    }
  }
  
  static async takeDebugScreenshot(page: Page, name: string) {
    await page.screenshot({ 
      path: `debug-screenshots/${name}-${Date.now()}.png`,
      fullPage: true 
    });
  }
}

Performance and Visual Testing

test('should meet performance benchmarks', async ({ page }) => {
  await page.goto('/');
  
  const performanceMetrics = await page.evaluate(() => {
    const navigation = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
    return {
      loadTime: navigation.loadEventEnd - navigation.loadEventStart,
      domContentLoaded: navigation.domContentLoadedEventEnd - navigation.domContentLoadedEventStart
    };
  });
  
  expect(performanceMetrics.loadTime).toBeLessThan(3000);
  expect(performanceMetrics.domContentLoaded).toBeLessThan(2000);
});

test('visual regression test', async ({ page }) => {
  await page.goto('/dashboard');
  await page.waitForLoadState('networkidle');
  
  await expect(page).toHaveScreenshot('dashboard.png', {
    fullPage: true,
    mask: [page.locator('[data-testid="timestamp"]')]
  });
});

Maintenance and Reporting

  • Use test.step() for better test reporting and debugging
  • Implement custom reporters for CI/CD integration
  • Regular selector maintenance and test review
  • Monitor test flakiness and execution time
  • Use tags for test categorization and selective execution
  • Implement proper cleanup in afterEach hooks
  • Consider parallel execution limitations and test isolation

FAQ

Common questions

Discussion

Questions & comments ยท 0

Sign In Sign in to leave a comment.