refactor: replace code generation with direct JSON interpretation

- Removed code generators (e2e/generators, storybook/generators)
- Created JSON test runner that executes Playwright tests directly from JSON
- Created JSON story loader that renders Storybook stories directly from JSON
- No intermediate code generation - JSON is executable/renderable at runtime
- json-packages.spec.ts auto-discovers and runs all package tests from JSON
- DynamicStory component renders stories from JSON definitions
- True meta/abstract architecture: configuration itself is executable
- Single source of truth: JSON definitions only (no generated .spec.ts or .stories.tsx)
- Changes to JSON take effect immediately without regeneration
- Added comprehensive READMEs explaining the interpretation approach

Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-01-16 18:59:13 +00:00
parent f2145f1aba
commit bccc336c7e
15 changed files with 892 additions and 915 deletions

View File

@@ -1,2 +0,0 @@
# Ignore generated test files
../generated/

View File

@@ -1,103 +0,0 @@
# E2E Test Generators
This folder contains tools for generating Playwright tests from declarative JSON definitions in packages.
## Structure
```
e2e/
├── generators/
│ ├── playwright-generator.ts # Core generator logic
│ └── generate.ts # CLI script
├── generated/ # Generated .spec.ts files (gitignored)
├── smoke.spec.ts # Manual smoke tests
└── *.config.ts # Playwright configurations
```
## Usage
### Generate All Package Tests
```bash
# From project root
npm run test:generate
# Or with watch mode
npm run test:generate:watch
```
### Generate Specific Package Tests
```bash
npm run test:generate ui_home
```
### Run Generated Tests
```bash
npm run test:e2e -- e2e/generated/ui_home.spec.ts
```
## How It Works
1. **Discovery**: Scans `packages/*/playwright/tests.json` files
2. **Parsing**: Reads JSON test definitions
3. **Generation**: Converts to TypeScript `.spec.ts` files
4. **Output**: Writes to `e2e/generated/`
## Package Test Definitions
Packages define tests in `playwright/tests.json`:
```json
{
"$schema": "https://metabuilder.dev/schemas/package-playwright.schema.json",
"package": "ui_home",
"tests": [
{
"name": "should load home page",
"steps": [
{ "action": "navigate", "url": "/" },
{
"action": "expect",
"selector": "body",
"assertion": { "matcher": "toBeVisible" }
}
]
}
]
}
```
## Generated Output Example
```typescript
/**
* Auto-generated Playwright tests for ui_home package
* Generated from: packages/ui_home/playwright/tests.json
* DO NOT EDIT - This file is auto-generated
*/
import { test, expect } from '@playwright/test'
test.describe('ui_home Package Tests', () => {
test('should load home page', async ({ page }) => {
await page.goto('/')
await expect(page.locator('body')).toBeVisible()
})
})
```
## Benefits
- **Data-Driven**: Tests are JSON configuration, not code
- **Package-Scoped**: Each package owns its test definitions
- **Auto-Generated**: No manual TypeScript test writing
- **Schema-Validated**: Tests conform to JSON schema
- **Meta Architecture**: Tests themselves are declarative
## See Also
- `schemas/package-schemas/playwright.schema.json` - JSON Schema
- `schemas/package-schemas/PLAYWRIGHT_SCHEMA_README.md` - Schema docs
- `packages/*/playwright/` - Package test definitions

View File

@@ -1,48 +0,0 @@
#!/usr/bin/env tsx
/**
* Generate Playwright tests from JSON definitions
*
* Usage:
* npm run test:generate # Generate all package tests
* npm run test:generate ui_home # Generate specific package tests
*/
import { resolve } from 'path'
import { generatePlaywrightTest, generateAllPlaywrightTests, discoverPlaywrightPackages } from './playwright-generator'
async function main() {
const packageName = process.argv[2]
const projectRoot = resolve(__dirname, '../..')
const packagesDir = resolve(projectRoot, 'packages')
const outputDir = resolve(projectRoot, 'e2e/generated')
console.log('🎭 Playwright Test Generator')
console.log('═'.repeat(50))
console.log(`Packages dir: ${packagesDir}`)
console.log(`Output dir: ${outputDir}`)
console.log('')
try {
if (packageName) {
// Generate for specific package
console.log(`Generating tests for package: ${packageName}`)
const outputPath = await generatePlaywrightTest(packageName, packagesDir, outputDir)
console.log(`✅ Generated: ${outputPath}`)
} else {
// Discover and generate all
const packages = await discoverPlaywrightPackages(packagesDir)
console.log(`Found ${packages.length} packages with Playwright tests:`)
packages.forEach(pkg => console.log(` - ${pkg}`))
console.log('')
const generated = await generateAllPlaywrightTests(packagesDir, outputDir)
console.log('')
console.log(`✅ Generated ${generated.length} test files`)
}
} catch (error) {
console.error('❌ Error:', error)
process.exit(1)
}
}
main()

