Back to catalog

playwright-basics

Core Playwright concepts, setup, and test execution for E2E testing

  • Category: discovered
  • Tags: github, community
  • Version: 1.0.0
  • Author: walid
  • Downloads: 0
  • GitHub stars: 1
  • Updated: 2025-11-21

Download the latest version (v1.0.0) from this page.

SKILL.md

---
name: playwright-basics
description: Core Playwright concepts, setup, and test execution for E2E testing
---

# Playwright Basics

Playwright is a Node.js library for end-to-end testing of web applications. This guide covers the essentials for testing the Clientt CRM Phoenix application.

## Project Structure

The Playwright tests are located in the `playwright_tests/` directory at the project root, independent of the Phoenix application:

```
playwright_tests/
├── tests/
│   ├── features/            # Feature-based tests (BDD scenarios)
│   ├── examples/            # Example test patterns
│   ├── support/             # Helper functions and page objects
│   └── fixtures/            # Custom test fixtures
├── playwright.config.js     # Configuration
├── package.json            # Dependencies
└── README.md               # Documentation
```

## Running Tests

All commands should be run from the `playwright_tests` directory:

```bash
# Run all tests
npm test

# Run with browser UI visible
npm run test:headed

# Interactive UI mode
npm run test:ui

# Debug mode
npm run test:debug

# Run specific test file
npx playwright test tests/features/authentication.spec.js

# Run specific browser
npx playwright test --project=chromium
```

## Basic Test Structure

```javascript
// @ts-check
const { test, expect } = require('@playwright/test');

test.describe('Feature Name', () => {

  test('should do something', async ({ page }) => {
    // Navigate to page
    await page.goto('/some-path');

    // Interact with elements
    await page.fill('input[name="email"]', 'test@example.com');
    await page.click('button[type="submit"]');

    // Wait for navigation/network
    await page.waitForLoadState('networkidle');

    // Make assertions
    await expect(page).toHaveURL(/.*dashboard/);
    await expect(page.locator('h1')).toContainText('Welcome');
  });
});
```

## Common Playwright Operations

### Navigation

```javascript
// Navigate to URL
await page.goto('/sign-in');

// Navigate with options
await page.goto('/dashboard', { waitUntil: 'networkidle' });

// Go back/forward
await page.goBack();
await page.goForward();

// Reload
await page.reload();
```

### Finding Elements

```javascript
// CSS selectors
await page.locator('.class-name')
await page.locator('#id')
await page.locator('button[type="submit"]')

// Text content
await page.locator('text=Sign In')
await page.locator('text=/log.*out/i')  // Regex

// Data attributes (preferred)
await page.locator('[data-test="submit-button"]')

// Get multiple elements
const items = await page.locator('.list-item').all();
```

### Interactions

```javascript
// Click
await page.click('button');

// Fill input
await page.fill('input[name="email"]', 'test@example.com');

// Type (with delay between keystrokes)
await page.type('input', 'Hello', { delay: 100 });

// Check/uncheck
await page.check('input[type="checkbox"]');
await page.uncheck('input[type="checkbox"]');

// Select dropdown
await page.selectOption('select#country', 'USA');

// Upload file
await page.setInputFiles('input[type="file"]', 'path/to/file');

// Keyboard
await page.keyboard.press('Enter');
await page.keyboard.type('Hello World');
```

### Waiting

```javascript
// Wait for load state
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');

// Wait for selector
await page.waitForSelector('.result');
await page.waitForSelector('.loading', { state: 'hidden' });

// Wait for URL
await page.waitForURL('**/dashboard');
await page.waitForURL(/.*success/);

// Wait for timeout (avoid if possible)
await page.waitForTimeout(1000);
```

### Assertions

```javascript
const { expect } = require('@playwright/test');

// Page assertions
await expect(page).toHaveURL(/.*dashboard/);
await expect(page).toHaveTitle(/CRM/);

// Element assertions
await expect(page.locator('h1')).toBeVisible();
await expect(page.locator('.error')).toBeHidden();
await expect(page.locator('h1')).toContainText('Welcome');
await expect(page.locator('input')).toHaveValue('test@example.com');
await expect(page.locator('button')).toBeEnabled();
await expect(page.locator('button')).toBeDisabled();

// Count
await expect(page.locator('.item')).toHaveCount(5);
```

## Configuration

The `playwright.config.js` file contains:

- **baseURL**: Default URL for the application (`http://localhost:4002`)
- **timeout**: Maximum test execution time (30 seconds)
- **projects**: Browser configurations (chromium, firefox, webkit)
- **webServer**: Automatically starts Phoenix server before tests
- **use**: Shared settings (screenshots, videos, traces)

