* feat: replace request send icon with Send/Cancel buttons --------- Co-authored-by: naman-bruno <naman@usebruno.com>
12 KiB
Playwright Testing Guide for Bruno
This guide explains how to create and run Playwright test cases for the Bruno application using the UI.
Table of Contents
- Overview
- Prerequisites
- Creating Tests Using Codegen
- Manual Test Creation
- Test Structure and Organization
- Available Test Fixtures
- Running Tests
- Best Practices
- Examples
- Troubleshooting
Overview
Bruno uses Playwright for end-to-end testing of its Electron application. The testing setup includes custom fixtures for Electron app testing and utilities for managing test data.
Prerequisites
- Node.js installed
- All dependencies installed (
npm install) - Electron app can be built and run
Creating Tests Using Codegen
The easiest way to create tests is using Playwright's codegen feature, which records your UI interactions and generates test code.
Using the Built-in Codegen Script
# Generate a test with a specific name
npm run test:codegen my-new-test
# Generate a test without specifying a name (will prompt for input)
npm run test:codegen
What Happens During Codegen
- The Electron app launches automatically
- Playwright Inspector opens in a separate window
- You interact with the Bruno UI
- Actions are recorded and converted to test code
- The generated test file is saved in
e2e-tests/
Codegen Workflow
- Start Recording: Run the codegen command
- Interact with UI: Perform the actions you want to test
- Add Assertions: Use the inspector to add assertions
- Save Test: The test file is automatically generated
- Review and Refine: Edit the generated test as needed
Manual Test Creation
You can also create tests manually by following the established patterns.
Basic Test Structure
import { test, expect } from '../../playwright';
test('Test description', async ({ page }) => {
// Test steps here
await page.getByLabel('Some Label').click();
// Assertions
await expect(page.getByText('Expected Text')).toBeVisible();
});
Test with Temporary Data
import { test, expect } from '../../playwright';
test('Test with temporary data', async ({ page, createTmpDir }) => {
// Create temporary directory for test data
const testDir = await createTmpDir('test-collection');
// Test steps
await page.getByLabel('Create Collection').click();
await page.getByLabel('Name').fill('test-collection');
await page.getByLabel('Location').fill(testDir);
// Assertions
await expect(page.getByText('test-collection')).toBeVisible();
});
Test Structure and Organization
Directory Structure
e2e-tests/
├── 001-sanity-tests/ # Basic functionality tests
│ ├── 001-home-screen.spec.ts
│ └── 002-create-new-collection-and-new-request.spec.ts
├── 002-feature-tests/ # Specific feature tests
├── 003-integration-tests/ # Complex workflow tests
└── bruno-testbench/ # Test utilities and helpers
Naming Conventions
- Files: Use descriptive names with
.spec.tsextension - Tests: Use clear, descriptive test names
- Folders: Use numbered prefixes for ordering
Test File Template
import { test, expect } from '../../playwright';
test.describe('Feature Name', () => {
test('should perform specific action', async ({ page }) => {
// Arrange
// Act
// Assert
});
test('should handle error case', async ({ page }) => {
// Test error scenarios
});
});
Available Test Fixtures
The Bruno Playwright setup provides several custom fixtures:
Core Fixtures
page: Main page for testingcontext: Browser contextelectronApp: Electron application instance
Utility Fixtures
createTmpDir: Creates temporary directories for test datanewPage: Creates a new page instancepageWithUserData: Page with custom user datalaunchElectronApp: Launches a new Electron app instancereuseOrLaunchElectronApp: Reuses existing app or launches new one
Using Fixtures
test('Test with multiple fixtures', async ({ page, createTmpDir, electronApp }) => {
const testDir = await createTmpDir('test-data');
// Your test logic here
});
Running Tests
Basic Commands
# Run all tests
npm run test:e2e
# Run specific test file
npx playwright test e2e-tests/001-sanity-tests/001-home-screen.spec.ts
# Run tests in a specific folder
npx playwright test e2e-tests/001-sanity-tests/
Advanced Options
# Run with UI mode (for debugging)
npx playwright test --ui
# Run in headed mode (see browser)
npx playwright test --headed
# Run with specific browser
npx playwright test --project="Bruno Electron App"
# Run with debugging
npx playwright test --debug
# Run with trace recording
npx playwright test --trace on
CI/CD Integration
# Install browsers for CI
npx playwright install
# Run tests in CI mode
npm run test:e2e
Best Practices
1. Use Semantic Selectors
Preferred:
await page.getByRole('button', { name: 'Create' }).click();
await page.getByLabel('Collection Name').fill('test');
await page.getByText('Success message').toBeVisible();
Avoid:
await page.locator('.btn-primary').click();
await page.locator('#collection-name').fill('test');
2. Create Isolated Tests
Each test should be independent and not rely on other tests:
test('should create collection', async ({ page, createTmpDir }) => {
const testDir = await createTmpDir('collection-test');
// Test creates its own data
await page.getByLabel('Create Collection').click();
await page.getByLabel('Name').fill('test-collection');
await page.getByLabel('Location').fill(testDir);
// Clean up happens automatically via createTmpDir
});
3. Add Meaningful Assertions
Always verify the expected outcomes:
test('should save request successfully', async ({ page }) => {
// Arrange
await page.getByLabel('Create Collection').click();
// Act
await page.getByRole('button', { name: 'Save' }).click();
// Assert
await expect(page.getByText('Request saved successfully')).toBeVisible();
await expect(page.getByRole('tab', { name: 'GET request' })).toBeVisible();
});
4. Handle Async Operations
test('should wait for network requests', async ({ page }) => {
// Wait for specific network request
await page.waitForResponse((response) => response.url().includes('/api/endpoint'));
// Or wait for element to be stable
await page.waitForSelector('[data-testid="loading"]', { state: 'hidden' });
});
5. Use Test Data Management
test('should work with test data', async ({ page, createTmpDir }) => {
const testDir = await createTmpDir('test-data');
// Create test files
await fs.writeFile(path.join(testDir, 'test.bru'), testContent);
// Use in test
await page.getByLabel('Open Collection').click();
await page.getByText(testDir).click();
});
Examples
Example 1: Basic Collection Creation
import { test, expect } from '../../playwright';
test('should create a new collection', async ({ page, createTmpDir }) => {
const testDir = await createTmpDir('new-collection');
await page.getByLabel('Create Collection').click();
await page.getByLabel('Name').fill('My Test Collection');
await page.getByLabel('Location').fill(testDir);
await page.getByRole('button', { name: 'Create' }).click();
await expect(page.getByText('My Test Collection')).toBeVisible();
});
Example 2: Request Creation and Execution
import { test, expect } from '../../playwright';
test('should create and execute HTTP request', async ({ page, createTmpDir }) => {
const testDir = await createTmpDir('request-test');
// Create collection
await page.getByLabel('Create Collection').click();
await page.getByLabel('Name').fill('Request Test');
await page.getByLabel('Location').fill(testDir);
await page.getByRole('button', { name: 'Create' }).click();
// Create request
await page.locator('#create-new-tab').getByRole('img').click();
await page.getByPlaceholder('Request Name').fill('Test Request');
await page.locator('#new-request-url .CodeMirror').click();
await page.locator('textarea').fill('http://localhost:8081/ping');
await page.getByRole('button', { name: 'Create' }).click();
// Execute request
await page.getByTestId('send-arrow-icon').click();
// Verify response
await expect(page.getByRole('main')).toContainText('200 OK');
});
Example 3: Environment Management
import { test, expect } from '../../playwright';
test('should create and use environment variables', async ({ page, createTmpDir }) => {
const testDir = await createTmpDir('env-test');
// Setup collection
await page.getByLabel('Create Collection').click();
await page.getByLabel('Name').fill('Environment Test');
await page.getByLabel('Location').fill(testDir);
await page.getByRole('button', { name: 'Create' }).click();
// Create environment
await page.getByRole('button', { name: 'Environments' }).click();
await page.getByRole('button', { name: 'Add Environment' }).click();
await page.getByLabel('Environment Name').fill('Development');
await page.getByRole('button', { name: 'Create' }).click();
// Add variable
await page.getByRole('button', { name: 'Add Variable' }).click();
await page.getByLabel('Variable Name').fill('API_URL');
await page.getByLabel('Variable Value').fill('http://localhost:3000');
await page.getByRole('button', { name: 'Save' }).click();
await expect(page.getByText('API_URL')).toBeVisible();
});
Troubleshooting
Common Issues
-
Electron App Not Starting
# Ensure dependencies are installed npm install # Try running the app manually first npm run dev:electron -
Tests Timing Out
// Increase timeout for specific test test('slow test', async ({ page }) => { test.setTimeout(60000); // 60 seconds // Test steps }); -
Element Not Found
// Wait for element to be present await page.waitForSelector('[data-testid="element"]'); // Or use more specific selectors await page.getByRole('button', { name: 'Exact Button Text' }).click(); -
Flaky Tests
// Use stable selectors await page.getByTestId('stable-id').click(); // Wait for state changes await page.waitForLoadState('networkidle');
Debug Mode
# Run with debug mode
npx playwright test --debug
# Run specific test in debug mode
npx playwright test --debug e2e-tests/001-sanity-tests/001-home-screen.spec.ts
Trace Analysis
# Run with trace recording
npx playwright test --trace on
# View trace in browser
npx playwright show-trace test-results/trace-*.zip
Configuration
The Playwright configuration is in playwright.config.ts:
export default defineConfig({
testDir: './e2e-tests',
fullyParallel: false,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 1 : 0,
workers: process.env.CI ? undefined : 1,
projects: [
{
name: 'Bruno Electron App'
}
],
webServer: [
{
command: 'npm run dev:web',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI
},
{
command: 'npm start --workspace=packages/bruno-tests',
url: 'http://localhost:8081/ping',
reuseExistingServer: !process.env.CI
}
]
});
Additional Resources
- Playwright Documentation
- Playwright Test API
- Electron Testing with Playwright
- Bruno Project Structure
For questions or issues with testing, please refer to the project's contributing guidelines or create an issue in the repository.