View File

@@ -1,363 +0,0 @@
/**
* Playwright Test Generator
*
* Generates executable Playwright .spec.ts files from declarative JSON test definitions
* in packages/*/playwright/tests.json
*
* Usage:
* import { generatePlaywrightTests } from '@/lib/generators/playwright-generator'
* await generatePlaywrightTests('ui_home')
*/
import { readFile, writeFile, mkdir } from 'fs/promises'
import { join, dirname } from 'path'
import { existsSync } from 'fs'
export interface PlaywrightTestDefinition {
$schema: string
package: string
version?: string
description?: string
baseURL?: string
setup?: {
beforeAll?: SetupStep[]
beforeEach?: SetupStep[]
afterEach?: SetupStep[]
afterAll?: SetupStep[]
}
fixtures?: Record<string, unknown>
tests: TestCase[]
}
export interface SetupStep {
action: string
description?: string
[key: string]: unknown
}
export interface TestCase {
name: string
description?: string
skip?: boolean
only?: boolean
timeout?: number
retries?: number
tags?: string[]
fixtures?: Record<string, unknown>
steps: TestStep[]
}
export interface TestStep {
description?: string
action: string
url?: string
selector?: string
role?: string
text?: string
label?: string
placeholder?: string
testId?: string
value?: unknown
key?: string
timeout?: number
assertion?: Assertion
state?: string
path?: string
fullPage?: boolean
script?: string
condition?: string
}
export interface Assertion {
matcher: string
expected?: unknown
not?: boolean
timeout?: number
}
/**
* Generate Playwright test file from JSON definition
*/
export async function generatePlaywrightTest(
packageName: string,
packagesDir: string,
outputDir: string
): Promise<string> {
// Read the test definition
const testDefPath = join(packagesDir, packageName, 'playwright', 'tests.json')
if (!existsSync(testDefPath)) {
throw new Error(`No playwright tests found for package: ${packageName}`)
}
const testDefContent = await readFile(testDefPath, 'utf-8')
const testDef: PlaywrightTestDefinition = JSON.parse(testDefContent)
// Generate TypeScript code
const code = generateTestCode(testDef)
// Write to output directory
const outputPath = join(outputDir, `${packageName}.spec.ts`)
await mkdir(dirname(outputPath), { recursive: true })
await writeFile(outputPath, code, 'utf-8')
return outputPath
}
/**
* Generate TypeScript code from test definition
*/
function generateTestCode(testDef: PlaywrightTestDefinition): string {
const lines: string[] = []
// Header
lines.push(`/**`)
lines.push(` * Auto-generated Playwright tests for ${testDef.package} package`)
lines.push(` * Generated from: packages/${testDef.package}/playwright/tests.json`)
lines.push(` * DO NOT EDIT - This file is auto-generated`)
lines.push(` */`)
lines.push(``)
lines.push(`import { test, expect } from '@playwright/test'`)
lines.push(``)
if (testDef.description) {
lines.push(`/**`)
lines.push(` * ${testDef.description}`)
lines.push(` */`)
}
lines.push(`test.describe('${testDef.package} Package Tests', () => {`)
// Generate setup hooks
if (testDef.setup?.beforeAll) {
lines.push(` test.beforeAll(async () => {`)
testDef.setup.beforeAll.forEach(step => {
lines.push(` // ${step.description || step.action}`)
})
lines.push(` })`)
lines.push(``)
}
if (testDef.setup?.beforeEach) {
lines.push(` test.beforeEach(async ({ page }) => {`)
testDef.setup.beforeEach.forEach(step => {
lines.push(` // ${step.description || step.action}`)
})
lines.push(` })`)
lines.push(``)
}
// Generate test cases
testDef.tests.forEach(testCase => {
generateTestCase(testCase, lines)
})
lines.push(`})`)
lines.push(``)
return lines.join('\n')
}
/**
* Generate a single test case
*/
function generateTestCase(testCase: TestCase, lines: string[]): void {
// Test declaration
let testDecl = ' test'
if (testCase.skip) testDecl += '.skip'
if (testCase.only) testDecl += '.only'
testDecl += `('${testCase.name}', async ({ page }) => {`
lines.push(testDecl)
// Test configuration
if (testCase.timeout) {
lines.push(` test.setTimeout(${testCase.timeout})`)
}
// Test steps
testCase.steps.forEach(step => {
if (step.description) {
lines.push(` // ${step.description}`)
}
const stepCode = generateStepCode(step)
lines.push(` ${stepCode}`)
})
lines.push(` })`)
lines.push(``)
}
/**
* Generate code for a single test step
*/
function generateStepCode(step: TestStep): string {
switch (step.action) {
case 'navigate':
return `await page.goto('${step.url}')`
case 'click':
return `await ${getLocator(step)}.click()`
case 'dblclick':
return `await ${getLocator(step)}.dblclick()`
case 'fill':
return `await ${getLocator(step)}.fill('${step.value}')`
case 'type':
return `await ${getLocator(step)}.type('${step.value}')`
case 'select':
return `await ${getLocator(step)}.selectOption('${step.value}')`
case 'check':
return `await ${getLocator(step)}.check()`
case 'uncheck':
return `await ${getLocator(step)}.uncheck()`
case 'hover':
return `await ${getLocator(step)}.hover()`
case 'focus':
return `await ${getLocator(step)}.focus()`
case 'press':
return `await page.keyboard.press('${step.key}')`
case 'wait':
return `await page.waitForTimeout(${step.timeout || 1000})`
case 'waitForSelector':
return `await page.waitForSelector('${step.selector}'${step.timeout ? `, { timeout: ${step.timeout} }` : ''})`
case 'waitForNavigation':
return `await page.waitForNavigation()`
case 'waitForLoadState':
return `await page.waitForLoadState('${step.state || 'load'}')`
case 'screenshot':
return `await page.screenshot({ path: '${step.path}'${step.fullPage ? ', fullPage: true' : ''} })`
case 'evaluate':
return `await page.evaluate(() => { ${step.script} })`
case 'expect':
return generateAssertion(step)
default:
return `// Unknown action: ${step.action}`
}
}
/**
* Get locator string for a step
*/
function getLocator(step: TestStep): string {
if (step.selector) {
return `page.locator('${step.selector}')`
}
if (step.role) {
const opts: string[] = []
if (step.text) opts.push(`name: /${step.text}/i`)
const optsStr = opts.length > 0 ? `{ ${opts.join(', ')} }` : ''
return `page.getByRole('${step.role}'${optsStr ? `, ${optsStr}` : ''})`
}
if (step.text) {
return `page.getByText('${step.text}')`
}
if (step.label) {
return `page.getByLabel('${step.label}')`
}
if (step.placeholder) {
return `page.getByPlaceholder('${step.placeholder}')`
}
if (step.testId) {
return `page.getByTestId('${step.testId}')`
}
return 'page'
}
/**
* Generate assertion code
*/
function generateAssertion(step: TestStep): string {
if (!step.assertion) {
return '// No assertion specified'
}
const locator = getLocator(step)
const { matcher, expected, not, timeout } = step.assertion
let assertion = `await expect(${locator})`
if (not) assertion += '.not'
assertion += `.${matcher}(`
// Add expected value if needed
if (expected !== undefined) {
if (typeof expected === 'string') {
assertion += `'${expected}'`
} else if (typeof expected === 'number') {
assertion += expected.toString()
} else {
assertion += JSON.stringify(expected)
}
}
assertion += ')'
// Add timeout if specified
if (timeout) {
assertion = assertion.slice(0, -1) + `, { timeout: ${timeout} })`
}
return assertion
}
/**
* Discover all packages with Playwright tests
*/
export async function discoverPlaywrightPackages(packagesDir: string): Promise<string[]> {
const { readdir } = await import('fs/promises')
const packages: string[] = []
const packageDirs = await readdir(packagesDir, { withFileTypes: true })
for (const dir of packageDirs) {
if (dir.isDirectory()) {
const testPath = join(packagesDir, dir.name, 'playwright', 'tests.json')
if (existsSync(testPath)) {
packages.push(dir.name)
}
}
}
return packages
}
/**
* Generate tests for all packages
*/
export async function generateAllPlaywrightTests(
packagesDir: string,
outputDir: string
): Promise<string[]> {
const packages = await discoverPlaywrightPackages(packagesDir)
const generated: string[] = []
for (const pkg of packages) {
try {
const path = await generatePlaywrightTest(pkg, packagesDir, outputDir)
generated.push(path)
console.log(`✓ Generated: ${path}`)
} catch (error) {
console.error(`✗ Failed to generate tests for ${pkg}:`, error)
}
}
return generated
}

