testgen
Generate tests with skill-preloaded routing, framework detection, and auto-TaskCreate. Triggers on: generate tests, write tests, testgen, create test file, add test coverage.
Install
npx skills add https://github.com/0xDarkMatter/claude-mods/tree/main/skills/testgen
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install 0xdarkmatter-claude-mods@llmmart
git clone https://github.com/0xDarkMatter/claude-mods.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole 0xdarkmatter/claude-mods collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
TestGen Skill - AI Test Generation
Generate comprehensive tests with automatic framework detection, skill-preloaded general-purpose dispatch, and project convention matching.
Architecture
testgen <target> [--type] [--focus] [--depth]
│
├─→ Step 1: Analyze Target
│ ├─ File exists? → Read and parse
│ ├─ Function specified? → Extract signature
│ ├─ Directory? → List source files
│ └─ Find existing tests (avoid duplicates)
│
├─→ Step 2: Detect Framework (parallel)
│ ├─ package.json → jest/vitest/mocha/cypress/playwright
│ ├─ pyproject.toml → pytest/unittest
│ ├─ go.mod → go test
│ ├─ Cargo.toml → cargo test
│ ├─ composer.json → phpunit/pest
│ └─ Check existing test patterns
│
├─→ Step 3: Load Project Standards
│ ├─ AGENTS.md, CLAUDE.md conventions
│ ├─ Existing test file structure
│ └─ Naming conventions (*.test.ts vs *.spec.ts)
│
├─→ Step 4: Route to Test Generator (general-purpose + skill preload)
│ ├─ .ts → general-purpose, preload typescript-ops
│ ├─ .tsx/.jsx → general-purpose, preload react-ops
│ ├─ .vue → general-purpose, preload vue-ops
│ ├─ .py → general-purpose, preload python-pytest-ops
│ ├─ .go → general-purpose, preload go-ops
│ ├─ .rs → general-purpose, preload rust-ops
│ ├─ .php → general-purpose, preload laravel-ops
│ ├─ E2E/Cypress → general-purpose, preload cypress-ops
│ ├─ Playwright → general-purpose, preload typescript-ops
│ ├─ Shell/bash → general-purpose, preload bash-ops
│ ├─ --visual → Chrome DevTools MCP
│ └─ Multi-file → parallel general-purpose dispatch
│
├─→ Step 5: Generate Tests
│ ├─ Create test file in correct location
│ ├─ Follow detected conventions
│ └─ Include: happy path, edge cases, error handling
│
└─→ Step 6: Integration
├─ Auto-create task (TaskCreate) for verification
└─ Suggest: run tests, /review, /save
Execution Steps
Step 1: Analyze Target
# Check if target exists
test -f "$TARGET" && echo "FILE" || test -d "$TARGET" && echo "DIRECTORY"
# For function-specific: extract signature
command -v ast-grep >/dev/null 2>&1 && ast-grep -p "function $FUNCTION_NAME" "$FILE"
# Fallback to ripgrep
rg "(?:function|const|def|public|private)\s+$FUNCTION_NAME" "$FILE" -A 10
Check for existing tests:
fd -e test.ts -e spec.ts -e test.js -e spec.js | rg "$BASENAME"
fd "test_*.py" | rg "$BASENAME"
Step 2: Detect Framework
JavaScript/TypeScript:
cat package.json 2>/dev/null | jq -r '.devDependencies | keys[]' | grep -E 'jest|vitest|mocha|cypress|playwright|@testing-library'
Python:
grep -E "pytest|unittest|nose" pyproject.toml setup.py requirements*.txt 2>/dev/null
Go:
test -f go.mod && echo "go test available"
Rust:
test -f Cargo.toml && echo "cargo test available"
PHP:
cat composer.json 2>/dev/null | jq -r '.["require-dev"] | keys[]' | grep -E 'phpunit|pest|codeception'
Step 3: Load Project Standards
# Claude Code conventions
cat AGENTS.md 2>/dev/null | head -50
cat CLAUDE.md 2>/dev/null | head -50
# Test config files
cat jest.config.* vitest.config.* pytest.ini pyproject.toml 2>/dev/null | head -30
Test location conventions:
# JavaScript
src/utils/helper.ts → src/utils/__tests__/helper.test.ts # __tests__ folder
→ src/utils/helper.test.ts # co-located
→ tests/utils/helper.test.ts # separate tests/
# Python
app/utils/helper.py → tests/test_helper.py # tests/ folder
→ tests/utils/test_helper.py # mirror structure
# Go
pkg/auth/token.go → pkg/auth/token_test.go # co-located (required)
# Rust
src/auth.rs → src/auth.rs (mod tests { ... }) # inline tests
→ tests/auth_test.rs # integration tests
Step 4: Route to Test Generator
Dispatch is skills-first: the generic general-purpose subagent preloads the relevant -ops skill before generating tests.
| File Pattern | Dispatch | Preload |
|---|---|---|
*.ts |
general-purpose | skills/typescript-ops/SKILL.md |
*.tsx, *.jsx |
general-purpose | skills/react-ops/SKILL.md + skills/typescript-ops/SKILL.md |
*.vue |
general-purpose | skills/vue-ops/SKILL.md + skills/typescript-ops/SKILL.md |
*.py |
general-purpose | skills/python-pytest-ops/SKILL.md |
*.go |
general-purpose | skills/go-ops/SKILL.md |
*.rs |
general-purpose | skills/rust-ops/SKILL.md |
*.php |
general-purpose | skills/laravel-ops/SKILL.md |
*.cy.ts, cypress/* |
general-purpose | skills/cypress-ops/SKILL.md + skills/typescript-ops/SKILL.md |
*.spec.ts (Playwright) |
general-purpose | skills/playwright-ops/SKILL.md + skills/typescript-ops/SKILL.md |
playwright/*, e2e/* |
general-purpose | skills/playwright-ops/SKILL.md + skills/typescript-ops/SKILL.md |
*.sh, *.bash |
general-purpose | skills/bash-ops/SKILL.md |
| (--visual flag) | Chrome DevTools MCP | skills/typescript-ops/SKILL.md |
Invoke via Task tool:
Task tool with subagent_type: "general-purpose" (or surviving specialist from table)
model: "sonnet"
Prompt includes:
- Skill preloading (domain knowledge):
"First, read these files for testing context:
- Read: skills/security-ops/references/owasp-detailed.md
- Read: skills/testing-ops/SKILL.md
- Read: [Preload column for the matched file pattern]"
- Source file content
- Function signatures to test
- Detected framework and conventions
- Requested test type and focus
Language-specific preloads (append to the preloading section above):
| Language | Additional Preload | Why |
|---|---|---|
| Python | skills/python-pytest-ops/SKILL.md |
Fixtures, marks, parametrize, async testing |
| Go | skills/go-ops/SKILL.md |
Table-driven tests, benchmarks, testify |
| Rust | skills/rust-ops/SKILL.md |
Property testing, criterion, proptest |
Step 5: Generate Tests
Test categories based on --focus:
| Focus | What to Generate |
|---|---|
happy |
Normal input, expected output |
edge |
Boundary values, empty inputs, nulls |
error |
Invalid inputs, exceptions, error handling |
all |
All of the above (default) |
Depth levels:
| Depth | Coverage |
|---|---|
quick |
Happy path only, 1-2 tests per function |
normal |
Happy + common edge cases (default) |
thorough |
Comprehensive: all paths, mocking, async |
Step 6: Integration
Auto-create task:
TaskCreate:
subject: "Run generated tests for src/auth.ts"
description: "Verify generated tests pass and review edge cases"
activeForm: "Running generated tests for auth.ts"
Suggest next steps:
Tests generated: src/auth.test.ts
Next steps:
1. Run tests: npm test src/auth.test.ts
2. Review and refine edge cases
3. Use /save to persist tasks across sessions
Routing Details
TypeScript/JavaScript (preload typescript-ops)
- Proper type imports
- Generic type handling
- Async/await patterns
- Mock typing
React/JSX (preload react-ops)
- React Testing Library patterns
- Component rendering tests
- Hook testing (renderHook)
- Accessibility queries (getByRole)
Vue (preload vue-ops)
- Vue Test Utils patterns
- Composition API testing
- Pinia store mocking
Python (preload python-pytest-ops)
- pytest fixtures
- Parametrized tests
- Mock/patch patterns
- Async test handling
Go (preload go-ops)
- Table-driven tests (
[]structpattern) testing.Tand subtests (t.Run)- Testify assertions (when detected)
- Benchmark functions (
testing.B) - Parallel tests (
t.Parallel())
Rust (preload rust-ops)
#[test]attribute functions#[cfg(test)]module organization#[should_panic]for error testing- proptest/quickcheck for property testing
PHP/Laravel (preload laravel-ops)
- PHPUnit/Pest patterns
- Database transactions
- Factory usage
E2E/Cypress (preload cypress-ops)
- Page object patterns
- Custom commands
- Network stubbing
Shell/bash (preload bash-ops)
- bats / shunit2 patterns
- Stubbing external commands
- Exit-code and stderr assertions
Playwright (preload typescript-ops)
- Page object model patterns
- Locator strategies
- Visual regression testing
CLI Tool Integration
| Tool | Purpose | Fallback |
|---|---|---|
jq |
Parse package.json | Read tool |
rg |
Find existing tests | Grep tool |
ast-grep |
Parse function signatures | ripgrep patterns |
fd |
Find test files | Glob tool |
| Chrome DevTools MCP | Visual testing (--visual) | Playwright/Cypress |
Graceful degradation:
command -v jq >/dev/null 2>&1 && cat package.json | jq '.devDependencies' || cat package.json
Reference Files
For framework-specific code examples, see:
frameworks.md- Complete test examples for all supported languagesvisual-testing.md- Chrome DevTools integration for --visual flag
Integration
| Command | Relationship |
|---|---|
/review |
Review generated tests before committing |
/explain |
Understand complex code before testing |
/save |
Track test coverage goals |
Files (claude-mods)
-
assets
-
.gitkeep 0 B · in bundle
-
-
references
-
.gitkeep 0 B · in bundle
-
-
scripts
-
.gitkeep 0 B · in bundle
-
-
frameworks.md 18 KB
# TestGen Framework Examples Code examples for each supported testing framework. These are loaded on-demand when the testgen skill detects a specific framework. --- ## Jest/Vitest (TypeScript) ```typescript import { describe, it, expect, vi, beforeEach } from 'vitest'; import { validateToken, TokenError } from '../auth'; describe('validateToken', () => { beforeEach(() => { vi.clearAllMocks(); }); describe('happy path', () => { it('should return true for valid JWT token', () => { const token = 'eyJhbGciOiJIUzI1NiIs...'; expect(validateToken(token)).toBe(true); }); it('should decode payload correctly', () => { const token = createTestToken({ userId: 123 }); const result = validateToken(token); expect(result.payload.userId).toBe(123); }); }); describe('edge cases', () => { it('should handle empty string', () => { expect(validateToken('')).toBe(false); }); it('should handle malformed token', () => { expect(validateToken('not.a.token')).toBe(false); }); it('should handle expired token', () => { const expiredToken = createTestToken({ exp: Date.now() - 1000 }); expect(validateToken(expiredToken)).toBe(false); }); }); describe('error handling', () => { it('should throw TokenError for null input', () => { expect(() => validateToken(null)).toThrow(TokenError); }); it('should throw with descriptive message', () => { expect(() => validateToken(null)).toThrow('Token cannot be null'); }); }); }); ``` --- ## React Testing Library ```typescript import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { LoginForm } from '../LoginForm'; describe('LoginForm', () => { const mockOnSubmit = vi.fn(); beforeEach(() => { mockOnSubmit.mockClear(); }); it('renders email and password fields', () => { render(<LoginForm onSubmit={mockOnSubmit} />); expect(screen.getByRole('textbox', { name: /email/i })).toBeInTheDocument(); expect(screen.getByLabelText(/password/i)).toBeInTheDocument(); }); it('submits form with credentials', async () => { const user = userEvent.setup(); render(<LoginForm onSubmit={mockOnSubmit} />); await user.type(screen.getByRole('textbox', { name: /email/i }), 'test@example.com'); await user.type(screen.getByLabelText(/password/i), 'password123'); await user.click(screen.getByRole('button', { name: /submit/i })); expect(mockOnSubmit).toHaveBeenCalledWith({ email: 'test@example.com', password: 'password123', }); }); it('shows validation error for invalid email', async () => { const user = userEvent.setup(); render(<LoginForm onSubmit={mockOnSubmit} />); await user.type(screen.getByRole('textbox', { name: /email/i }), 'invalid'); await user.click(screen.getByRole('button', { name: /submit/i })); expect(await screen.findByText(/invalid email/i)).toBeInTheDocument(); expect(mockOnSubmit).not.toHaveBeenCalled(); }); it('disables submit button while loading', () => { render(<LoginForm onSubmit={mockOnSubmit} isLoading />); expect(screen.getByRole('button', { name: /submit/i })).toBeDisabled(); }); }); ``` --- ## pytest (Python) ```python import pytest from unittest.mock import Mock, patch, AsyncMock from app.auth import validate_token, TokenError class TestValidateToken: """Tests for validate_token function.""" def test_valid_token_returns_true(self): """Should return True for valid JWT token.""" token = "eyJhbGciOiJIUzI1NiIs..." assert validate_token(token) is True def test_decodes_payload_correctly(self, valid_token): """Should decode payload with correct user ID.""" result = validate_token(valid_token) assert result.payload["userId"] == 123 @pytest.mark.parametrize("invalid_input", [ "", "not.a.token", "a.b", None, ]) def test_rejects_invalid_tokens(self, invalid_input): """Should return False for invalid token formats.""" assert validate_token(invalid_input) is False def test_rejects_expired_token(self, expired_token): """Should return False for expired tokens.""" assert validate_token(expired_token) is False def test_raises_token_error_for_null(self): """Should raise TokenError with descriptive message.""" with pytest.raises(TokenError, match="Token cannot be null"): validate_token(None) @pytest.fixture def valid_token(self): """Create a valid test token.""" return create_test_token({"userId": 123}) @pytest.fixture def expired_token(self): """Create an expired test token.""" return create_test_token({"exp": time.time() - 1000}) class TestValidateTokenAsync: """Tests for async token validation.""" @pytest.mark.asyncio async def test_async_validation(self): """Should validate token asynchronously.""" token = create_test_token({"userId": 456}) result = await validate_token_async(token) assert result.valid is True @pytest.mark.asyncio async def test_handles_network_timeout(self): """Should handle network timeout gracefully.""" with patch("app.auth.fetch_public_key", new_callable=AsyncMock) as mock: mock.side_effect = TimeoutError() with pytest.raises(TokenError, match="Validation timeout"): await validate_token_async("token") ``` --- ## Go (Table-Driven Tests) ```go package auth import ( "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestValidateToken(t *testing.T) { t.Parallel() tests := []struct { name string token string want bool wantErr error }{ { name: "valid token", token: "eyJhbGciOiJIUzI1NiIs...", want: true, }, { name: "empty string", token: "", want: false, }, { name: "malformed token", token: "not.a.token", want: false, }, { name: "nil token", token: "", wantErr: ErrTokenNil, }, } for _, tt := range tests { tt := tt // capture range variable t.Run(tt.name, func(t *testing.T) { t.Parallel() got, err := ValidateToken(tt.token) if tt.wantErr != nil { require.ErrorIs(t, err, tt.wantErr) return } require.NoError(t, err) assert.Equal(t, tt.want, got) }) } } func TestValidateToken_Expired(t *testing.T) { token := createTestToken(t, TokenClaims{ UserID: 123, Exp: time.Now().Add(-1 * time.Hour), }) got, err := ValidateToken(token) require.NoError(t, err) assert.False(t, got, "expired token should be invalid") } func TestValidateToken_Integration(t *testing.T) { if testing.Short() { t.Skip("skipping integration test in short mode") } client := NewAuthClient(testConfig) token, _ := client.GenerateToken(TestUser) got, err := ValidateToken(token) require.NoError(t, err) assert.True(t, got) } func BenchmarkValidateToken(b *testing.B) { token := createValidToken() b.ResetTimer() for i := 0; i < b.N; i++ { ValidateToken(token) } } func createTestToken(t *testing.T, claims TokenClaims) string { t.Helper() token, err := generateToken(claims, testSecret) require.NoError(t, err) return token } ``` --- ## Rust (#[test] Patterns) ```rust use crate::auth::{validate_token, TokenError, TokenClaims}; use std::time::{Duration, SystemTime}; #[cfg(test)] mod tests { use super::*; #[test] fn valid_token_returns_ok() { let token = create_test_token(TokenClaims { user_id: 123, exp: future_time(), }); let result = validate_token(&token); assert!(result.is_ok()); assert!(result.unwrap()); } #[test] fn empty_token_returns_false() { let result = validate_token(""); assert!(result.is_ok()); assert!(!result.unwrap()); } #[test] fn malformed_token_returns_false() { let result = validate_token("not.a.token"); assert!(result.is_ok()); assert!(!result.unwrap()); } #[test] fn expired_token_returns_false() { let token = create_test_token(TokenClaims { user_id: 123, exp: past_time(), }); let result = validate_token(&token); assert!(result.is_ok()); assert!(!result.unwrap(), "expired token should be invalid"); } #[test] #[should_panic(expected = "Token cannot be null")] fn null_token_panics() { validate_token_unchecked(None); } #[test] fn returns_token_error_for_invalid_signature() { let token = "eyJhbGciOiJIUzI1NiIs.tampered.signature"; let result = validate_token(token); assert!(matches!(result, Err(TokenError::InvalidSignature))); } #[test] fn rejects_various_invalid_tokens() { let invalid_tokens = [ ("empty", ""), ("single_part", "abc"), ("two_parts", "a.b"), ("whitespace", " "), ("special_chars", "!@#$%"), ]; for (name, token) in invalid_tokens { let result = validate_token(token); assert!( result.is_ok() && !result.unwrap(), "case '{}' should return false", name ); } } #[tokio::test] async fn async_validation_works() { let token = create_test_token(valid_claims()); let result = validate_token_async(&token).await; assert!(result.is_ok()); } fn create_test_token(claims: TokenClaims) -> String { crate::auth::generate_token(&claims, TEST_SECRET).unwrap() } fn valid_claims() -> TokenClaims { TokenClaims { user_id: 123, exp: future_time(), } } fn future_time() -> SystemTime { SystemTime::now() + Duration::from_secs(3600) } fn past_time() -> SystemTime { SystemTime::now() - Duration::from_secs(3600) } const TEST_SECRET: &[u8] = b"test_secret_key"; } #[cfg(test)] mod property_tests { use super::*; use proptest::prelude::*; proptest! { #[test] fn doesnt_crash_on_arbitrary_input(s in "\\PC*") { let _ = validate_token(&s); } #[test] fn valid_tokens_always_validate(user_id in 1u64..1000000) { let token = create_test_token(TokenClaims { user_id, exp: future_time(), }); let result = validate_token(&token); prop_assert!(result.is_ok()); prop_assert!(result.unwrap()); } } } ``` --- ## PHPUnit (PHP) ```php <?php namespace Tests\Unit\Services; use PHPUnit\Framework\TestCase; use App\Services\AuthService; use App\Exceptions\TokenException; use Mockery; class AuthServiceTest extends TestCase { private AuthService $service; protected function setUp(): void { parent::setUp(); $this->service = new AuthService(); } protected function tearDown(): void { Mockery::close(); parent::tearDown(); } /** @test */ public function it_validates_correct_token(): void { $token = $this->createValidToken(['user_id' => 123]); $result = $this->service->validateToken($token); $this->assertTrue($result); } /** @test */ public function it_rejects_expired_token(): void { $token = $this->createExpiredToken(); $result = $this->service->validateToken($token); $this->assertFalse($result); } /** @test */ public function it_throws_for_null_token(): void { $this->expectException(TokenException::class); $this->expectExceptionMessage('Token cannot be null'); $this->service->validateToken(null); } /** * @test * @dataProvider invalidTokenProvider */ public function it_rejects_invalid_tokens(string $invalidToken): void { $result = $this->service->validateToken($invalidToken); $this->assertFalse($result); } public static function invalidTokenProvider(): array { return [ 'empty string' => [''], 'malformed' => ['not.a.token'], 'missing parts' => ['a.b'], ]; } } ``` --- ## Pest (PHP) ```php <?php use App\Services\AuthService; use App\Exceptions\TokenException; describe('AuthService', function () { beforeEach(function () { $this->service = new AuthService(); }); describe('validateToken', function () { it('validates correct token', function () { $token = createValidToken(['user_id' => 123]); expect($this->service->validateToken($token))->toBeTrue(); }); it('rejects expired token', function () { $token = createExpiredToken(); expect($this->service->validateToken($token))->toBeFalse(); }); it('throws for null token', function () { $this->service->validateToken(null); })->throws(TokenException::class, 'Token cannot be null'); it('rejects invalid tokens', function (string $invalidToken) { expect($this->service->validateToken($invalidToken))->toBeFalse(); })->with([ 'empty string' => '', 'malformed' => 'not.a.token', 'missing parts' => 'a.b', ]); }); }); ``` --- ## Cypress (E2E) ```typescript describe('Login Flow', () => { beforeEach(() => { cy.visit('/login'); }); it('should login with valid credentials', () => { cy.get('[data-cy=email]').type('user@example.com'); cy.get('[data-cy=password]').type('password123'); cy.get('[data-cy=submit]').click(); cy.url().should('include', '/dashboard'); cy.get('[data-cy=welcome]').should('contain', 'Welcome'); }); it('should show error with invalid credentials', () => { cy.intercept('POST', '/api/login', { statusCode: 401, body: { error: 'Invalid credentials' }, }).as('loginRequest'); cy.get('[data-cy=email]').type('user@example.com'); cy.get('[data-cy=password]').type('wrong'); cy.get('[data-cy=submit]').click(); cy.wait('@loginRequest'); cy.get('[data-cy=error]').should('be.visible'); cy.url().should('include', '/login'); }); it('should persist session after reload', () => { cy.login('user@example.com', 'password123'); cy.visit('/dashboard'); cy.reload(); cy.get('[data-cy=welcome]').should('be.visible'); }); }); ``` --- ## Cypress (Component) ```typescript import LoginForm from './LoginForm.vue'; describe('LoginForm Component', () => { it('renders login form', () => { cy.mount(LoginForm); cy.get('[data-cy=email]').should('exist'); cy.get('[data-cy=password]').should('exist'); cy.get('[data-cy=submit]').should('contain', 'Login'); }); it('emits submit event with credentials', () => { const onSubmitSpy = cy.spy().as('submitSpy'); cy.mount(LoginForm, { props: { onSubmit: onSubmitSpy } }); cy.get('[data-cy=email]').type('user@example.com'); cy.get('[data-cy=password]').type('password123'); cy.get('[data-cy=submit]').click(); cy.get('@submitSpy').should('have.been.calledWith', { email: 'user@example.com', password: 'password123', }); }); it('validates email format', () => { cy.mount(LoginForm); cy.get('[data-cy=email]').type('invalid-email'); cy.get('[data-cy=submit]').click(); cy.get('[data-cy=email-error]').should('contain', 'Invalid email'); }); }); ``` --- ## Playwright (E2E) ```typescript import { test, expect } from '@playwright/test'; test.describe('Login Flow', () => { test.beforeEach(async ({ page }) => { await page.goto('/login'); }); test('should login with valid credentials', async ({ page }) => { await page.getByRole('textbox', { name: /email/i }).fill('user@example.com'); await page.getByRole('textbox', { name: /password/i }).fill('password123'); await page.getByRole('button', { name: /submit/i }).click(); await expect(page).toHaveURL(/dashboard/); await expect(page.getByText(/welcome/i)).toBeVisible(); }); test('should show error with invalid credentials', async ({ page }) => { await page.route('**/api/login', async (route) => { await route.fulfill({ status: 401, body: JSON.stringify({ error: 'Invalid credentials' }), }); }); await page.getByRole('textbox', { name: /email/i }).fill('user@example.com'); await page.getByRole('textbox', { name: /password/i }).fill('wrong'); await page.getByRole('button', { name: /submit/i }).click(); await expect(page.getByTestId('error-message')).toBeVisible(); await expect(page).toHaveURL(/login/); }); test('should validate email format', async ({ page }) => { await page.getByRole('textbox', { name: /email/i }).fill('invalid-email'); await page.getByRole('button', { name: /submit/i }).click(); await expect(page.getByText(/invalid email/i)).toBeVisible(); }); test('should persist session after reload', async ({ page, context }) => { await page.getByRole('textbox', { name: /email/i }).fill('user@example.com'); await page.getByRole('textbox', { name: /password/i }).fill('password123'); await page.getByRole('button', { name: /submit/i }).click(); await expect(page).toHaveURL(/dashboard/); await page.reload(); await expect(page.getByText(/welcome/i)).toBeVisible(); }); test('should be accessible', async ({ page }) => { const accessibilityScanResults = await new AxeBuilder({ page }).analyze(); expect(accessibilityScanResults.violations).toEqual([]); }); }); test.describe('Login Form - Visual', () => { test('matches snapshot', async ({ page }) => { await page.goto('/login'); await expect(page).toHaveScreenshot('login-form.png'); }); test('error state matches snapshot', async ({ page }) => { await page.goto('/login'); await page.getByRole('textbox', { name: /email/i }).fill('invalid'); await page.getByRole('button', { name: /submit/i }).click(); await expect(page).toHaveScreenshot('login-form-error.png'); }); }); ``` -
SKILL.md 10.2 KB
--- name: testgen description: "Generate tests with skill-preloaded routing, framework detection, and auto-TaskCreate. Triggers on: generate tests, write tests, testgen, create test file, add test coverage." when_to_use: "Use when the user asks to generate or expand tests for a file, function, or directory — e.g. 'write tests for auth.ts', 'add coverage for the parser', 'create a test file for this module'." argument-hint: "<target> [--focus happy|edge|error|all] [--depth quick|normal|thorough] [--visual]" license: MIT allowed-tools: "Read Write Edit Bash Glob Grep Task TaskCreate" metadata: author: claude-mods --- # TestGen Skill - AI Test Generation Generate comprehensive tests with automatic framework detection, skill-preloaded general-purpose dispatch, and project convention matching. ## Architecture ``` testgen <target> [--type] [--focus] [--depth] │ ├─→ Step 1: Analyze Target │ ├─ File exists? → Read and parse │ ├─ Function specified? → Extract signature │ ├─ Directory? → List source files │ └─ Find existing tests (avoid duplicates) │ ├─→ Step 2: Detect Framework (parallel) │ ├─ package.json → jest/vitest/mocha/cypress/playwright │ ├─ pyproject.toml → pytest/unittest │ ├─ go.mod → go test │ ├─ Cargo.toml → cargo test │ ├─ composer.json → phpunit/pest │ └─ Check existing test patterns │ ├─→ Step 3: Load Project Standards │ ├─ AGENTS.md, CLAUDE.md conventions │ ├─ Existing test file structure │ └─ Naming conventions (*.test.ts vs *.spec.ts) │ ├─→ Step 4: Route to Test Generator (general-purpose + skill preload) │ ├─ .ts → general-purpose, preload typescript-ops │ ├─ .tsx/.jsx → general-purpose, preload react-ops │ ├─ .vue → general-purpose, preload vue-ops │ ├─ .py → general-purpose, preload python-pytest-ops │ ├─ .go → general-purpose, preload go-ops │ ├─ .rs → general-purpose, preload rust-ops │ ├─ .php → general-purpose, preload laravel-ops │ ├─ E2E/Cypress → general-purpose, preload cypress-ops │ ├─ Playwright → general-purpose, preload typescript-ops │ ├─ Shell/bash → general-purpose, preload bash-ops │ ├─ --visual → Chrome DevTools MCP │ └─ Multi-file → parallel general-purpose dispatch │ ├─→ Step 5: Generate Tests │ ├─ Create test file in correct location │ ├─ Follow detected conventions │ └─ Include: happy path, edge cases, error handling │ └─→ Step 6: Integration ├─ Auto-create task (TaskCreate) for verification └─ Suggest: run tests, /review, /save ``` ## Execution Steps ### Step 1: Analyze Target ```bash # Check if target exists test -f "$TARGET" && echo "FILE" || test -d "$TARGET" && echo "DIRECTORY" # For function-specific: extract signature command -v ast-grep >/dev/null 2>&1 && ast-grep -p "function $FUNCTION_NAME" "$FILE" # Fallback to ripgrep rg "(?:function|const|def|public|private)\s+$FUNCTION_NAME" "$FILE" -A 10 ``` **Check for existing tests:** ```bash fd -e test.ts -e spec.ts -e test.js -e spec.js | rg "$BASENAME" fd "test_*.py" | rg "$BASENAME" ``` ### Step 2: Detect Framework **JavaScript/TypeScript:** ```bash cat package.json 2>/dev/null | jq -r '.devDependencies | keys[]' | grep -E 'jest|vitest|mocha|cypress|playwright|@testing-library' ``` **Python:** ```bash grep -E "pytest|unittest|nose" pyproject.toml setup.py requirements*.txt 2>/dev/null ``` **Go:** ```bash test -f go.mod && echo "go test available" ``` **Rust:** ```bash test -f Cargo.toml && echo "cargo test available" ``` **PHP:** ```bash cat composer.json 2>/dev/null | jq -r '.["require-dev"] | keys[]' | grep -E 'phpunit|pest|codeception' ``` ### Step 3: Load Project Standards ```bash # Claude Code conventions cat AGENTS.md 2>/dev/null | head -50 cat CLAUDE.md 2>/dev/null | head -50 # Test config files cat jest.config.* vitest.config.* pytest.ini pyproject.toml 2>/dev/null | head -30 ``` **Test location conventions:** ``` # JavaScript src/utils/helper.ts → src/utils/__tests__/helper.test.ts # __tests__ folder → src/utils/helper.test.ts # co-located → tests/utils/helper.test.ts # separate tests/ # Python app/utils/helper.py → tests/test_helper.py # tests/ folder → tests/utils/test_helper.py # mirror structure # Go pkg/auth/token.go → pkg/auth/token_test.go # co-located (required) # Rust src/auth.rs → src/auth.rs (mod tests { ... }) # inline tests → tests/auth_test.rs # integration tests ``` ### Step 4: Route to Test Generator Dispatch is skills-first: the generic `general-purpose` subagent preloads the relevant `-ops` skill before generating tests. | File Pattern | Dispatch | Preload | |--------------|----------|---------| | `*.ts` | general-purpose | `skills/typescript-ops/SKILL.md` | | `*.tsx`, `*.jsx` | general-purpose | `skills/react-ops/SKILL.md` + `skills/typescript-ops/SKILL.md` | | `*.vue` | general-purpose | `skills/vue-ops/SKILL.md` + `skills/typescript-ops/SKILL.md` | | `*.py` | general-purpose | `skills/python-pytest-ops/SKILL.md` | | `*.go` | general-purpose | `skills/go-ops/SKILL.md` | | `*.rs` | general-purpose | `skills/rust-ops/SKILL.md` | | `*.php` | general-purpose | `skills/laravel-ops/SKILL.md` | | `*.cy.ts`, `cypress/*` | general-purpose | `skills/cypress-ops/SKILL.md` + `skills/typescript-ops/SKILL.md` | | `*.spec.ts` (Playwright) | general-purpose | `skills/playwright-ops/SKILL.md` + `skills/typescript-ops/SKILL.md` | | `playwright/*`, `e2e/*` | general-purpose | `skills/playwright-ops/SKILL.md` + `skills/typescript-ops/SKILL.md` | | `*.sh`, `*.bash` | general-purpose | `skills/bash-ops/SKILL.md` | | (--visual flag) | Chrome DevTools MCP | `skills/typescript-ops/SKILL.md` | **Invoke via Task tool:** ``` Task tool with subagent_type: "general-purpose" (or surviving specialist from table) model: "sonnet" Prompt includes: - Skill preloading (domain knowledge): "First, read these files for testing context: - Read: skills/security-ops/references/owasp-detailed.md - Read: skills/testing-ops/SKILL.md - Read: [Preload column for the matched file pattern]" - Source file content - Function signatures to test - Detected framework and conventions - Requested test type and focus ``` **Language-specific preloads** (append to the preloading section above): | Language | Additional Preload | Why | |----------|-------------------|-----| | Python | `skills/python-pytest-ops/SKILL.md` | Fixtures, marks, parametrize, async testing | | Go | `skills/go-ops/SKILL.md` | Table-driven tests, benchmarks, testify | | Rust | `skills/rust-ops/SKILL.md` | Property testing, criterion, proptest | ### Step 5: Generate Tests **Test categories based on --focus:** | Focus | What to Generate | |-------|------------------| | `happy` | Normal input, expected output | | `edge` | Boundary values, empty inputs, nulls | | `error` | Invalid inputs, exceptions, error handling | | `all` | All of the above (default) | **Depth levels:** | Depth | Coverage | |-------|----------| | `quick` | Happy path only, 1-2 tests per function | | `normal` | Happy + common edge cases (default) | | `thorough` | Comprehensive: all paths, mocking, async | ### Step 6: Integration **Auto-create task:** ``` TaskCreate: subject: "Run generated tests for src/auth.ts" description: "Verify generated tests pass and review edge cases" activeForm: "Running generated tests for auth.ts" ``` **Suggest next steps:** ``` Tests generated: src/auth.test.ts Next steps: 1. Run tests: npm test src/auth.test.ts 2. Review and refine edge cases 3. Use /save to persist tasks across sessions ``` --- ## Routing Details ### TypeScript/JavaScript (preload typescript-ops) - Proper type imports - Generic type handling - Async/await patterns - Mock typing ### React/JSX (preload react-ops) - React Testing Library patterns - Component rendering tests - Hook testing (renderHook) - Accessibility queries (getByRole) ### Vue (preload vue-ops) - Vue Test Utils patterns - Composition API testing - Pinia store mocking ### Python (preload python-pytest-ops) - pytest fixtures - Parametrized tests - Mock/patch patterns - Async test handling ### Go (preload go-ops) - Table-driven tests (`[]struct` pattern) - `testing.T` and subtests (`t.Run`) - Testify assertions (when detected) - Benchmark functions (`testing.B`) - Parallel tests (`t.Parallel()`) ### Rust (preload rust-ops) - `#[test]` attribute functions - `#[cfg(test)]` module organization - `#[should_panic]` for error testing - proptest/quickcheck for property testing ### PHP/Laravel (preload laravel-ops) - PHPUnit/Pest patterns - Database transactions - Factory usage ### E2E/Cypress (preload cypress-ops) - Page object patterns - Custom commands - Network stubbing ### Shell/bash (preload bash-ops) - bats / shunit2 patterns - Stubbing external commands - Exit-code and stderr assertions ### Playwright (preload typescript-ops) - Page object model patterns - Locator strategies - Visual regression testing --- ## CLI Tool Integration | Tool | Purpose | Fallback | |------|---------|----------| | `jq` | Parse package.json | Read tool | | `rg` | Find existing tests | Grep tool | | `ast-grep` | Parse function signatures | ripgrep patterns | | `fd` | Find test files | Glob tool | | Chrome DevTools MCP | Visual testing (--visual) | Playwright/Cypress | **Graceful degradation:** ```bash command -v jq >/dev/null 2>&1 && cat package.json | jq '.devDependencies' || cat package.json ``` --- ## Reference Files For framework-specific code examples, see: - `frameworks.md` - Complete test examples for all supported languages - `visual-testing.md` - Chrome DevTools integration for --visual flag --- ## Integration | Command | Relationship | |---------|--------------| | `/review` | Review generated tests before committing | | `/explain` | Understand complex code before testing | | `/save` | Track test coverage goals | -
visual-testing.md 4.6 KB
# Visual Testing with Chrome DevTools Documentation for the `--visual` flag which uses Chrome DevTools MCP for interactive visual testing. --- ## Overview The `--visual` flag enables browser-based testing using Chrome DevTools MCP tools. This provides real browser verification, accessibility snapshots, and visual regression testing. ```bash # Visual test with Chrome DevTools /testgen src/pages/Login.tsx --visual # Combined with E2E type /testgen src/components/Form.tsx --type e2e --visual ``` --- ## How It Works 1. **Detects Chrome DevTools MCP availability** via `mcp__chrome_devtools__*` tools 2. **For each component/page:** - Launches browser with `mcp__chrome_devtools__navigate_page` - Takes accessibility snapshot with `mcp__chrome_devtools__take_snapshot` - Captures screenshot with `mcp__chrome_devtools__take_screenshot` - Tests interactions with `mcp__chrome_devtools__click`, `mcp__chrome_devtools__fill` 3. **Generates test documentation with:** - Visual regression baselines - Accessibility tree verification - Interactive element mapping --- ## Chrome DevTools MCP Tools | Tool | Purpose | |------|---------| | `navigate_page` | Load component in browser | | `take_snapshot` | Get accessibility tree for element refs | | `take_screenshot` | Capture visual state | | `click` | Test interactive elements | | `fill` | Test form inputs | | `list_console_messages` | Capture JS errors | | `list_network_requests` | Verify API calls | | `performance_start_trace` | Performance profiling | --- ## When to Use --visual | Scenario | Recommended | |----------|-------------| | Component styling verification | Yes | | Form interaction testing | Yes | | Accessibility compliance | Yes | | Unit testing pure functions | No | | API integration tests | No | --- ## Example Output ```markdown ## Visual Test Results: LoginForm ### Accessibility Snapshot - Form: role="form", aria-label="Login" - Email input: role="textbox", name="email" - Password input: role="textbox", name="password" (masked) - Submit button: role="button", name="Log in" ### Screenshot Captured: login-form-baseline.png ### Interactive Tests - [PASS] Email field accepts input - [PASS] Password field masks characters - [PASS] Submit button triggers form validation - [PASS] Error state displays for invalid email ### Console Errors None detected ### Suggested Playwright Tests [Generated code based on visual analysis] ``` --- ## Claude-in-Chrome Integration When the `mcp__claude-in-chrome__*` tools are available, `--visual` can also use: | Tool | Purpose | |------|---------| | `read_page` | Get comprehensive accessibility tree | | `find` | Locate elements by natural language | | `computer` | Simulate real user interactions | | `get_page_text` | Extract text content | --- ## Detection Logic ```bash # Check for Chrome DevTools MCP if available(mcp__chrome_devtools__take_snapshot); then echo "Chrome DevTools available - enabling visual testing" fi # Check for Claude-in-Chrome if available(mcp__claude-in-chrome__read_page); then echo "Claude-in-Chrome available - enabling enhanced visual testing" fi ``` --- ## Fallback Chain If Chrome DevTools is not available, `--visual` gracefully degrades: ``` Chrome DevTools MCP → Claude-in-Chrome → Playwright → Cypress → Manual testing ``` 1. **Chrome DevTools**: Real-time browser control via MCP 2. **Claude-in-Chrome**: Enhanced accessibility and natural language queries 3. **Playwright**: Generate Playwright test code for manual execution 4. **Cypress**: Generate Cypress test code for manual execution 5. **Manual**: Output instructions for manual visual testing --- ## Advanced Flags ### --coverage + --visual Combine coverage analysis with visual testing: ```bash /testgen src/components/ --coverage --visual ``` This identifies untested visual states and generates tests for them. ### --from-review + --visual Generate visual regression tests for UI issues found by `/review`: ```bash /testgen --from-review --visual ``` --- ## Output Artifacts When `--visual` is used, the following artifacts may be created: | Artifact | Location | Purpose | |----------|----------|---------| | Screenshots | `tests/__screenshots__/` | Visual regression baselines | | Accessibility snapshots | `tests/__a11y__/` | A11y tree for comparison | | Generated tests | `tests/visual/` | Playwright/Cypress test files | --- ## Integration with CI/CD Visual tests can be run in CI with Chrome: ```yaml # GitHub Actions example - name: Visual Tests run: | npx playwright test --project=chromium env: CI: true ``` For Chrome DevTools MCP in CI, ensure the browser is launched in headless mode with remote debugging enabled.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.