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.
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 sectionsbutton.visual.play.ts— Playwright visual regression testsbutton.a11y.play.ts— Comprehensive accessibility testsbutton.component.play.ts— Event and behavior testsbutton.po.ts— Page object inpackages/playwright/src/lib/components/button.util.spec.ts— Unit tests (if.util.tsexists)
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
valueprop, testhasValue()assertion
4. Page Object
button.po.ts — Page object for testing:
- Part locators —
readonlyproperty for each@partJSDoc tag- Pattern:
native = this.el.locator('[part="native"]')
- Pattern:
- Action methods —
click(),focus(), etc. - Assertion methods —
assertToBeDisabled(),assertToBeLoading(), etc.- One method per boolean state prop
- Plus
hasValue()if component hasvalueprop`
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.tsif 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
npm 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: npm run play, npm test
Example: Button Component
Generated visual.html sections:
<!-- Basic -->
<section data-testid="basic">
<span>Basic</span>
<ds-button icon="plus">Button</ds-button>
</section>
<!-- Colors (enum) -->
<section data-testid="colors">
<span>Colors</span>
<ds-button color="primary">Primary</ds-button>
<ds-button color="secondary">Secondary</ds-button>
...
</section>
<!-- Disabled (state) -->
<section data-testid="disabled">
<span>Disabled</span>
<ds-button disabled>Disabled</ds-button>
</section>
<!-- Icon slot demo -->
<section data-testid="slot-icon">
<span>Slot: Icon</span>
<ds-button><ds-icon name="plus"></ds-icon>Button</ds-button>
</section>
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.el).toHaveValue(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
@partJSDoc tags - ✅ Auto-discovers utils — generates comprehensive tests if
.util.tsexists - ✅ 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.