16
e2e/json-packages.spec.ts Normal file
View File

@@ -0,0 +1,16 @@
/**
* JSON-Driven Package Tests
*
* This test file dynamically loads and executes all Playwright tests
* defined in packages/*/playwright/tests.json
*
* No code generation - tests are interpreted directly from JSON at runtime.
*/
import { test } from '@playwright/test'
import { resolve } from 'path'
import { loadAllPackageTests } from './json-runner/playwright-json-runner'
// Load all package tests from JSON
const packagesDir = resolve(__dirname, '../packages')
await loadAllPackageTests(packagesDir, test)

139
e2e/json-runner/README.md Normal file
View File

@@ -0,0 +1,139 @@
# JSON Playwright Test Runner
**No code generation - tests are interpreted directly from JSON at runtime.**
This is the true meta/abstract approach: the JSON itself is executable, not just a template for code generation.
## Philosophy
Instead of generating `.spec.ts` files from JSON, we **directly execute** the JSON test definitions. This keeps tests as pure data that's interpreted at runtime, staying true to the 95% configuration rule.
## How It Works
1. **Discovery**: Scans `packages/*/playwright/tests.json` files
2. **Loading**: Reads JSON test definitions at runtime
3. **Interpretation**: Executes test steps directly from JSON
4. **No Intermediate**: No code generation step - JSON → Execution
## Usage
### Run JSON-Defined Package Tests
```bash
# Run all JSON-defined package tests
npm run test:e2e:json
# Or run directly
npm run test:e2e -- e2e/json-packages.spec.ts
# With UI mode
npm run test:e2e:ui -- e2e/json-packages.spec.ts
```
### How Tests Are Loaded
The `json-packages.spec.ts` file automatically discovers and loads all tests:
```typescript
import { loadAllPackageTests } from './json-runner/playwright-json-runner'
// Discovers packages/*/playwright/tests.json and registers tests
await loadAllPackageTests(packagesDir, test)
```
## Example: JSON Test Definition
`packages/ui_home/playwright/tests.json`:
```json
{
"$schema": "https://metabuilder.dev/schemas/package-playwright.schema.json",
"package": "ui_home",
"tests": [{
"name": "should display hero section",
"tags": ["@smoke", "@ui"],
"steps": [
{
"description": "Navigate to home page",
"action": "navigate",
"url": "/"
},
{
"description": "Wait for page load",
"action": "waitForLoadState",
"state": "domcontentloaded"
},
{
"description": "Verify hero title visible",
"action": "expect",
"selector": ".hero-title",
"assertion": {
"matcher": "toBeVisible"
}
}
]
}]
}
```
**Executed directly - no intermediate code generation!**
## Supported Actions
- **Navigation**: `navigate`, `waitForNavigation`, `waitForLoadState`
- **Interactions**: `click`, `dblclick`, `fill`, `type`, `select`, `check`, `uncheck`, `hover`, `focus`, `press`
- **Assertions**: `expect` with all Playwright matchers
- **Utilities**: `wait`, `waitForSelector`, `screenshot`, `evaluate`
## Supported Selectors
- `selector` - CSS selector
- `role` - ARIA role with optional text
- `text` - Text content
- `label` - Form label
- `placeholder` - Input placeholder
- `testId` - data-testid attribute
## Supported Assertion Matchers
All standard Playwright matchers:
- Visibility: `toBeVisible`, `toBeHidden`
- State: `toBeEnabled`, `toBeDisabled`, `toBeChecked`, `toBeFocused`, `toBeEmpty`
- Content: `toHaveText`, `toContainText`, `toHaveValue`
- Count: `toHaveCount`
- Attributes: `toHaveAttribute`, `toHaveClass`, `toHaveCSS`
- Page: `toHaveURL`, `toHaveTitle`
## Benefits of JSON Execution
1. **True Meta Architecture**: Tests are data, not code
2. **No Build Step**: JSON is directly executable
3. **Runtime Interpretation**: Changes take effect immediately
4. **Single Source of Truth**: JSON is the only definition
5. **Package Ownership**: Each package owns its test data
6. **Schema Validated**: Tests conform to JSON schema
## File Structure
```
e2e/
├── json-runner/
│ └── playwright-json-runner.ts # JSON test interpreter
├── json-packages.spec.ts # Auto-loads all package tests
└── smoke.spec.ts # Manual smoke tests
```
## vs Code Generation
| Approach | Source of Truth | Runtime | Changes |
|----------|----------------|---------|---------|
| **Code Generation** | JSON → Generate `.spec.ts` | Executes TypeScript | Requires regeneration |
| **JSON Execution** ✅ | JSON (only) | Interprets JSON | Immediate effect |
JSON execution is more meta/abstract - the configuration itself is executable!
## See Also
- `schemas/package-schemas/playwright.schema.json` - JSON Schema
- `schemas/package-schemas/PLAYWRIGHT_SCHEMA_README.md` - Schema documentation
- `packages/*/playwright/tests.json` - Test definitions

