Test Generation
Gasoline is an open-source MCP server that watches your browser session and generates Playwright tests with real API assertions — not just button clicks. Your AI assistant saw you use the app; now it writes the regression test.
The Problem
Section titled “The Problem”You fixed a bug. You manually verified it works. But without a regression test, it’ll break again next sprint. Writing that test is tedious — you have to remember what you clicked, what API calls fired, and what the responses looked like.
Meanwhile, Gasoline already captured all of it.
Not Just Replay — Real Assertions
Section titled “Not Just Replay — Real Assertions”Most record-and-replay tools produce fragile scripts that just click buttons. generate_test produces a regression test:
| Typical Replay Script | Gasoline generate_test |
|---|---|
| Clicks buttons | Clicks buttons |
| No network assertions | Asserts API status codes |
| No response validation | Validates response structure |
| No error checking | Asserts zero console errors |
| Hardcoded URLs | Configurable base URL |
| Fragile CSS selectors | Multi-strategy selectors (data-testid > aria > role > CSS) |
Example Output
Section titled “Example Output”You log in, submit a form, and see the result. generate_test produces:
import { test, expect } from '@playwright/test';
test('submit-form flow', async ({ page }) => { // Collect console errors const consoleErrors = []; page.on('console', msg => { if (msg.type() === 'error') consoleErrors.push(msg.text()); });
await page.goto('http://localhost:3000/login');
// Login await page.getByLabel('Email').fill('user@example.com'); await page.getByLabel('Password').fill('[user-provided]');
const resp1Promise = page.waitForResponse(r => r.url().includes('/api/auth/login')); await page.getByRole('button', { name: 'Sign In' }).click(); const resp1 = await resp1Promise; expect(resp1.status()).toBe(200); const resp1Body = await resp1.json(); expect(resp1Body).toHaveProperty('token'); expect(resp1Body).toHaveProperty('user');
// Navigate to form await expect(page).toHaveURL(/\/dashboard/);
// Submit await page.getByTestId('name-input').fill('Test Project'); const resp2Promise = page.waitForResponse(r => r.url().includes('/api/projects')); await page.getByRole('button', { name: 'Create' }).click(); const resp2 = await resp2Promise; expect(resp2.status()).toBe(201);
// Assert: no console errors during flow expect(consoleErrors).toHaveLength(0);});Passwords are automatically redacted. Selectors prefer data-testid and ARIA roles over brittle CSS paths.
How It Works
Section titled “How It Works”1. Session Timeline
Section titled “1. Session Timeline”Gasoline correlates three data sources into a unified timeline:
- User actions — clicks, inputs, keypresses, navigation, scrolls
- Network requests — URL, method, status, response body, timing
- Console events — errors and warnings with messages
Each network request is attributed to the preceding user action that triggered it.
2. Response Shape Extraction
Section titled “2. Response Shape Extraction”For JSON API responses, Gasoline extracts the structural type signature — field names and types, never values:
Response: {"id": 42, "name": "Project", "tasks": [{"title": "Todo"}]} ↓Shape: {"id": "number", "name": "string", "tasks": [{"title": "string"}]}Your test asserts the API contract, not specific data that changes between runs.
3. Assertion Generation
Section titled “3. Assertion Generation”| Signal | Assertion |
|---|---|
| Click triggers API call | waitForResponse + expect(status).toBe(...) |
| Navigation occurs | expect(page).toHaveURL(/pattern/) |
| JSON response received | expect(body).toHaveProperty('field') |
| No errors in session | expect(consoleErrors).toHaveLength(0) |
| Errors present in session | Commented-out assertion + listed known errors |
4. Selector Priority
Section titled “4. Selector Priority”Gasoline computes multiple selector strategies for every interaction and picks the most resilient:
data-testid—getByTestId('submit-btn')- ARIA role + name —
getByRole('button', { name: 'Submit' }) - ARIA label —
getByLabel('Email') - Visible text —
getByText('Sign In') - Element ID —
locator('#submit') - CSS path —
locator('form > button.primary')(last resort)
MCP Tools
Section titled “MCP Tools”generate_test
Section titled “generate_test”Generate a full Playwright test from the current session:
| Parameter | Default | Description |
|---|---|---|
test_name | Derived from URL | Name for the test() block |
base_url | Captured origin | Replace origin for portability |
assert_network | true | Assert API status codes |
assert_no_errors | true | Assert zero console errors |
assert_response_shape | false | Assert response body structure |
last_n_actions | All | Scope to recent N actions |
get_session_timeline
Section titled “get_session_timeline”Get the correlated timeline without generating a test. Useful for understanding cause-and-effect:
{ "last_n_actions": 5, "url": "checkout", "include": ["actions", "network"]}Returns a sorted timeline with summary stats (action count, network requests, console errors, duration).
Use Cases
Section titled “Use Cases”After Fixing a Bug
Section titled “After Fixing a Bug”“I just fixed the login timeout issue. Generate a regression test from my session.”
Your AI calls generate_test and produces a test that verifies login works — with the actual API assertions that would catch a regression.
Documenting a Flow
Section titled “Documenting a Flow”“Generate a test for the checkout flow I just walked through.”
Get a complete Playwright test covering navigation, form fills, API calls, and error checks — no manual test authoring.
API Contract Testing
Section titled “API Contract Testing”“Generate a test with response shape assertions for the user profile flow.”
With assert_response_shape: true, the test validates that API responses maintain their structure — catching breaking changes even when values differ.
Portable Tests
Section titled “Portable Tests”“Generate a test using http://localhost:3000 as the base URL.”
The base_url parameter rewrites captured URLs (e.g., from https://staging.example.com) to your local dev server.
Privacy
Section titled “Privacy”- Passwords are redacted to
[user-provided]before test generation - Response shapes contain types only, never actual values
- All processing is local — no data leaves your machine
- Generated tests never contain auth tokens or secrets