npm package: @lytics/playwright-slack
Description: Slack notifications for Playwright test results with rich formatting and automatic section handling
Latest version: 0.3.1

# @lytics/playwright-slack

Slack notifications for Playwright test results with rich formatting and automatic section handling.

## Features

- 🎨 **Rich formatting** using Slack Block Kit via fluent MessageBuilder API
- 📊 **Test result summary** with pass/fail counts and percentages
- ❌ **Failed test details** with error messages and report links
- ⚠️ **Flaky test tracking** for tests that passed after retry
- 🔗 **Dashboard and action links** for quick access
- 🏷️ **Environment labels** (Production, Staging, etc.)
- 🎯 **Automatic section hiding** for empty results

## Installation

```bash
npm install @lytics/playwright-slack
# or
pnpm add @lytics/playwright-slack
# or
yarn add @lytics/playwright-slack
```

## Usage

### Basic Example

```typescript
import { PlaywrightNotifier } from "@lytics/playwright-slack";
import { SlackClient } from "@lytics/slack-client";

// Create Slack client
const slackClient = new SlackClient({
  webhook_url: process.env.SLACK_WEBHOOK_URL!,
});

// Create notifier with environment label
const notifier = new PlaywrightNotifier(slackClient, {
  environment: "Production",
});

// Send test results
await notifier.sendTestResults({
  total: 50,
  passed: 45,
  failed: [
    {
      name: "Login Flow",
      error: "Timeout waiting for element .submit-button",
      reportUrl: "https://dashboard.com/reports/login-flow",
    },
    {
      name: "Checkout Process",
      error: "Assertion failed: expected 200, got 500",
      reportUrl: "https://dashboard.com/reports/checkout",
    },
  ],
  flaky: [
    {
      name: "Search Results",
      retries: 2,
    },
  ],
  duration: 120,
  trigger: "schedule",
  dashboardUrl: "https://dashboard.com",
  actionUrl: "https://github.com/org/repo/actions/runs/123",
});
```

### Integration with Playwright

```typescript
import { test, expect } from "@playwright/test";
import { PlaywrightNotifier } from "@lytics/playwright-slack";
import { SlackClient } from "@lytics/slack-client";

// After test run
test.afterAll(async () => {
  // Parse Playwright test results
  const results = {
    total: testResults.allTests().length,
    passed: testResults.passed().length,
    failed: testResults.failed().map((test) => ({
      name: test.title,
      error: test.error?.message || "Unknown error",
      reportUrl: `https://dashboard.com/reports/${test.id}`,
    })),
    flaky: testResults.flaky().map((test) => ({
      name: test.title,
      retries: test.results.length - 1,
    })),
    duration: Math.round(testResults.duration / 1000),
    trigger: process.env.GITHUB_EVENT_NAME || "manual",
    actionUrl: process.env.GITHUB_SERVER_URL
      ? `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`
      : undefined,
  };

  // Send to Slack
  const slackClient = new SlackClient({
    webhook_url: process.env.SLACK_WEBHOOK_URL!,
  });

  const notifier = new PlaywrightNotifier(slackClient, {
    environment: process.env.TEST_ENV || "Test",
  });

  await notifier.sendTestResults(results);
});
```

### GitHub Actions Integration

```yaml
name: E2E Tests

on:
  schedule:
    - cron: "0 9 * * 1-5" # 9 AM weekdays
  workflow_dispatch:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 22

      - name: Install dependencies
        run: pnpm install

      - name: Run Playwright tests
        run: pnpm exec playwright test

      - name: Send Slack notification
        if: always()
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
          TEST_ENV: Production
        run: node notify-results.js
```

## Custom Formatters

The package supports custom message formatting through the formatter pattern. This allows you to customize the message layout while maintaining clean, testable code.

### Using Default Formatter (with skipped/flaky counts)

The default formatter now includes skipped and flaky test counts in the summary:

```typescript
await notifier.sendTestResults({
  total: 62,
  passed: 61,
  failed: [],
  flaky: [{ name: 'Flaky Test', retries: 1 }],
  skipped: 1,
  duration: 643,
  trigger: 'schedule',
  dashboardUrl: 'https://dashboard.com',
});

// Summary: "61/62 passed (98%) • 1 skipped • 1 flaky • ⏱️ 643s • View Dashboard"
```

### Extending Default Formatter

Override specific formatting methods:

```typescript
import { DefaultPlaywrightFormatter, PlaywrightNotifier } from '@lytics/playwright-slack';
import { SlackClient } from '@lytics/slack-client';

class MyFormatter extends DefaultPlaywrightFormatter {
  // Customize the summary line
  protected formatSummary(results: TestResults): string {
    const percent = Math.round((results.passed / results.total) * 100);
    
    const parts = [
      `${results.passed}/${results.total} passed (${percent}%)`,
    ];
    
    if (results.skipped > 0) {
      parts.push(`${results.skipped} skipped`);
    }
    
    if (results.flaky.length > 0) {
      parts.push(`${results.flaky.length} flaky`);
    }
    
    parts.push(`:stopwatch: ${results.duration}s`);
    
    if (results.dashboardUrl) {
      parts.push(`<${results.dashboardUrl}|View Dashboard>`);
    }
    
    return parts.join(' • ');
  }
  
