Skip to main content

Playwright Component Testing: setup completo

Article in the Playwright Component Testing Advanced series. Complete Playwright setup for component testing in Angular with fixtures, configuration and best practices.

What do you think about this article?

Playwright Component Testing: setup completo
Playwright ComponentsPhotos:Ruben BoekelooPexels

At a Glance

Article in the Playwright Component Testing Advanced series. Complete Playwright setup for component testing in Angular with fixtures, configuration and best practices.

Playwright Component Testing: why and how to get started

Playwright for component testing represents one of the most efficient ways to validate individual components in isolation, before integrating them into the complete application. Unlike integration or end-to-end tests, component testing focuses on a single UI component, interacts with it directly, and verifies that the behavior is correct. This article provides a complete setup to get started immediately.

💡 What you will learn

  • How to install and configure Playwright for component testing
  • Setup the playwright.config.ts file
  • Write your first component test
  • Using fixtures and helpers to simplify tests
  • Best practices for component testing at scale

Difference between Component Testing and E2E Testing

Component testing validates a single component in isolation. The component is rendered in a dedicated test page, and interacts directly via DOM without the entire application behind it. This makes tests faster, more deterministic, and easier to debug.

E2E testing, on the other hand, starts the complete application and simulates the user's real journey across entire pages. E2E is more "realistic" but also slower and more fragile.

Prerequisites

To follow this tutorial you need:

  • Node.js 16+ and npm/yarn
  • An Angular project (or React/Vue, Playwright supports all)
  • Basic familiarity with TypeScript and testing
  • Code editor (VS Code recommended)

Installing Playwright CT (Component Testing)

Playwright provides a dedicated package for component testing: @playwright/experimental-ct-angular (or for React/Vue). Install via npm:

Bash - Installing Playwright CT
npm install --save-dev @playwright/experimental-ct-angular @playwright/test

This will install the test runner, component mounting utilities, and all necessary tools. No need for browserify, webpack, or additional configuration — Playwright handles everything internally.

Configuring the playwright.config.ts File

Create (or update) the playwright.config.ts file in your project root:

TypeScript - playwright.config.ts
import {{ '{' }} defineConfig, devices {{ '}' }} from '@playwright/experimental-ct-angular';

export default defineConfig({{ '{' }}
  testDir: 'src/**/*.spec.ts',
  testMatch: '**/*.spec.ts',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: 'html',
  use: {{ '{' }}
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
  {{ '}' }},
  webServer: {{ '{' }}
    command: 'npm run ng serve',
    port: 4200,
    reuseExistingServer: !process.env.CI,
  {{ '}' }},
  projects: [
    {{ '{' }}
      name: 'chromium',
      use: {{ '{' }} ...devices['chromium'] {{ '}' }},
    {{ '}' }},
    {{ '{' }}
      name: 'firefox',
      use: {{ '{' }} ...devices['firefox'] {{ '}' }},
    {{ '}' }},
    {{ '{' }}
      name: 'webkit',
      use: {{ '{' }} ...devices['webkit'] {{ '}' }},
    {{ '}' }},
  ],
{{ '}' }});

The configuration enables:

  • testDir: Finds all tests in src folders
  • fullyParallel: Runs tests in parallel for speed
  • reporter: Generates interactive HTML reports
  • trace and screenshot: Records traces and screenshots on failure
  • projects: Tests on Chromium, Firefox, WebKit

Writing Your First Component Test

Create a sample component (e.g., src/app/button.component.ts):

TypeScript - button.component.ts
import {{ '{' }} Component, Input, Output, EventEmitter {{ '}' }} from '@angular/core';

@Component({{ '{' }}
  selector: 'app-button',
  standalone: true,
  template: `
    <button
      (click)="onClick()"
      [disabled]="isDisabled"
      class="btn"
    >
      {{ '{{' }} label {{ '}}' }}
    </button>
  `,
  styles: [`
    .btn {{ '{' }}
      padding: 8px 16px;
      cursor: pointer;
      background: #007bff;
      color: white;
      border: none;
      border-radius: 4px;
    {{ '}' }}
    .btn:disabled {{ '{' }}
      opacity: 0.5;
      cursor: not-allowed;
    {{ '}' }}
  `],
{{ '}' }})
export class ButtonComponent {{ '{' }}
  @Input() label: string = 'Click me';
  @Input() isDisabled: boolean = false;
  @Output() clicked = new EventEmitter<void>();

  onClick() {{ '{' }}
    this.clicked.emit();
  {{ '}' }}
{{ '}' }}

Now create the test file next to it (same folder):

TypeScript - button.component.spec.ts
import {{ '{' }} test, expect {{ '}' }} from '@playwright/experimental-ct-angular';
import {{ '{' }} ButtonComponent {{ '}' }} from './button.component';

