> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vibetunnel.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Playwright testing

# Playwright Testing Best Practices for VibeTunnel

## Overview

This guide documents best practices for writing reliable, non-flaky Playwright tests for VibeTunnel, based on official Playwright documentation and community best practices.

## Core Principles

### 1. Use Auto-Waiting Instead of Arbitrary Delays

**❌ Bad: Arbitrary timeouts**

```typescript theme={null}
await page.waitForTimeout(1000); // Don't do this!
```

**✅ Good: Wait for specific conditions**

```typescript theme={null}
// Wait for element to be visible
await page.waitForSelector('vibe-terminal', { state: 'visible' });

// Wait for loading indicator to disappear
await page.locator('.loading-spinner').waitFor({ state: 'hidden' });

// Wait for specific text to appear
await page.getByText('Session created').waitFor();
```

### 2. Use Web-First Assertions

Web-first assertions automatically wait and retry until the condition is met:

```typescript theme={null}
// These assertions auto-wait
await expect(page.locator('session-card')).toBeVisible();
await expect(page).toHaveURL(/\?session=/);
await expect(sessionCard).toContainText('RUNNING');
```

### 3. Prefer User-Facing Locators

**Locator Priority (best to worst):**

1. `getByRole()` - semantic HTML roles
2. `getByText()` - visible text content
3. `getByTestId()` - explicit test IDs
4. `locator()` with CSS - last resort

```typescript theme={null}
// Good examples
await page.getByRole('button', { name: 'Create Session' }).click();
await page.getByText('Session Name').fill('My Session');
await page.getByTestId('terminal-output').waitFor();
```

## VibeTunnel-Specific Patterns

### Waiting for Terminal Ready

Instead of arbitrary delays, wait for terminal indicators:

```typescript theme={null}
// Wait for terminal component to be visible
await page.waitForSelector('vibe-terminal', { state: 'visible' });

// Wait for terminal to have content or structure
await page.waitForFunction(() => {
  const terminal = document.querySelector('vibe-terminal');
  return terminal && (
    terminal.textContent?.trim().length > 0 ||
    !!terminal.shadowRoot ||
    !!terminal.querySelector('vibe-terminal')
  );
});
```

### Handling Session Creation

```typescript theme={null}
// Wait for navigation after session creation
await expect(page).toHaveURL(/\?session=/, { timeout: 2000 });

// Wait for terminal to be ready
await page.locator('vibe-terminal').waitFor({ state: 'visible' });
```

### Managing Modal Animations

Instead of waiting for animations, wait for the modal state:

```typescript theme={null}
// Wait for modal to be fully visible
await page.locator('[role="dialog"]').waitFor({ state: 'visible' });

// Wait for modal to be completely gone
await page.locator('[role="dialog"]').waitFor({ state: 'hidden' });
```

### Session List Updates

```typescript theme={null}
// Wait for session cards to update
await page.locator('session-card').first().waitFor();

// Wait for specific session by name
await page.locator(`session-card:has-text("${sessionName}")`).waitFor();
```

## Common Anti-Patterns to Avoid

### 1. Storing Element References

```typescript theme={null}
// ❌ Bad: Element reference can become stale
const button = await page.$('button');
await doSomething();
await button.click(); // May fail!

// ✅ Good: Re-query element when needed
await doSomething();
await page.locator('button').click();
```

### 2. Assuming Immediate Availability

```typescript theme={null}
// ❌ Bad: No waiting
await page.goto('/');
await page.click('session-card'); // May not exist yet!

// ✅ Good: Wait for element
await page.goto('/');
await page.locator('session-card').waitFor();
await page.locator('session-card').click();
```

### 3. Fixed Sleep for Dynamic Content

```typescript theme={null}
// ❌ Bad: Arbitrary wait for data load
await page.click('#load-data');
await page.waitForTimeout(3000);

// ✅ Good: Wait for loading state
await page.click('#load-data');
await page.locator('.loading').waitFor({ state: 'hidden' });
// Or wait for results
await page.locator('[data-testid="results"]').waitFor();
```

## Test Configuration

### Timeouts

Configure appropriate timeouts in `playwright.config.ts`:

```typescript theme={null}
use: {
  // Global timeout for assertions
  expect: { timeout: 5000 },
  
  // Action timeout (click, fill, etc.)
  actionTimeout: 10000,
  
  // Navigation timeout
  navigationTimeout: 10000,
}
```

### Test Isolation

Each test should be independent:

```typescript theme={null}
test.beforeEach(async ({ page }) => {
  // Fresh start for each test
  await page.goto('/');
  await page.waitForSelector('vibetunnel-app', { state: 'attached' });
});
```

## Debugging Flaky Tests

### 1. Enable Trace Recording

```typescript theme={null}
// In playwright.config.ts
use: {
  trace: 'on-first-retry',
}
```

### 2. Use Debug Mode

```bash theme={null}
# Run with headed browser and inspector
pnpm exec playwright test --debug
```

### 3. Add Strategic Logging

```typescript theme={null}
console.log('Waiting for terminal to be ready...');
await page.locator('vibe-terminal').waitFor();
console.log('Terminal is ready');
```

## Terminal-Specific Patterns

### Waiting for Terminal Output

```typescript theme={null}
// Wait for specific text in terminal
await page.waitForFunction(
  (searchText) => {
    const terminal = document.querySelector('vibe-terminal');
    return terminal?.textContent?.includes(searchText);
  },
  'Expected output'
);
```

### Waiting for Shell Prompt

```typescript theme={null}
// Wait for prompt patterns
await page.waitForFunction(() => {
  const terminal = document.querySelector('vibe-terminal');
  const content = terminal?.textContent || '';
  return /[$>#%❯]\s*$/.test(content);
});
```

### Handling Server-Side Terminals

When `spawnWindow` is false, terminals run server-side:

```typescript theme={null}
// Create session with server-side terminal
await sessionListPage.createNewSession(sessionName, false);

// Wait for WebSocket v3 connection
await page.locator('vibe-terminal').waitFor({ state: 'visible' });

// Terminal content comes through WebSocket - no need for complex waits
```

## Summary

1. **Never use `waitForTimeout()`** - always wait for specific conditions
2. **Use web-first assertions** that auto-wait
3. **Prefer semantic locators** over CSS selectors
4. **Wait for observable conditions** not arbitrary time
5. **Configure appropriate timeouts** for your application
6. **Keep tests isolated** and independent
7. **Use Playwright's built-in debugging tools** for flaky tests

By following these practices, tests will be more reliable, faster, and easier to maintain.