  // Customize the header
  protected formatHeader(results: TestResults, options?: PlaywrightNotifierOptions): string {
    const emoji = results.failed.length === 0 ? '🎭' : '💥';
    return `${emoji} Custom Test Report - ${options?.environment || 'Dev'}`;
  }
}

const slackClient = new SlackClient({
  webhook_url: process.env.SLACK_WEBHOOK_URL!,
});

const notifier = new PlaywrightNotifier(
  slackClient,
  { environment: 'Production' },
  new MyFormatter()  // Use custom formatter
);

await notifier.sendTestResults(results);
```

### Complete Custom Formatter

For complete control, implement the `PlaywrightFormatter` interface:

```typescript
import { PlaywrightFormatter, type TestResults } from '@lytics/playwright-slack';
import { MessageBuilder, type SlackMessage } from '@lytics/slack-client';

class RadicallyDifferentFormatter implements PlaywrightFormatter {
  formatMessage(results: TestResults): SlackMessage {
    return new MessageBuilder()
      .setHeader('🎭 My Custom Format')
      .setSummary(`Results: ${results.passed}/${results.total}`)
      .addSection(
        'Details',
        'Custom section',
        [`${results.failed.length} failed`, `${results.flaky.length} flaky`]
      )
      .setFooter('_Custom footer_')
      .build();
  }
}

const notifier = new PlaywrightNotifier(
  slackClient,
  options,
  new RadicallyDifferentFormatter()
);
```

### Available Protected Methods

When extending `DefaultPlaywrightFormatter`, you can override:

- `formatHeader(results, options)` - Message header with status emoji
- `formatSummary(results)` - Pass/fail summary with counts and links
- `formatFailedTests(tests)` - Failed test list formatting
- `formatFlakyTests(tests)` - Flaky test list formatting
- `formatFooter(results)` - Footer with trigger and links
- `formatFallbackText(results)` - Plain text fallback for notifications

## API Reference

### `PlaywrightNotifier`

Main class for sending Playwright test results to Slack.

#### Constructor

```typescript
new PlaywrightNotifier(
  slackClient: SlackClient,
  options?: PlaywrightNotifierOptions,
  formatter?: PlaywrightFormatter
)
```

**Parameters:**

- `slackClient`: SlackClient instance for sending messages
- `options.environment`: Optional environment label (e.g., 'Production', 'Staging')
- `formatter`: Optional custom formatter (defaults to `DefaultPlaywrightFormatter`)

#### Methods

##### `sendTestResults(results: TestResults): Promise<void>`

Send test results notification to Slack.

**Parameters:**

- `results`: Test results data (see [TestResults](#testresults))

**Example:**

```typescript
await notifier.sendTestResults({
  total: 50,
  passed: 45,
  failed: [...],
  flaky: [...],
  skipped: 2,
  duration: 120,
  trigger: 'schedule',
});
```

## Types

### `TestResults`

```typescript
interface TestResults {
  /** Total number of tests */
  total: number;

  /** Number of passed tests */
  passed: number;

  /** Array of failed test details */
  failed: FailedTest[];

  /** Array of flaky test details (passed after retry) */
  flaky: FlakyTest[];

  /** Number of skipped tests */
  skipped: number;

  /** Test duration in seconds */
  duration: number;

  /** What triggered the test run */
  trigger: string;

  /** Optional URL to test dashboard */
  dashboardUrl?: string;

  /** Optional URL to GitHub Actions run */
  actionUrl?: string;
}
```

### `FailedTest`

```typescript
interface FailedTest {
  /** Test name */
  name: string;

  /** Error message or stack trace */
  error: string;

  /** Optional URL to detailed test report */
  reportUrl?: string;
}
```

### `FlakyTest`

```typescript
interface FlakyTest {
  /** Test name */
  name: string;

  /** Number of retries before passing */
  retries: number;
}
```

### `PlaywrightNotifierOptions`

```typescript
interface PlaywrightNotifierOptions {
  /** Environment label (e.g., 'Production', 'Staging', 'Development') */
  environment?: string;
}
```

## Message Format

The generated Slack message includes:

### Header

- ✅ Success emoji (all tests pass) or ❌ failure emoji
- Environment label
- Example: "✅ E2E Test Results - Production"

### Summary

- Pass/fail counts with percentage
- Skipped count (if present)
- Flaky count (if present)
- Test duration
- Dashboard link (if provided)
- Example: "61/62 passed (98%) • 1 skipped • 1 flaky • ⏱️ 643s • View Dashboard"

### Failed Tests Section (auto-hidden if empty)

- Test name (bold)
- Error message
- Link to detailed report (if provided)

### Flaky Tests Section (auto-hidden if empty)

- Test name (bold)
- Number of retries before passing

### Footer

- Trigger source
- GitHub Actions link (if provided)
- Example: "_Triggered by schedule • GitHub Actions_"

## Testing

Run tests:

```bash
pnpm test
```

Run tests in watch mode:

```bash
pnpm test:watch
```

## Development

Build the package:

```bash
pnpm build
```

Type check:

```bash
pnpm typecheck
```

Lint:

```bash
pnpm lint
```

## Related Packages

- [`@lytics/slack-client`](https://www.npmjs.com/package/@lytics/slack-client) - Generic Slack client with fluent MessageBuilder API

## License

MIT