speclab
v0.5.0
Published
Auto-generate Playwright specs from app source + runtime crawl + DB schema
Readme
speclab — Playwright Test Generation Toolkit
Generate annotated Playwright specs and human-readable test deliverables from natural-language scenarios — with a built-in locator inventory, migration pipeline, behavioral test generators, and token-efficient Claude Code skills.
Quick start
# 1. Install
npm install --save-dev speclab @playwright/test
npx playwright install chromium
# 2. Scaffold into your project
npx speclab init
# 3. Configure
# Edit speclab.config.json with your app's baseURL, auth, and (optionally) DB
# 4. Run
npx playwright testWhat's in the box
speclab ships three complementary pipelines:
| Pipeline | Purpose | Entry point |
|---|---|---|
| Spec generation | Turn scenarios → passing .spec.ts files | /playwright-testgen skill |
| Migration testing | AsIs↔ToBe behavioral equivalence (Stage A→G) | speclab migrate |
| Behavioral extraction | Auto-generate filter-reactive tests from source | filter:extract + filter:generate |
1 — Spec generation
speclab init scaffold
your-project/
├── speclab.config.json
├── playwright.config.ts
├── tests/helpers/
│ ├── config.ts ← typed config export
│ ├── api.ts ← loginAs() helper
│ ├── db.ts ← queryDB(), entityTable()
│ └── locators.ts ← locate(), findComponent()
└── .claude/skills/
├── playwright-testgen/ ← /playwright-testgen skill
└── playwright-batch/ ← /playwright-batch skillConfiguration — speclab.config.json
{
"project": "my-app",
"baseURL": "https://your-app.example.com/",
"extractor": { "kind": "disabled" },
"auth": {
"loginEndpoint": "/api/auth/login",
"admin": { "username": "admin", "password": "..." }
}
}extractor.kind options:
"disabled"— no source scan; rely on runtime discovery"react-jsx"— scans React/JSX source (+ optional@ast-grep/napifor deeper AST)"thymeleaf"— scans Spring Boot + Thymeleaf templates
Claude Code skills
/playwright-testgen — turn a natural-language scenario into a passing Playwright spec + markdown deliverable.
/playwright-batch — run /playwright-testgen in batch across a multi-scenario markdown file.
2 — Migration pipeline (Stage A→G)
Tests behavioral equivalence between an AsIs (legacy) system and a ToBe (new) system during a migration project.
# Configure once
cp node_modules/speclab/templates/migrate/migrate.config.example.json migrate.config.json
# Edit migrate.config.json with asis/tobe URLs, auth, source paths
# Run all 7 stages
speclab migrate run migrate.config.json
# Or run individual stages
speclab migrate coverage migrate.config.json # coverage report
speclab migrate gap-hint migrate.config.json # gap analysisStages
| Stage | What it does | Output |
|---|---|---|
| A — asis-ingest | Crawl AsIs app, capture live signal | asis-signal.json |
| B — abs-generate | Extract ABS scenarios from source code | abs.json |
| C — tobe-route | Map ABS scenarios → ToBe routes | routing.json |
| D — abs-to-spec | Generate .spec.ts for ToBe only | specs/abs-*.spec.ts |
| E — abs-to-excel | Generate Excel deliverable | *.xlsx |
| F — abs-to-markdown | Generate human-readable docs | docs/*.md |
| G — abs-to-compare | Generate AsIs↔ToBe equivalence specs | compare/*.compare.spec.ts |
Dual-harness test fixture
import { dualTest, assertEquivalent, loginBoth } from './helpers/dual-harness';
dualTest('CV list loads same data', async ({ asisPage, tobePage }) => {
await loginBoth(asisPage, tobePage);
await assertEquivalent(asisPage, tobePage, {
redirectPath: '/cv',
visibleText: ['Employee Code', 'Full Name'],
screenshot: true, // pixel-diff comparison
});
});Source extractors
Supports 6 legacy frameworks via semgrep-backed extractors:
| Framework | Rule file | Rules |
|---|---|---|
| ASP.NET Web Forms | semgrep-rules/aspnet.yml | 9 |
| CodeIgniter 4 | semgrep-rules/ci4.yml | 9 |
| Laravel | semgrep-rules/laravel.yml | 10 |
| Spring MVC | semgrep-rules/spring.yml | 14 |
| Apache Struts | semgrep-rules/struts.yml | 10 |
| React/TSX | built-in (+ ast-grep) | — |
Install semgrep for legacy framework extraction: pip install semgrep
Install ast-grep for deeper React AST patterns: npm install @ast-grep/napi
3 — Behavioral extraction (filter-reactive)
Automatically extract React filter-state signals from source and generate Playwright tests that verify filter controls correctly affect displayed data.
# Step 1 — scan source, emit FilterSignal JSON per module
node scripts/extract-filter-signals.mjs \
--source /path/to/app/src \
--out output/filter-signals/
# Step 2 — review JSON, annotate cardSelectors, prune false positives
# Step 3 — generate .spec.ts from signals
node scripts/generate-filter-specs.mjs \
--signals output/filter-signals/ee.json \
--out output/specs/
# Step 4 — run
ASIS_BASE_URL=https://your-app.com \
npx playwright test output/specs/ee-filter-reactive.spec.ts \
--config playwright.migrate.config.tsFilterSignal JSON format
{
"meta": {
"module": "EE",
"route": "/ee",
"waitForTable": "loading-div",
"activeClass": "ring-1",
"cardCountLocator": ".text-2xl"
},
"signals": [
{
"signalId": "filter_ee_statusFilter",
"stateName": "statusFilter",
"controlType": "card-toggle",
"cardValues": ["critical", "warning", "healthy"],
"cardSelectors": {
"critical": "[class*=\"bg-red-900\"]"
}
}
],
"supplement": [
{
"id": "T-EXPORT",
"ts": "test('T-EXPORT — export triggers download', async ({ page }) => { ... });"
}
]
}Detected control types
| controlType | Pattern in source | Generated invariant |
|---|---|---|
| card-toggle | active={state === "value"} | count-match + toggle-off |
| text-input | <input value={state}> | canary + prefix search |
| dropdown | <select value={state}> | API intercept + count assert |
Meta options
| Field | Default | Description |
|---|---|---|
| waitForTable | "tbody" | "loading-div" | "networkidle" | "tbody" |
| activeClass | "ring-1" | CSS class added to active card (e.g. "ring-2") |
| cardCountLocator | ".text-2xl" | Locator for count display inside card; "" = button textContent |
| dataRowsSelector | "tbody tr:not(:has(td[colspan]))" | Selector for data rows |
supplement[] — hand-authored tests
Add verbatim TypeScript tests that the extractor cannot generate (cross-filter combinations, exports, etc.). They are appended inside the test.describe block and tracked in version control alongside the signal JSON.
CLI reference
| Command | Purpose |
|---|---|
| speclab init [--force] | Scaffold helpers, skills, and config |
| speclab locators [refresh\|force] | Build locator inventory from source |
| speclab deliverables | Generate markdown deliverables from specs |
| speclab migrate run <config> | Run full A→G migration pipeline |
| speclab migrate coverage <config> | Route × ABS × spec coverage report |
| speclab migrate gap-hint <config> | CRUD gaps, RBAC blindspots, DB entity gaps |
| speclab skill:start <name> <summary> | Open skill-trace invocation |
| speclab skill:end <id> [artifacts...] | Close skill-trace invocation |
| speclab batch:parse <file.md> | Parse and verify a test-suite markdown |
| speclab batch:report <state.json> <out.md> | Render a batch report |
| speclab batch:run <suite.md> | Run /playwright-batch via claude -p |
npm scripts (in your project after speclab init)
| Script | Command |
|---|---|
| npm test | playwright test |
| npm run test:headed | Headed + slowMo 1500ms |
| npm run locators:refresh | Locator inventory (cached) |
| npm run locators:force | Locator inventory (force rebuild) |
| npm run deliverables | Specs → markdown deliverables |
| npm run filter:extract | Scan source → FilterSignal JSON |
| npm run filter:generate | FilterSignal JSON → spec.ts |
Peer dependencies
{
"@playwright/test": ">=1.60.0",
"pg": ">=8.0.0", // optional — DB assertions
"pixelmatch": ">=5.0.0", // optional — visual regression
"pngjs": ">=6.0.0" // optional — visual regression
}Optional external tools:
pip install semgrep— legacy framework extraction (ASP.NET, Laravel, Spring, Struts, CI4)npm install @ast-grep/napi— deeper React AST patterns in react-tsx extractor
License
ISC