test.describe('ButtonComponent', () => {{ '{' }}
  test('renders the label correctly', async ({{ '{' }} mount {{ '}' }}) => {{ '{' }}
    const component = await mount(ButtonComponent, {{ '{' }}
      props: {{ '{' }} label: 'Submit' {{ '}' }},
    {{ '}' }});

    await expect(component).toContainText('Submit');
  {{ '}' }});

  test('emits event on click', async ({{ '{' }} mount {{ '}' }}) => {{ '{' }}
    const component = await mount(ButtonComponent, {{ '{' }}
      props: {{ '{' }} label: 'Click' {{ '}' }},
    {{ '}' }});

    let eventCount = 0;
    component.on('clicked', () => {{ '{' }} eventCount++; {{ '}' }});

    await component.locator('button').click();
    expect(eventCount).toBe(1);
  {{ '}' }});

  test('disables the button when isDisabled is true', async ({{ '{' }} mount {{ '}' }}) => {{ '{' }}
    const component = await mount(ButtonComponent, {{ '{' }}
      props: {{ '{' }} isDisabled: true {{ '}' }},
    {{ '}' }});

    const button = component.locator('button');
    await expect(button).toBeDisabled();
  {{ '}' }});
{{ '}' }});

Run the tests with:

Bash - Running tests
npm run test:ct
# or
npx playwright test

Custom Fixtures and Helpers

To reduce code duplication, create custom fixtures. For example, a file src/fixtures.ts:

TypeScript - src/fixtures.ts
import {{ '{' }} test as base {{ '}' }} from '@playwright/experimental-ct-angular';

export const test = base.extend({{ '{' }}
  customMount: async ({{ '{' }} mount, page {{ '}' }}, use) => {{ '{' }}
    // Helper to check accessibility
    const mountWithA11y = async (component: any, options = {{ '}' }}) => {{ '{' }}
      const mounted = await mount(component, options);

      // Validates ARIA attributes
      const axeResults = await page.evaluate(() => {{ '{' }}
        return (window as any).axe?.run();
      {{ '}' }});

      return mounted;
    {{ '}' }};

    await use(mountWithA11y);
  {{ '}' }},
{{ '}' }});

export const expect = base.expect;

Best Practices for Component Testing at Scale

As tests grow, following these best practices prevents issues:

1. Modular Organization

Keep tests close to components:

src/
  ├── app/
  │   ├── button/
  │   │   ├── button.component.ts
  │   │   └── button.component.spec.ts
  │   ├── form/
  │   │   ├── form.component.ts
  │   │   └── form.component.spec.ts

2. Use Data Attributes for Selection

Avoid tying tests to CSS classes that change frequently. Use data-testid:

// Template
<button data-testid="submit-btn">Submit</button>

// Test
const submitBtn = component.locator('[data-testid="submit-btn"]');
await submitBtn.click();

3. Test the Interface, Not the Implementation

Test public behaviors and visible results, not internal details.

4. Keep Tests Isolated

Each test should be independent. Do not rely on state from previous tests.

Adding Scripts to package.json

Add these scripts to your package.json to facilitate execution:

JSON - package.json scripts
"test:ct": "playwright test --config=playwright.config.ts",
"test:ct:ui": "playwright test --config=playwright.config.ts --ui",
"test:ct:debug": "playwright test --config=playwright.config.ts --debug",
"test:ct:report": "playwright show-report"

CI/CD Integration

To integrate into GitHub Actions:

YAML - .github/workflows/test.yml
name: Playwright Tests
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm install
      - run: npm run test:ct

Common Troubleshooting

Problem: "mount is not defined"
Solution: Make sure you import test and expect from @playwright/experimental-ct-angular, not from @playwright/test directly.

Problem: Component does not render
Solution: Make sure the dev server is active. Verify the port in the webServer config.

Problem: Flaky test (intermittent)
Solution: Use explicit waitFor and avoid timeouts that are too short. Disable parallel execution if there are side effects.

FAQ

Is it necessary to use mocks in component testing?

Not always. If the component depends on services, you can pass mocks via dependency injection. Playwright CT fully supports Angular provider configuration.

Can I test components with Reactive Forms?

Yes. Test forms as you would normally. Playwright fully supports Reactive Forms and all validations.

What is the average time to run a component test suite?

It depends on the number of tests and parallelization. Typically 100-200 component tests run in 30-60 seconds on modern hardware.

Can I use Playwright CT for end-to-end tests?

Yes, but it's not its primary purpose. For full e2e tests, use standard @playwright/test which provides more browser control.

How do I incorporate Playwright CT into my CI/CD?

Add the npm run test:ct commands to your GitHub Actions, GitLab CI, or other workflows. Make sure the dev server is available during execution.

Commenti

Caricamento commenti...

Sign in to comment

Discuss this article in the community

Join the Telegram group to discuss with other developers, ask questions and share your experiences.

Did you like this article?

Explore other content on the blog or discover my projects