Add Playwright playbook runner, Storybook generator, and comprehensive documentation

Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-01-08 15:23:41 +00:00
parent ef1a912833
commit 4136f3c50d
4 changed files with 692 additions and 0 deletions

View File

@@ -628,6 +628,10 @@ npm run dev
- `npm run test` - Run unit tests with Vitest
- `npm run test:e2e` - Run E2E tests with Playwright
- `npm run storybook` - Start Storybook for component development
- `npm run build-storybook` - Build Storybook for production
See [PLAYWRIGHT_PLAYBOOKS.md](./docs/PLAYWRIGHT_PLAYBOOKS.md) for Playwright playbook testing documentation.
See [STORYBOOK.md](./docs/STORYBOOK.md) for Storybook configuration and usage.
#### Code Quality
- `npm run lint` - Run ESLint

View File

@@ -0,0 +1,422 @@
# Playwright Playbook Testing
This project uses Playwright for end-to-end testing with test playbooks defined in `features.json` for reusable test scenarios.
## Getting Started
### Running Playwright Tests
```bash
# Run all tests
npm run test:e2e
# Run in UI mode (interactive)
npx playwright test --ui
# Run specific test file
npx playwright test tests/e2e/Playbooks.e2e.ts
# Run tests in headed mode (see browser)
npx playwright test --headed
```
## Playbook Runner Utility
The playbook runner (`tests/utils/playbookRunner.ts`) executes test scenarios defined in the `playwrightPlaybooks` section of `features.json`.
### Why Use Playbooks?
- **Reusability** - Define common workflows once, use in multiple tests
- **Consistency** - Ensure tests follow the same patterns
- **Maintainability** - Update test steps in one place
- **Documentation** - Playbooks serve as living documentation
- **Configuration-driven** - Non-developers can update test scenarios
### Using the Playbook Runner
#### Basic Usage
```typescript
import { test } from '@playwright/test';
import { runPlaybook } from '../utils/playbookRunner';
test('should execute login workflow', async ({ page }) => {
await runPlaybook(page, 'adminLogin', {
username: 'admin',
password: 'password123',
});
});
```
#### With Variables
Playbooks support variable substitution using `{{variableName}}` syntax:
```typescript
await runPlaybook(page, 'createTable', {
tableName: 'users',
columnName: 'id',
dataType: 'INTEGER',
});
```
#### With Cleanup
Some playbooks include cleanup steps:
```typescript
await runPlaybook(page, 'createTable',
{ tableName: 'test_table' },
{ runCleanup: true } // Runs cleanup steps after main steps
);
```
### Available Utilities
#### `runPlaybook(page, playbookName, variables?, options?)`
Executes a complete playbook from features.json.
**Parameters:**
- `page` - Playwright Page object
- `playbookName` - Name of the playbook in features.json
- `variables` - Object with variable values for substitution
- `options.runCleanup` - Whether to run cleanup steps
#### `executeStep(page, step, variables?)`
Executes a single playbook step.
#### `getPlaybooksByTag(tag)`
Returns all playbooks with a specific tag.
```typescript
const adminPlaybooks = getPlaybooksByTag('admin');
```
#### `listPlaybooks()`
Returns names of all available playbooks.
```typescript
const playbooks = listPlaybooks();
console.log('Available playbooks:', playbooks);
```
## Defining Playbooks in features.json
Playbooks are defined in the `playwrightPlaybooks` section:
```json
{
"playwrightPlaybooks": {
"playbookName": {
"name": "Human-Readable Name",
"description": "What this playbook does",
"tags": ["admin", "crud"],
"steps": [
{
"action": "goto",
"url": "/admin/dashboard"
},
{
"action": "click",
"selector": "button:has-text('Create')"
},
{
"action": "fill",
"selector": "input[name='name']",
"value": "{{name}}"
},
{
"action": "expect",
"selector": "text={{name}}",
"text": "visible"
}
],
"cleanup": [
{
"action": "click",
"selector": "button:has-text('Delete')"
}
]
}
}
}
```
### Supported Actions
| Action | Description | Parameters |
|--------|-------------|------------|
| `goto` | Navigate to URL | `url` |
| `click` | Click element | `selector` |
| `fill` | Fill input | `selector`, `value` |
| `select` | Select dropdown option | `selector`, `value` |
| `wait` | Wait for timeout | `timeout` (ms) |
| `expect` | Assert condition | `selector`, `text` or `url` |
| `screenshot` | Take screenshot | `selector` (optional) |
### Variable Substitution
Use `{{variableName}}` in any string field:
```json
{
"action": "fill",
"selector": "input[name='{{fieldName}}']",
"value": "{{fieldValue}}"
}
```
When running the playbook:
```typescript
await runPlaybook(page, 'myPlaybook', {
fieldName: 'username',
fieldValue: 'admin',
});
```
## Pre-defined Playbooks
The following playbooks are available in features.json:
### adminLogin
Complete admin login flow.
- **Tags:** admin, auth, login
- **Variables:** username, password
### createTable
Create a new database table through UI.
- **Tags:** admin, table, crud
- **Variables:** tableName
- **Cleanup:** Yes (drops the table)
### addColumn
Add a column to an existing table.
- **Tags:** admin, column, crud
- **Variables:** tableName, columnName, dataType
### createIndex
Create a database index.
- **Tags:** admin, index, performance
- **Variables:** tableName, indexName, columnName
### queryBuilder
Build and execute a query.
- **Tags:** admin, query, select
- **Variables:** tableName, columnName
### securityCheck
Verify API endpoints require authentication.
- **Tags:** security, api, auth
- **Variables:** None
## Best Practices
### 1. Tag Your Playbooks
Use tags for organization and filtering:
```json
{
"tags": ["admin", "crud", "table"]
}
```
### 2. Use Meaningful Names
Make playbook names descriptive:
-`createUserAndVerifyEmail`
-`test1`
### 3. Add Cleanup Steps
Clean up test data to keep tests independent:
```json
{
"cleanup": [
{
"action": "click",
"selector": "button:has-text('Delete')"
}
]
}
```
### 4. Make Playbooks Composable
Break complex workflows into smaller playbooks:
```typescript
// Login first
await runPlaybook(page, 'adminLogin', { username, password });
// Then run specific test
await runPlaybook(page, 'createTable', { tableName });
```
### 5. Use Descriptive Selectors
Prefer text selectors and test IDs:
-`button:has-text('Create')`
-`[data-testid="create-button"]`
-`.btn-primary`
## Example Tests
### Simple Playbook Test
```typescript
import { test } from '@playwright/test';
import { runPlaybook } from '../utils/playbookRunner';
test('create and delete table', async ({ page }) => {
const tableName = `test_${Date.now()}`;
await runPlaybook(page, 'createTable',
{ tableName },
{ runCleanup: true }
);
});
```
### Multiple Playbooks
```typescript
test('complete workflow', async ({ page }) => {
// Step 1: Login
await runPlaybook(page, 'adminLogin', {
username: 'admin',
password: 'password',
});
// Step 2: Create table
const tableName = 'users';
await runPlaybook(page, 'createTable', { tableName });
// Step 3: Add column
await runPlaybook(page, 'addColumn', {
tableName,
columnName: 'email',
dataType: 'VARCHAR',
});
// Step 4: Create index
await runPlaybook(page, 'createIndex', {
tableName,
indexName: 'idx_email',
columnName: 'email',
});
});
```
### Tag-based Testing
```typescript
import { getPlaybooksByTag } from '../utils/playbookRunner';
test.describe('Admin CRUD operations', () => {
const crudPlaybooks = getPlaybooksByTag('crud');
for (const [name, playbook] of Object.entries(crudPlaybooks)) {
test(playbook.name, async ({ page }) => {
// Run each CRUD playbook
await runPlaybook(page, name, {
/* variables */
});
});
}
});
```
## Debugging
### View Test Results
```bash
# Show test report
npx playwright show-report
# Open trace viewer
npx playwright show-trace trace.zip
```
### Debug Mode
```bash
# Run in debug mode
npx playwright test --debug
# Run specific test in debug mode
npx playwright test tests/e2e/Playbooks.e2e.ts --debug
```
### Screenshots
Playbooks can take screenshots:
```json
{
"action": "screenshot",
"selector": ".query-results"
}
```
Screenshots are saved to `screenshots/` directory.
## Continuous Integration
In CI environments, tests run automatically:
```yaml
# .github/workflows/test.yml
- name: Run Playwright tests
run: npm run test:e2e
```
The playwright.config.ts is configured to:
- Use different settings for CI vs local
- Record videos on failure
- Generate test reports
## Troubleshooting
### Playbook not found
Make sure the playbook name matches exactly in features.json:
```typescript
const playbooks = listPlaybooks();
console.log('Available:', playbooks);
```
### Timeout errors
Increase wait times in playbook steps:
```json
{
"action": "wait",
"timeout": 5000
}
```
Or configure global timeout in playwright.config.ts.
### Variable substitution not working
Check variable names match exactly:
```typescript
// In features.json: {{tableName}}
// In test:
await runPlaybook(page, 'createTable', {
tableName: 'users', // Must match: tableName
});
```
## Additional Resources
- [Playwright Documentation](https://playwright.dev/)
- [Playwright Best Practices](https://playwright.dev/docs/best-practices)
- [Test Examples](/tests/e2e/)

185
docs/STORYBOOK.md Normal file
View File

@@ -0,0 +1,185 @@
# Storybook Configuration and Usage
This project uses Storybook for component development and documentation, with configurations driven by `features.json`.
## Getting Started
### Running Storybook
```bash
npm run storybook
```
This will start Storybook on port 6006: http://localhost:6006
### Building Storybook
```bash
npm run build-storybook
```
This creates a static build in the `storybook-static` directory.
## Story Generator Utility
The project includes a story generator utility (`src/utils/storybook/storyGenerator.ts`) that creates stories from the `storybookStories` section in `features.json`.
### Using the Story Generator
#### Basic Usage
```typescript
import type { Meta, StoryObj } from '@storybook/react';
import Button from './Button';
import { generateMeta, generateStories } from '@/utils/storybook/storyGenerator';
// Generate meta from features.json
const meta = generateMeta(Button, 'Button') satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
// Generate all stories for the component
const stories = generateStories<typeof Button>('Button');
// Export individual stories
export const Primary: Story = stories.primary;
export const Secondary: Story = stories.secondary;
export const WithIcon: Story = stories.withIcon;
```
#### Custom Meta
You can override or extend the generated meta:
```typescript
const meta = generateMeta(Button, 'Button', {
title: 'Custom/Button/Path',
parameters: {
layout: 'fullscreen',
},
}) satisfies Meta<typeof Button>;
```
### Adding Stories to features.json
Stories are defined in the `storybookStories` section of `features.json`:
```json
{
"storybookStories": {
"ComponentName": {
"storyName": {
"name": "Display Name",
"description": "Story description",
"args": {
"prop1": "value1",
"prop2": "value2"
},
"parameters": {
"layout": "centered"
}
}
}
}
}
```
### Available Utilities
#### `generateMeta<T>(component, componentName, customMeta?)`
Generates Storybook meta configuration from features.json.
#### `generateStory<T>(storyConfig)`
Generates a single story from a story configuration.
#### `generateStories<T>(componentName)`
Generates all stories for a component.
#### `listStorybookComponents()`
Returns an array of all components that have story definitions.
#### `createMockHandlers(handlerNames)`
Creates mock event handlers for stories.
## Component Stories
Stories are organized by component category:
- **Atoms** - Basic UI building blocks (Button, TextField, Typography, Icon, IconButton)
- **Components** - Composed components (DataGrid, ConfirmDialog, FormDialog)
- **Admin** - Admin-specific components
## Best Practices
1. **Use the story generator** - Define stories in features.json and use the generator utility
2. **Keep args simple** - Complex props should have reasonable defaults
3. **Add descriptions** - Help other developers understand the story's purpose
4. **Include multiple states** - Show default, loading, error, empty states
5. **Use mock handlers** - Use `createMockHandlers()` for event handlers
## Testing Stories
Run Storybook tests with:
```bash
npm run storybook:test
```
This uses Vitest to test stories in isolation.
## Component Documentation
Storybook automatically generates documentation from:
- TypeScript prop types
- JSDoc comments
- Story configurations from features.json
Add JSDoc comments to your components:
```typescript
/**
* Button component for user interactions
*
* @example
* <Button variant="contained" color="primary" text="Click Me" />
*/
export default function Button({ text, ...props }: ButtonProps) {
// ...
}
```
## Examples
See these files for examples:
- `src/components/atoms/Button.generated.stories.tsx` - Generated stories example
- `src/components/atoms/Button.stories.tsx` - Manual stories example
- `src/components/admin/DataGrid.stories.tsx` - Complex component stories
## Troubleshooting
### Stories not appearing
1. Check that the component is in `src/**/*.stories.@(js|jsx|ts|tsx)`
2. Verify the story configuration in features.json
3. Check console for errors
### Type errors
Make sure your story definitions match the component's prop types:
```typescript
// features.json
{
"args": {
"variant": "contained", // Must be a valid variant value
"color": "primary" // Must be a valid color value
}
}
```
## Additional Resources
- [Storybook Documentation](https://storybook.js.org/)
- [Storybook Best Practices](https://storybook.js.org/docs/react/writing-stories/introduction)
- [Component Story Format](https://storybook.js.org/docs/react/api/csf)

View File

@@ -0,0 +1,81 @@
import { test, expect } from '@playwright/test';
import { runPlaybook, listPlaybooks, getPlaybooksByTag } from '../utils/playbookRunner';
/**
* Example test using playbookRunner to execute tests from features.json
*/
test.describe('Playbook-driven tests', () => {
test('should list available playbooks', () => {
const playbooks = listPlaybooks();
expect(playbooks).toBeDefined();
expect(playbooks.length).toBeGreaterThan(0);
// Check for expected playbooks from features.json
expect(playbooks).toContain('adminLogin');
expect(playbooks).toContain('createTable');
expect(playbooks).toContain('queryBuilder');
});
test('should filter playbooks by tag', () => {
const adminPlaybooks = getPlaybooksByTag('admin');
expect(Object.keys(adminPlaybooks).length).toBeGreaterThan(0);
// All returned playbooks should have the 'admin' tag
for (const playbook of Object.values(adminPlaybooks)) {
expect(playbook.tags).toContain('admin');
}
});
// Example test using a playbook from features.json
test.skip('should execute query builder playbook', async ({ page }) => {
// Note: This test is skipped as it requires a running application
// To enable, remove test.skip and ensure the app is running
await runPlaybook(page, 'queryBuilder', {
tableName: 'users',
columnName: 'name',
});
// The playbook includes assertions, so if we get here, the test passed
expect(true).toBe(true);
});
});
/**
* These tests demonstrate the playbook system but are skipped by default
* because they require a running application. In a real CI/CD environment,
* you would remove the .skip and ensure the app is running before tests.
*/
test.describe.skip('Full playbook integration tests', () => {
test('admin login flow', async ({ page }) => {
await runPlaybook(page, 'adminLogin', {
username: 'admin',
password: 'testpassword',
});
});
test('create table workflow', async ({ page }) => {
await runPlaybook(page, 'createTable', {
tableName: 'test_table_' + Date.now(),
}, { runCleanup: true });
});
test('add column workflow', async ({ page }) => {
await runPlaybook(page, 'addColumn', {
tableName: 'users',
columnName: 'test_column',
dataType: 'VARCHAR',
});
});
test('create index workflow', async ({ page }) => {
await runPlaybook(page, 'createIndex', {
tableName: 'users',
indexName: 'idx_test_' + Date.now(),
columnName: 'name',
});
});
});