View File

@@ -0,0 +1,356 @@
/**
* JSON Playwright Test Runner
*
* Directly executes Playwright tests from JSON definitions without code generation.
* Tests are interpreted at runtime from packages/*/playwright/tests.json
*
* This is the meta/abstract approach - JSON itself is executable, not just a template.
*/
import { test as baseTest, expect, Page } from '@playwright/test'
import { readFile, readdir } from 'fs/promises'
import { join } from 'path'
import { existsSync } from 'fs'
interface PlaywrightTestDefinition {
$schema: string
package: string
version?: string
description?: string
baseURL?: string
setup?: {
beforeAll?: SetupStep[]
beforeEach?: SetupStep[]
afterEach?: SetupStep[]
afterAll?: SetupStep[]
}
fixtures?: Record<string, unknown>
tests: TestCase[]
}
interface SetupStep {
action: string
description?: string
[key: string]: unknown
}
interface TestCase {
name: string
description?: string
skip?: boolean
only?: boolean
timeout?: number
retries?: number
tags?: string[]
fixtures?: Record<string, unknown>
steps: TestStep[]
}
interface TestStep {
description?: string
action: string
url?: string
selector?: string
role?: string
text?: string
label?: string
placeholder?: string
testId?: string
value?: unknown
key?: string
timeout?: number
assertion?: Assertion
state?: string
path?: string
fullPage?: boolean
script?: string
condition?: string
}
interface Assertion {
matcher: string
expected?: unknown
not?: boolean
timeout?: number
}
/**
* Discover all packages with Playwright test definitions
*/
export async function discoverTestPackages(packagesDir: string): Promise<string[]> {
const packages: string[] = []
if (!existsSync(packagesDir)) {
return packages
}
const packageDirs = await readdir(packagesDir, { withFileTypes: true })
for (const dir of packageDirs) {
if (dir.isDirectory()) {
const testPath = join(packagesDir, dir.name, 'playwright', 'tests.json')
if (existsSync(testPath)) {
packages.push(dir.name)
}
}
}
return packages
}
/**
* Load test definition from package
*/
export async function loadTestDefinition(packageName: string, packagesDir: string): Promise<PlaywrightTestDefinition> {
const testPath = join(packagesDir, packageName, 'playwright', 'tests.json')
const content = await readFile(testPath, 'utf-8')
return JSON.parse(content)
}
/**
* Execute a test step
*/
async function executeStep(step: TestStep, page: Page): Promise<void> {
if (step.description) {
// Log step description for debugging
console.log(`${step.description}`)
}
switch (step.action) {
case 'navigate':
await page.goto(step.url!)
break
case 'click':
await getLocator(step, page).click()
break
case 'dblclick':
await getLocator(step, page).dblclick()
break
case 'fill':
await getLocator(step, page).fill(String(step.value))
break
case 'type':
await getLocator(step, page).pressSequentially(String(step.value))
break
case 'select':
await getLocator(step, page).selectOption(String(step.value))
break
case 'check':
await getLocator(step, page).check()
break
case 'uncheck':
await getLocator(step, page).uncheck()
break
case 'hover':
await getLocator(step, page).hover()
break
case 'focus':
await getLocator(step, page).focus()
break
case 'press':
await page.keyboard.press(step.key!)
break
case 'wait':
await page.waitForTimeout(step.timeout || 1000)
break
case 'waitForSelector':
await page.waitForSelector(step.selector!, step.timeout ? { timeout: step.timeout } : undefined)
break
case 'waitForNavigation':
await page.waitForLoadState('networkidle')
break
case 'waitForLoadState':
await page.waitForLoadState((step.state || 'load') as 'load' | 'domcontentloaded' | 'networkidle')
break
case 'screenshot':
await page.screenshot({
path: step.path,
fullPage: step.fullPage
})
break
case 'evaluate':
await page.evaluate(step.script!)
break
case 'expect':
await executeAssertion(step, page)
break
default:
throw new Error(`Unknown action: ${step.action}`)
}
}
/**
* Get locator for a step
*/
function getLocator(step: TestStep, page: Page) {
if (step.selector) {
return page.locator(step.selector)
}
if (step.role) {
const options: any = {}
if (step.text) options.name = new RegExp(step.text, 'i')
return page.getByRole(step.role as any, options)
}
if (step.text) {
return page.getByText(step.text)
}
if (step.label) {
return page.getByLabel(step.label)
}
if (step.placeholder) {
return page.getByPlaceholder(step.placeholder)
}
if (step.testId) {
return page.getByTestId(step.testId)
}
throw new Error('No selector specified for step')
}
/**
* Execute an assertion
*/
async function executeAssertion(step: TestStep, page: Page): Promise<void> {
if (!step.assertion) {
throw new Error('No assertion specified')
}
const locator = getLocator(step, page)
const { matcher, expected, not, timeout } = step.assertion
let assertion = expect(locator)
if (not) assertion = assertion.not as any
const options = timeout ? { timeout } : undefined
// Execute the matcher
switch (matcher) {
case 'toBeVisible':
await assertion.toBeVisible(options)
break
case 'toBeHidden':
await assertion.toBeHidden(options)
break
case 'toBeEnabled':
await assertion.toBeEnabled(options)
break
case 'toBeDisabled':
await assertion.toBeDisabled(options)
break
case 'toBeChecked':
await assertion.toBeChecked(options)
break
case 'toBeFocused':
await assertion.toBeFocused(options)
break
case 'toBeEmpty':
await assertion.toBeEmpty(options)
break
case 'toHaveText':
await assertion.toHaveText(String(expected), options)
break
case 'toContainText':
await assertion.toContainText(String(expected), options)
break
case 'toHaveValue':
await assertion.toHaveValue(String(expected), options)
break
case 'toHaveCount':
await assertion.toHaveCount(Number(expected), options)
break
case 'toHaveAttribute':
// Expected should be [name, value]
if (Array.isArray(expected) && expected.length === 2) {
await assertion.toHaveAttribute(expected[0], expected[1], options)
}
break
case 'toHaveClass':
await assertion.toHaveClass(expected as any, options)
break
case 'toHaveCSS':
// Expected should be [name, value]
if (Array.isArray(expected) && expected.length === 2) {
await assertion.toHaveCSS(expected[0], expected[1], options)
}
break
case 'toHaveURL':
await (assertion as any).toHaveURL(String(expected), options)
break
case 'toHaveTitle':
await (assertion as any).toHaveTitle(String(expected), options)
break
default:
throw new Error(`Unknown matcher: ${matcher}`)
}
}
/**
* Register tests from a JSON definition
*/
export function registerTestsFromJSON(testDef: PlaywrightTestDefinition, testFn = baseTest) {
testFn.describe(`${testDef.package} Package Tests (from JSON)`, () => {
// Setup hooks
if (testDef.setup?.beforeAll) {
testFn.beforeAll(async () => {
console.log(`[Setup] beforeAll for ${testDef.package}`)
// Setup steps would be executed here
})
}
if (testDef.setup?.beforeEach) {
testFn.beforeEach(async ({ page }) => {
console.log(`[Setup] beforeEach for ${testDef.package}`)
// Setup steps would be executed here
})
}
// Register each test
testDef.tests.forEach(testCase => {
let test = testFn
if (testCase.skip) test = test.skip
if (testCase.only) test = test.only
test(testCase.name, async ({ page }) => {
if (testCase.timeout) {
test.setTimeout(testCase.timeout)
}
console.log(`\n[Test] ${testCase.name}`)
// Execute all steps
for (const step of testCase.steps) {
await executeStep(step, page)
}
})
})
})
}
/**
* Load and register all package tests
*/
export async function loadAllPackageTests(packagesDir: string, testFn = baseTest) {
const packages = await discoverTestPackages(packagesDir)
for (const packageName of packages) {
const testDef = await loadTestDefinition(packageName, packagesDir)
registerTestsFromJSON(testDef, testFn)
}
}