A production-ready Playwright test framework with AI-powered self-healing, visual regression testing, API mocking, and BrowserStack integration. TypeScript port of the Python version.
Targets The Internet test application.
- Prerequisites
- Quick Start
- Environment Configuration
- Running Tests
- Multi-Browser Support
- Visual Regression Testing
- API Mocking
- AI Self-Healing
- BrowserStack Integration
- Jira Integration
- Test Observability
- CI/CD Setup
- Directory Structure
- Python to TypeScript Migration
- Troubleshooting
# Clone the repository
git clone https://github.com/philmcneely/playwright-ai-framework-ts.git
cd playwright-ai-framework-ts
# Install dependencies
npm install
# Install Playwright browsers
npx playwright install --with-deps
# Copy environment template
cp .env.example .env.dev
# Run smoke tests
npm run test:smokeThe framework loads environment variables from .env files using dotenv. It loads .env first, then .env.<ENV> (defaulting to .env.dev).
Copy .env.example and customize per environment:
cp .env.example .env.dev # Development
cp .env.example .env.test # Test
cp .env.example .env.prod # ProductionSwitch environments at runtime:
ENV=test npx playwright test| Variable | Default | Description |
|---|---|---|
BASE_URL |
https://the-internet.herokuapp.com |
Application under test |
ENV |
dev |
Environment name (loads .env.<ENV>) |
BROWSER |
chromium |
Browser engine: chromium, firefox, webkit |
HEADLESS |
true |
Run browser headless |
SLOW_MO |
100 |
Slow down actions by N ms |
TIMEOUT |
30000 |
Global test timeout in ms |
RETRY_COUNT |
3 |
Number of retries on failure |
SCREENSHOT_ON_FAILURE |
true |
Capture screenshot on failure |
VIDEO_ON_FAILURE |
true |
Record video, retained on failure |
AI_HEALING_ENABLED |
false |
Enable AI self-healing analysis |
OLLAMA_MODEL |
llama3.1:8b |
Ollama model for healing |
OLLAMA_HOST |
http://localhost:11434 |
Ollama server URL |
OLLAMA_TEMPERATURE |
0.1 |
LLM temperature |
AI_HEALING_CONFIDENCE |
0.7 |
Confidence threshold for healing suggestions |
AI_HEALING_CONTEXT_WINDOW |
5000 |
Max DOM characters sent to the model |
BROWSERSTACK_ENABLED |
false |
Run on BrowserStack |
BROWSERSTACK_USERNAME |
— | BrowserStack username |
BROWSERSTACK_ACCESS_KEY |
— | BrowserStack access key |
JIRA_ENABLED |
false |
Enable Jira test reporting |
JIRA_BASE |
— | Jira instance URL |
JIRA_USER |
— | Jira username/email |
JIRA_TOKEN |
— | Jira API token |
JIRA_JQL |
project = ABC AND status in ("To Do", "In Progress") |
JQL filter for ticket lookup |
JIRA_DRY_RUN |
false |
Log instead of posting |
JIRA_TRANSITION_ON_PASS |
— | Transition name on test pass |
JIRA_TRANSITION_ON_FAIL |
— | Transition name on test fail |
OBSERVABILITY_ENABLED |
false |
Enable test metrics collection |
DEBUG_MSG |
false |
Verbose debug logging |
All commands use @playwright/test via the scripts defined in package.json.
# Full test suite (default project: chromium)
npm test
# Smoke tests only
npm run test:smoke
# Visual regression tests
npm run test:visual
# Security / login attack tests
npm run test:security
# API mocking tests
npm run test:api
# Headed mode (browser visible)
npm run test:headed
# Debug mode (Playwright Inspector)
npm run test:debugAppend flags after --:
# Run a specific test file
npx playwright test tests/login/test-login.spec.ts
# Run against Firefox
npx playwright test --project=firefox
# Filter by tag
npx playwright test --grep @smoke --project=chromium
# Type check and lint
npm run typecheck
npm run lintThree browser projects are configured in playwright.config.ts:
| Project | Engine | Device Profile |
|---|---|---|
chromium |
Chromium | Desktop Chrome |
firefox |
Firefox | Desktop Firefox |
webkit |
WebKit | Desktop Safari |
Run against a specific browser:
npx playwright test --project=firefoxRun against all browsers:
npx playwright test --project=chromium --project=firefox --project=webkitUses pixelmatch and pngjs for pixel-level screenshot comparison. No external services required.
- First run — saves a baseline screenshot.
- Subsequent runs — compares current screenshot against baseline.
- If diff exceeds tolerance (or dimensions changed) —
compare()returnspassed: falseand a diff image is saved. Assert onresult.passedto fail the test.
| Directory | Purpose |
|---|---|
test_artifacts/visual/visual_baselines/ |
Stored baseline images |
test_artifacts/visual/visual_current/ |
Current run screenshots |
test_artifacts/visual/visual_diffs/ |
Diff images (only on failure) |
import { test, expect } from "../../fixtures/index.js";
test("homepage visual check @visual", async ({ visualRegression, page }) => {
await page.goto("/");
const result = await visualRegression.compare("homepage", {
tolerance: 0.02, // 2% pixel difference allowed
fullPage: true,
});
expect(result.passed).toBeTruthy();
});selector— CSS selector for element-level screenshotsfullPage— capture the full scrollable page (default:false)tolerance— max fraction of differing pixels (default:0.01= 1%)
Delete a baseline to regenerate it on the next run. The utility also exports helper functions:
import { resetBaseline, resetAllBaselines, listVisualFiles } from "../utils/visual-regression.js";
resetBaseline("homepage"); // Delete one baseline
resetAllBaselines(); // Delete all baselines
listVisualFiles(); // List all visual artifactsThe NetworkMocker utility wraps Playwright's route interception with a clean API for mocking HTTP requests.
The apiMocker fixture is available automatically through test.extend():
import { test, expect } from "../../fixtures/index.js";
test("mock API response @api", async ({ apiMocker, page }) => {
await apiMocker.mockGet("**/api/users", { users: [{ id: 1, name: "Test" }] });
await page.goto("/some-page");
// The mocked response will be returned for any matching GET request
});| Method | Description |
|---|---|
mockGet(pattern, body, options?) |
Mock GET requests |
mockPost(pattern, body, options?) |
Mock POST requests |
mockPut(pattern, body, options?) |
Mock PUT requests |
mockDelete(pattern, body, options?) |
Mock DELETE requests |
mockFromFile(pattern, filePath, method?, options?) |
Load mock data from a JSON file |
mockWithFunction(pattern, handler, method?, options?) |
Dynamic response via callback |
simulateNetworkFailure(pattern) |
Abort matching requests |
simulateSlowNetwork(pattern, delayMs) |
Add latency before continuing |
simulateOffline() |
Abort all requests |
clearMocks() |
Remove all registered mocks |
getRequestLog() / getResponseLog() |
Inspect intercepted traffic |
Pre-built response templates for common HTTP scenarios:
import { getMockTemplate } from "../utils/network-mocking.js";
const template = getMockTemplate("unauthorized"); // 401 response
// Available: success, created, noContent, badRequest, unauthorized, forbidden, notFound, serverErrorWhen a test fails after all retries, the AI healing system queries a local Ollama instance to analyze the failure and suggest fixes. Reports are saved as Markdown files.
- Install Ollama: https://ollama.ai
- Pull a model:
# Text-only (faster, smaller)
ollama pull llama3.1:8b
# Vision + text (can analyze failure screenshots)
ollama pull llava:7b- Enable in your
.envfile:
AI_HEALING_ENABLED=true
OLLAMA_MODEL=llama3.1:8b
AIHealingReporteris registered inplaywright.config.tsas a custom reporter.- On each test failure, the reporter tracks retry counts.
- After the final failure (all retries exhausted), it triggers healing:
- Reads the test source file
- Collects error details and any failure screenshots
- Sends a structured prompt to Ollama
- Parses the JSON response (with 6 fallback parsing strategies)
- Writes a Markdown report to
test_artifacts/ai/ai_healing_reports/ - Optionally saves a healed
.tsfile if the model provides updated code
A dedicated test exists to trigger AI healing intentionally:
AI_HEALING_ENABLED=true npx playwright test --grep @trigger_ai_healing --project=chromiumReports appear in test_artifacts/ai/ai_healing_reports/.
The Python version uses pytest_runtest_makereport hooks. The TypeScript version uses Playwright's custom Reporter API (AIHealingReporter), which receives onTestEnd callbacks with full access to test metadata, errors, and attachments.
Run tests on real browsers in the cloud via BrowserStack Automate.
- Set credentials in your
.envfile:
BROWSERSTACK_ENABLED=true
BROWSERSTACK_USERNAME=your_username
BROWSERSTACK_ACCESS_KEY=your_access_key
- Run tests:
npx playwright test --project=browserstackWhen BROWSERSTACK_ENABLED=true, an additional browserstack project is added to playwright.config.ts that connects via WebSocket to BrowserStack's CDP endpoint. The default capabilities target Chrome on macOS Sonoma. Customize in utils/browserstack.ts.
When BROWSERSTACK_ENABLED=false, the browserstack project is not registered and all tests run locally.
Automatically post test results to Jira tickets and trigger workflow transitions on pass/fail. Conditionally enabled via environment variables — zero overhead when disabled.
Add the following to your .env.dev (or relevant .env file):
JIRA_ENABLED=true
JIRA_BASE=https://your-instance.atlassian.net
JIRA_USER=you@example.com
JIRA_TOKEN=your_api_token
Note: Set
JIRA_ENABLED=falseto disable entirely. SetJIRA_DRY_RUN=trueto log what would be posted without making API calls.
JiraReporteris registered inplaywright.config.tsas a custom reporter.- Test names and title paths are scanned for Jira ticket patterns (e.g.,
PROJ-123). - On test completion (pass or final retry failure), a formatted comment is posted to the ticket.
- If transition env vars are set, the ticket's workflow state is updated automatically.
- Tests can also use
test.info().annotationswith typejiraand the ticket ID as description.
Include the ticket ID anywhere in the test title path:
test("PROJ-123 login flow works correctly @smoke", async ({ page }) => {
// ...
});Or annotate the test:
test("login flow works correctly", async ({ page }) => {
test.info().annotations.push({ type: "jira", description: "PROJ-123" });
// ...
});The Python version uses pytest hooks (pytest_runtest_logreport). The TypeScript version uses Playwright's custom Reporter API (JiraReporter), which provides onTestEnd callbacks with full access to test metadata and retry state.
Structured per-test metrics collection with JSONL output, summary reports, and error categorization. Tracks flake rate, pass rate, slowest tests, heal events, and failures by category.
OBSERVABILITY_ENABLED=true
Note: Set
OBSERVABILITY_ENABLED=falseto disable metric collection entirely. Zero overhead when disabled.
Each test execution records:
| Field | Description |
|---|---|
testId |
Unique test identifier |
testName |
Human-readable test name |
suite |
Test suite/describe block |
status |
passed, failed, skipped, timedOut |
durationMs |
Execution time in milliseconds |
retryCount |
Number of retries for this test |
browser |
Browser engine used |
commitSha |
Git commit (auto-detected) |
branch |
Git branch (auto-detected) |
healEvent |
Whether AI healing was triggered |
errorCategory |
Auto-categorized: timeout, element-not-found, navigation, assertion, browser-crash, other |
| File | Location |
|---|---|
| JSONL metrics | test_artifacts/observability/metrics.jsonl |
| Summary JSON | test_artifacts/observability/summary.json |
| Markdown report | test_artifacts/observability/report.md |
OBSERVABILITY_ENABLED=true npx playwright test --project=chromiumThe summary is printed to the console at session end and a Markdown report is written to test_artifacts/observability/report.md.
A GitHub Actions workflow is provided at .github/workflows/smoke-full.yml.
push/PR to main ──> Smoke Job ──> (pass) ──> Full Job
(fail) ──> Stop
Smoke job:
- Installs Node 22, runs
npm ci, installs Chromium only - Runs tests matching
@smokeagainst thechromiumproject - Uploads
playwright-report/as an artifact
Full job:
- Depends on smoke passing
- Installs all browsers
- Runs the complete test suite
- Uploads the full report
No secrets are required for the default configuration (tests target a public site). If you add authenticated tests, configure repository secrets and reference them in the workflow env block.
playwright-ai-framework-ts/
├── .env.example # Environment variable template
├── .github/
│ └── workflows/
│ └── smoke-full.yml # CI/CD pipeline
├── config/
│ ├── artifact-paths.ts # Centralized artifact directory paths
│ ├── index.ts # Config barrel export
│ └── settings.ts # Environment-driven settings
├── data/
│ ├── index.ts # Data barrel export
│ └── test-data.ts # Test personas, credentials, expected messages
├── fixtures/
│ └── index.ts # test.extend() with app, apiMocker, visualRegression
├── pages/
│ ├── app.ts # App facade (aggregates all page objects)
│ ├── base-page.ts # Base page with shared helpers
│ ├── index.ts # Pages barrel export
│ ├── login-page.ts # Login page object
│ └── secure-page.ts # Secure area page object
├── tests/
│ ├── ai-healing/
│ │ └── test-ai-healing-trigger.spec.ts
│ ├── api/
│ │ └── test-api-mocking.spec.ts
│ ├── login/
│ │ ├── test-login.spec.ts
│ │ └── test-login-attacks.spec.ts
│ └── visual/
│ └── test-visual-regression.spec.ts
├── utils/
│ ├── ai-healing.ts # Ollama healing service
│ ├── ai-healing-reporter.ts # Custom Playwright Reporter
│ ├── browserstack.ts # BrowserStack capability builder
│ ├── decorators.ts # Utility decorators
│ ├── jira-client.ts # Jira REST API client
│ ├── jira-reporter.ts # Jira Playwright Reporter
│ ├── network-mocking.ts # NetworkMocker + templates
│ ├── observability-reporter.ts # Observability Playwright Reporter
│ ├── test-observability.ts # Test metrics collector
│ └── visual-regression.ts # pixelmatch-based visual comparison
├── test_artifacts/ # Generated at runtime (gitignored)
│ ├── ai/ai_healing_reports/ # AI analysis Markdown reports
│ ├── observability/ # JSONL metrics, summary, report
│ └── visual/ # Baselines, current screenshots, diffs
├── package.json
├── playwright.config.ts
└── tsconfig.json
This project is a full TypeScript port of the Python Playwright AI Framework. The following table maps each Python feature to its TypeScript equivalent.
| Python Feature | TypeScript Equivalent |
|---|---|
pytest + pytest-asyncio |
@playwright/test (built-in async) |
conftest.py (page fixture) |
fixtures/index.ts via test.extend() |
config/settings.py |
config/settings.ts |
config/artifact_paths.py |
config/artifact-paths.ts |
pages/ (Page Object Model) |
pages/ (Page Object Model) |
data/test_data.py |
data/test-data.ts |
utils/ai_healing.py |
utils/ai-healing.ts |
pytest_runtest_makereport hook |
AIHealingReporter (custom Reporter) |
utils/visual_regression.py (OpenCV) |
utils/visual-regression.ts (pixelmatch + pngjs) |
utils/network_mocking.py |
utils/network-mocking.ts |
@screenshot_on_failure decorator |
Built-in Playwright config (screenshot: "only-on-failure") |
@retry_decorator |
Built-in Playwright config (retries) |
| BrowserStack integration | utils/browserstack.ts + config project |
utils/jira_client.py |
utils/jira-client.ts |
utils/test_observability.py |
utils/test-observability.ts |
Jira via pytest_runtest_logreport hook |
JiraReporter (custom Reporter) |
| Observability via pytest hooks | ObservabilityReporter (custom Reporter) |
.github/workflows/ |
.github/workflows/smoke-full.yml |
requirements_with_versions.txt |
package.json (npm) |
| Python virtual environment | Node.js (no venv needed) |
python-dotenv |
dotenv npm package |
- No conftest.py — Fixtures are defined via
test.extend<Fixtures>()infixtures/index.tsand imported by test files. - No pytest markers — Tags are embedded in test titles (e.g.,
@smoke,@visual) and filtered with--grep. - Visual regression — Python used OpenCV; TypeScript uses pixelmatch (pure JS, no native dependencies).
- Reporter vs hooks — Python used pytest hooks; TypeScript uses Playwright's Reporter API, which provides structured callbacks for test lifecycle events.
Error: Executable doesn't exist at ...
Run npx playwright install --with-deps to download all browser binaries and system dependencies.
Error: BASE_URL is not defined
Ensure you have copied .env.example to .env.dev (or the file matching your ENV value) and that the variables are set.
[AI Healing] Ollama service not running, attempting to start...
- Verify Ollama is installed:
ollama --version - Start it manually:
ollama serve - Check the model is pulled:
ollama list - Test connectivity:
curl http://localhost:11434/api/tags
If baselines were generated on a different OS or screen resolution, delete the baseline and re-run to regenerate:
rm test_artifacts/visual/visual_baselines/<name>.png
npx playwright test --grep @visualThe project uses NodeNext module resolution. Ensure imports include the .js extension:
// Correct
import { settings } from "./config/settings.js";
// Wrong — will fail at runtime
import { settings } from "./config/settings";- Verify credentials:
echo $BROWSERSTACK_USERNAME - Check that
BROWSERSTACK_ENABLED=trueis set - Ensure your BrowserStack account is active and has available parallel slots