### Required Viewport and Screenshot Settings

**IMPORTANT**: Always configure 1080p viewport in playwright.config.js:

```javascript
module.exports = defineConfig({
  use: {
    // 1080p viewport for all screenshots
    viewport: { width: 1920, height: 1080 },

    // Screenshot on failure (viewport only, not full page)
    screenshot: {
      mode: 'only-on-failure',
      fullPage: false,  // MUST be false
    },
  },

  projects: [
    {
      name: 'chromium',
      // Explicit viewport to override device presets
      use: { ...devices['Desktop Chrome'], viewport: { width: 1920, height: 1080 } },
    },
  ],
});
```

**Why this matters:**
- Device presets (like `devices['Desktop Chrome']`) may override global viewport settings
- Always explicitly set viewport in each project configuration
- `fullPage: false` ensures screenshots stay at 1920x1080 max

## Environment Variables

Create a `.env` file in `playwright_tests/`:

```env
BASE_URL=http://localhost:4002
TEST_USER_EMAIL=test@example.com
TEST_USER_PASSWORD=SecurePassword123!
```

Access in tests:

```javascript
const email = process.env.TEST_USER_EMAIL;
```

## Test Hooks

```javascript
test.describe('Feature', () => {

  // Runs once before all tests
  test.beforeAll(async () => {
    // Setup code
  });

  // Runs before each test
  test.beforeEach(async ({ page }) => {
    await page.goto('/');
  });

  // Runs after each test
  test.afterEach(async ({ page }) => {
    // Cleanup
  });

  // Runs once after all tests
  test.afterAll(async () => {
    // Teardown code
  });

  test('my test', async ({ page }) => {
    // Test code
  });
});
```

## Debugging

### Debug Mode

```bash
npm run test:debug
```

This opens Playwright Inspector for stepping through tests.

### Console Logs

```javascript
// Listen to console messages
page.on('console', msg => console.log('PAGE LOG:', msg.text()));

// Listen to page errors
page.on('pageerror', error => console.log('PAGE ERROR:', error));
```

### Screenshots

**IMPORTANT**: All screenshots must be captured at 1080p (1920x1080) viewport only. Do NOT use fullPage: true.

```javascript
// Take screenshot at 1080p viewport (CORRECT)
await page.screenshot({
  path: 'screenshots/my-screenshot.png',
  fullPage: false  // Always use false to capture viewport only
});

// Helper function pattern for consistent screenshots
async function screenshot(page, name) {
  await page.screenshot({
    path: path.join(__dirname, 'screenshots', `${name}.png`),
    fullPage: false  // Never use fullPage: true
  });
}
```

**Screenshot Configuration Rules:**
- Always use `fullPage: false` to capture viewport only (1920x1080)
- Screenshots should never exceed 8000 pixels in any dimension
- Viewport is set in playwright.config.js as `{ width: 1920, height: 1080 }`

### Pause Execution

```javascript
// Pause in headed mode
await page.pause();
```

## Best Practices

1. **Use baseURL**: Configure in `playwright.config.js` and use relative paths
2. **Wait for network idle**: After navigation and form submissions
3. **Use data-test attributes**: More stable than CSS classes
4. **Explicit waits**: Use `waitForSelector` instead of `waitForTimeout`
5. **Isolate tests**: Each test should be independent
6. **Clean up**: Remove test data after tests (or use transactions)
7. **Use fixtures**: For common setup like authentication

## Common Patterns

### Form Submission

```javascript
await page.fill('input[name="email"]', 'test@example.com');
await page.fill('input[name="password"]', 'password');
await page.click('button[type="submit"]');
await page.waitForLoadState('networkidle');
```

### Authentication Check

```javascript
// Check if logged in
const isLoggedIn = !page.url().includes('sign-in');
```

### Multiple Windows/Tabs

```javascript
const [newPage] = await Promise.all([
  context.waitForEvent('page'),
  page.click('a[target="_blank"]')
]);

await newPage.waitForLoadState();
```

## Troubleshooting

### Test Timeouts

- Increase timeout in config: `timeout: 60 * 1000`
- Add explicit waits: `await page.waitForLoadState('networkidle')`

### Element Not Found

- Use Playwright Inspector: `npm run test:debug`
- Check selector in browser DevTools
- Add waits: `await page.waitForSelector('.element')`

### Flaky Tests

- Use proper waits (networkidle, selector visible)
- Avoid fixed timeouts
- Check for race conditions
- Ensure test isolation

## Resources

- [Playwright Documentation](https://playwright.dev)
- [API Reference](https://playwright.dev/docs/api/class-playwright)
- [Best Practices](https://playwright.dev/docs/best-practices)
- Project README: `playwright_tests/README.md`