Skip to the content.

Go to Main Page »

Get Started with Playwright-elements

Build maintainable, type-safe end-to-end tests with Playwright

Playwright-elements is a powerful TypeScript framework that extends Playwright with enterprise-grade testing capabilities. Whether you’re building a small project or a large-scale application, this guide will help you get started quickly.


Who Is This For?

Playwright-elements is designed for:

Role Benefit
QA Engineers Write maintainable tests with reusable components
Frontend Developers Test React/Vue/Angular apps with component-driven testing
Test Automation Engineers Build scalable test suites with clean architecture
Technical Leads Enforce type safety and best practices across the team

You should use playwright-elements if:


Prerequisites

Before you begin, ensure you have:

  1. Node.js 18+ installed
  2. npm or yarn package manager
  3. Basic knowledge of TypeScript
  4. Familiarity with Playwright basics (recommended but not required)

If you’re new to Playwright, we recommend completing the Playwright Getting Started first.


Installation

1. Install Playwright (if not already installed)

npm init playwright@latest

This will create a new Playwright project with the basic configuration.

2. Install playwright-elements

npm install -D playwright-elements

Your First Test

Let’s create a simple test to understand the core concepts.

Step 1: Create a Page Object

Create a new file pages/login.page.ts:

// pages/login.page.ts
import { $ } from 'playwright-elements';

export class LoginPage {
  // Define elements using CSS selectors
  readonly usernameField = $('input[name="username"]');
  readonly passwordField = $('input[name="password"]');
  readonly submitButton = $('button[type="submit"]');
  readonly errorMessage = $('.error-message');

  // Add custom methods for reusable actions
  async login(username: string, password: string) {
    await this.usernameField.fill(username);
    await this.passwordField.fill(password);
    await this.submitButton.click();
  }

  async getErrorMessage() {
    return await this.errorMessage.textContent();
  }
}

What’s happening here?

Step 2: Create a Test

Create a new test file tests/login.spec.ts:

// tests/login.spec.ts
import { test } from 'playwright-elements';
import { LoginPage } from '../pages/login.page';

test.describe('Login Page', () => {
  test('should login successfully with valid credentials', async ({ goto }) => {
    // Navigate to your login page
    await goto('/login');

    // Create page object instance
    const loginPage = new LoginPage();

    // Use the custom method
    await loginPage.login('admin', 'password123');

    // Make assertions
    await loginPage.errorMessage.expect().toBeHidden();
  });

  test('should show error with invalid credentials', async ({ goto }) => {
    await goto('/login');

    const loginPage = new LoginPage();
    await loginPage.login('admin', 'wrongpassword');

    // Assert error message is visible
    await loginPage.errorMessage.expect().toBeVisible();
    await loginPage.errorMessage.expect().toContainText('Invalid credentials');
  });
});

Key points:

Step 3: Run Your Test

npx playwright test

Core Concepts Explained

WebElement

WebElement is the foundation of playwright-elements. It’s a wrapper around Playwright’s Locator that provides:

// Simple element
const button = $('button.submit');

// With sub-elements
const form = $('.login-form').with({
  username: $('input[name="username"]'),
  password: $('input[name="password"]'),
  submit: $('button[type="submit"]'),
});

// Access sub-elements with full type safety
await form.username.fill('admin');
await form.submit.click();

The with() Method

The most powerful feature of playwright-elements is the ability to create nested component structures that mirror your application’s DOM.

// Multi-level hierarchy
const app = $('.app').with({
  header: $('.header').with({
    logo: $('.logo'),
    navigation: $('.nav').with({
      items: $('.nav-item')
    })
  }),
  main: $('.main-content'),
  footer: $('.footer')
});

// Usage: Chain through the hierarchy
await app.header.navigation.items.first().click();

Benefits:

Type-Safe Test IDs (v1.19.0+)

For even better type safety, use the Test IDs module:

// testIds.ts
import { factory, sid } from 'playwright-elements';

export const ids = {
  login: {
    username: sid<'login.username'>('username-input'),
    password: sid<'login.password'>('password-input'),
    submit: sid<'login.submit'>('submit-button'),
  },
  button: factory<'button'>('btn')
} as const;

// Use in components (React example)
import { testIdProps } from 'playwright-elements';
import { ids } from './testIds';

function LoginForm() {
  return (
    <form>
      <input {...testIdProps(ids.login.username)} type="text" />
      <input {...testIdProps(ids.login.password)} type="password" />
      <button {...testIdProps(ids.login.submit)} type="submit">Login</button>
    </form>
  );
}

// Use in tests
import { $byTestId } from 'playwright-elements';

const usernameField = $byTestId(ids.login.username);
await usernameField.fill('admin');

Benefits:


Project Structure Recommendations

Here’s a recommended project structure for medium to large projects:

project/
├── src/
│   └── app/                    # Your application code
│
├── test/
│   ├── fixtures/               # Custom test fixtures
│   │   └── index.ts            # Main fixtures file
│   │
│   ├── pages/                  # Page objects
│   │   ├── login.page.ts       # Login page
│   │   ├── dashboard.page.ts    # Dashboard page
│   │   └── index.ts            # Auto-generated index
│   │
│   ├── components/             # Reusable components
│   │   ├── header.component.ts
│   │   ├── form.component.ts
│   │   └── index.ts
│   │
│   ├── testIds/                # Test ID definitions
│   │   └── index.ts
│   │
│   ├── utils/                  # Test utilities
│   │   └── helpers.ts
│   │
│   └── specs/                  # Test files
│       ├── login.spec.ts
│       ├── dashboard.spec.ts
│       └── api.spec.ts
│
├── playwright.config.ts        # Playwright configuration
└── package.json

Setting Up Fixtures

Create a central fixtures file:

// test/fixtures/index.ts
import { test as baseTest, buildPageObject, PageObject } from 'playwright-elements';
import * as pageObjectModule from '../pages';

type TestFixtures = {
  pageObject: PageObject<typeof pageObjectModule>;
};

export const test = baseTest.extend<TestFixtures>({
  pageObject: [
    async ({}, use) => {
      await use(buildPageObject(pageObjectModule));
    },
    { scope: 'test' }
  ],
});

// Re-export everything you need
export * from 'playwright-elements';

Now you can use page objects with full autocompletion in your tests:

// tests/dashboard.spec.ts
import { test } from '../fixtures';

test('dashboard navigation', async ({ pageObject }) => {
  // Full autocompletion for all pages
  await pageObject.login.login('admin', 'password');
  await pageObject.dashboard.navigateTo('settings');
});

Next Steps

Now that you have the basics, explore these topics:

Topic Description Time
WebElement Deep Dive Master all WebElement features and methods 30 min
Test IDs Module Learn the type-safe test ID system 20 min
Page Object Pattern Build scalable page objects 25 min
Fixtures & Setup Configure your test environment 20 min
Architecture & Patterns Advanced patterns and best practices 45 min

Need Help?


Go to Main Page »