| name | ds-test-component |
| description | Auto-generate all test files for DS components including visual, a11y, component, page object, and unit tests. Use when creating new components or setting up comprehensive test coverage. Use when this capability is needed. |
| metadata | {"author":"baloise"} |
Test Component
Auto-generates all test files for a Helvetia Design System component in one command.
Quick Start
Generate all tests for a component:
ds-test-component button
Process:
- Parse component props, events, slots, parts, states
- Show checklists for visual props and slots
- Generate 6 file types (or fewer if not applicable)
- Report summary with test counts
Output files:
button.visual.html — Visual test fixture with variant sections
button.visual.play.ts — Playwright visual regression tests
button.a11y.play.ts — Comprehensive accessibility tests
button.component.play.ts — Event and behavior tests
button.po.ts — Page object in packages/playwright/src/lib/components/
button.util.spec.ts — Unit tests (if .util.ts exists)
What Gets Generated
1. Visual Tests
visual.html — Test fixture with sections:
- Basic — Minimal component demo with auto-detected common props
- Enum variants — All color/size values in one section per enum
- State variants — Separate section per state (disabled, loading, invalid, etc.)
- Slot demos — One section per slot, auto-detected from
render() JSX
visual.play.ts — Playwright tests:
- "style" suite: screenshots from
button.style.html
- "host" suite: screenshots from
button.visual.html
- One test per variant
Data-Driven Test Pattern (VARIANTS constant)
Use a simple string array VARIANTS constant to avoid repetitive test code:
Define VARIANTS with testid strings:
const TAG = 'data'
const VARIANTS = ['basic', 'border', 'horizontal', 'multiline', 'required', 'disabled', 'custom-form'] as const
const image = screenshot(TAG)
Loop through variants in forEach:
test.describe('host', () => {
test.beforeEach('Setup', async ({ page }) => {
await page.setupVisualTest(`/components/${TAG}/test/${TAG}.visual.html`)
})
VARIANTS.forEach(variant => {
test(variant, async ({ page }) => {
const el = page.getByTestId(variant)
await expectScreenshot(el, image(variant))
})
})
})
If component has both style.html and visual.html:
test.describe('style', () => {
test.beforeEach('Setup', async ({ page }) => {
await page.setupVisualTest(`/components/${TAG}/test/${TAG}.style.html`)
})
VARIANTS.forEach(variant => {
test(variant, async ({ page }) => {
const el = page.getByTestId(variant)
await expectScreenshot(el, image(`style-${variant}`))
})
})
})
test.describe('host', () => {
test.beforeEach('Setup', async ({ page }) => {
await page.setupVisualTest(`/components/${TAG}/test/${TAG}.visual.html`)
})
VARIANTS.forEach(variant => {
test(variant, async ({ page }) => {
const el = page.getByTestId(variant)
await expectScreenshot(el, image(variant))
})
})
})
Benefits:
- Simple — just a string array, no complex objects
- DRY — one forEach covers all variants
- Maintainable — testid is both the test name and selector
- Scalable — works for any number of variants
- Consistent — matches the tag component pattern
2. Accessibility Tests
a11y.play.ts — Comprehensive a11y coverage:
- Axe-core checks (auto-detected violations)
- Semantic checks (ARIA labels, roles, heading hierarchy)
- Contrast checks (WCAG AA compliance)
- Tests all variants (colors, sizes, states)
3. Component Tests
component.play.ts — Behavior tests:
- Event tests — Auto-discovered from
@Event() decorators
- Verify each event fires
- Verify event doesn't fire when expected (e.g., disabled)
- State tests — Auto-discovered from boolean props
- Assertions for each state (disabled, loading, invalid, etc.)
- Slot tests — Verify each slot renders correctly
- Value tests — If component has
value prop, test hasValue() assertion
4. Page Object
button.po.ts — Page object for testing:
- Part locators —
readonly property for each @part JSDoc tag
- Pattern:
native = this.el.locator('[part="native"]')
- Action methods —
click(), focus(), etc.
- Assertion methods —
assertToBeDisabled(), assertToBeLoading(), etc.
- One method per boolean state prop
- Plus
hasValue() if component has value prop`
Located in: packages/playwright/src/lib/components/button.po.ts
5. Unit Tests (if applicable)
button.util.spec.ts — Tests for utility functions:
- Auto-detects
button.util.ts if it exists
- Generates comprehensive test cases per function
- Happy paths (valid inputs → expected outputs)
- Edge cases (undefined, null, empty string, invalid values)
- Type variations (if function accepts multiple types)
- Ready to run with
pnpm test
Workflow
Step 1: Invoke
ds-test-component button
Step 2: Confirm Visual Props
Detected props: color, size, disabled, loading, icon, label
Which are visual props? (deselect unwanted)
✓ color (enum)
✓ size (enum)
✓ disabled (state)
✓ loading (state)
✓ icon (prop)
✗ label (skip)
Step 3: Confirm Slots
Detected slots: icon, label, badge
Which to demo? (deselect unwanted)
✓ icon
✓ label
✓ badge
Step 4: Files Generated
✓ button.visual.html (8 variant sections)
✓ button.visual.play.ts (16 visual tests)
✓ button.a11y.play.ts (12 a11y tests)
✓ button.component.play.ts (8 event + 4 state tests)
✓ button.po.ts (5 parts + 4 assertions)
✓ button.util.spec.ts (4 utils, 18 test cases)
Next: Review files, run tests: pnpm play, pnpm test
Example: Button Component
Generated visual.html sections:
<section data-testid="basic">
<span>Basic</span>
<ds-button icon="plus">Button</ds-button>
</section>
<section data-testid="colors">
<span>Colors</span>
<ds-button color="primary">Primary</ds-button>
<ds-button color="secondary">Secondary</ds-button>
...
</section>
<section data-testid="disabled">
<span>Disabled</span>
<ds-button disabled>Disabled</ds-button>
</section>
<section data-testid="slot-icon">
Slot: Icon
Button
Generated component test:
test('should fire dsClick event', async ({ page }) => {
await page.mount(`<ds-button>Click me</ds-button>`)
const dsButton = new DsButton(page.locator('ds-button'))
const spy = await dsButton.el.spyOnEvent('dsClick')
await dsButton.click()
expect(spy).toHaveReceivedEventTimes(1)
})
test('should not fire dsClick when disabled', async ({ page }) => {
await page.mount(`<ds-button disabled>Disabled</ds-button>`)
const dsButton = new DsButton(page.locator('ds-button'))
const spy = await dsButton.el.spyOnEvent('dsClick')
await dsButton.assertToBeDisabled()
expect(spy).toHaveReceivedEventTimes(0)
})
Generated page object:
export class DsButton extends PageObject {
readonly native = this.el.locator('[part="native"]')
readonly spinner = this.el.locator('[part="spinner"]')
readonly icon = this.el.locator('[part="icon"]')
async click() {
await this.native.click()
}
async assertToBeDisabled() {
await expect(this.el).toHaveAttribute('disabled')
}
async assertToBeLoading() {
await expect(this.el).toHaveAttribute('loading')
}
async hasValue(value: string) {
await expect(this.).(value)
}
}
Key Behaviors
- ✅ Auto-discovers events from
@Event() decorators
- ✅ Auto-discovers states from boolean props (disabled, loading, invalid, checked, etc.)
- ✅ Auto-discovers slots from
render() JSX, auto-generates demo content
- ✅ Auto-discovers parts from
@part JSDoc tags
- ✅ Auto-discovers utils — generates comprehensive tests if
.util.ts exists
- ✅ Never skips visual props that user confirms — all get sections
- ✅ Never skips events or states — all get test coverage
- ✅ Comprehensive unit tests — not stubs; edge cases included
Related
See REFERENCE.md for detailed generation rules for each test type.
Source: baloise/design-system — distributed by TomeVault.