196 lines
21 KiB
JSON
196 lines
21 KiB
JSON
{
|
|
"name": "Testing Automation Workflow",
|
|
"description": "Comprehensive testing workflow for unit, integration, and end-to-end testing",
|
|
"workflowType": "testing-automation",
|
|
"applicablePatterns": ["Unit Testing", "Integration Testing", "E2E Testing", "Performance Testing"],
|
|
"phases": {
|
|
"planning": {
|
|
"description": "Test planning and strategy phase",
|
|
"activities": [
|
|
"Define testing strategy and coverage goals",
|
|
"Identify critical paths and edge cases",
|
|
"Plan test data and fixtures",
|
|
"Define testing environments and CI/CD integration",
|
|
"Establish quality gates and acceptance criteria"
|
|
]
|
|
},
|
|
"implementation": {
|
|
"description": "Test implementation phase",
|
|
"activities": [
|
|
"Write unit tests for individual functions and components",
|
|
"Create integration tests for API endpoints and workflows",
|
|
"Implement end-to-end tests for user journeys",
|
|
"Set up test data factories and fixtures",
|
|
"Configure test environments and mocking"
|
|
]
|
|
},
|
|
"automation": {
|
|
"description": "Test automation and CI/CD integration",
|
|
"activities": [
|
|
"Integrate tests into CI/CD pipeline",
|
|
"Set up parallel test execution",
|
|
"Configure test reporting and notifications",
|
|
"Implement test result analysis and trending",
|
|
"Set up automated test maintenance"
|
|
]
|
|
},
|
|
"monitoring": {
|
|
"description": "Test monitoring and maintenance phase",
|
|
"activities": [
|
|
"Monitor test execution metrics and trends",
|
|
"Maintain test suites and remove flaky tests",
|
|
"Update tests for new features and changes",
|
|
"Analyze test coverage and identify gaps",
|
|
"Optimize test execution performance"
|
|
]
|
|
}
|
|
},
|
|
"testingLevels": {
|
|
"unit": {
|
|
"scope": "Individual functions, methods, and components in isolation",
|
|
"goals": "Fast feedback, high coverage, isolated testing",
|
|
"tools": "Jest, Vitest, Mocha, Jasmine",
|
|
"coverage": "80%+ for business logic",
|
|
"characteristics": "Fast (<1s), Isolated, Repeatable, Self-validating"
|
|
},
|
|
"integration": {
|
|
"scope": "Interaction between multiple components or services",
|
|
"goals": "Verify component integration and data flow",
|
|
"tools": "Supertest, TestContainers, Testing Library",
|
|
"coverage": "Critical integration points",
|
|
"characteristics": "Moderate speed, Real dependencies, Contract validation"
|
|
},
|
|
"contract": {
|
|
"scope": "API contracts between services",
|
|
"goals": "Ensure API compatibility and prevent breaking changes",
|
|
"tools": "Pact, OpenAPI validators, Postman",
|
|
"coverage": "All public APIs",
|
|
"characteristics": "Consumer-driven, Version compatibility, Schema validation"
|
|
},
|
|
"e2e": {
|
|
"scope": "Complete user workflows from start to finish",
|
|
"goals": "Validate critical user journeys work end-to-end",
|
|
"tools": "Playwright, Cypress, Selenium",
|
|
"coverage": "Critical business flows",
|
|
"characteristics": "Slow, Real browser, Full system testing"
|
|
},
|
|
"performance": {
|
|
"scope": "System performance under various load conditions",
|
|
"goals": "Ensure performance requirements are met",
|
|
"tools": "Artillery, K6, JMeter, Lighthouse",
|
|
"coverage": "Critical performance paths",
|
|
"characteristics": "Load testing, Stress testing, Performance monitoring"
|
|
}
|
|
},
|
|
"testingPatterns": {
|
|
"aaa": {
|
|
"name": "Arrange, Act, Assert",
|
|
"description": "Structure tests with clear setup, execution, and verification",
|
|
"example": "// Arrange\nconst user = createTestUser();\n// Act\nconst result = await service.createUser(user);\n// Assert\nexpect(result.id).toBeDefined();"
|
|
},
|
|
"given_when_then": {
|
|
"name": "Given, When, Then (BDD)",
|
|
"description": "Behavior-driven testing with clear preconditions, actions, and outcomes",
|
|
"example": "describe('User registration', () => {\n it('should create user when valid data provided', async () => {\n // Given\n const userData = validUserData();\n // When\n const user = await userService.register(userData);\n // Then\n expect(user).toMatchObject(userData);\n });\n});"
|
|
},
|
|
"test_doubles": {
|
|
"name": "Test Doubles (Mocks, Stubs, Spies)",
|
|
"description": "Use test doubles to isolate system under test",
|
|
"types": {
|
|
"mock": "Verify interactions with dependencies",
|
|
"stub": "Provide controlled responses",
|
|
"spy": "Monitor calls to real objects",
|
|
"fake": "Working implementation with shortcuts"
|
|
}
|
|
},
|
|
"data_driven": {
|
|
"name": "Data-Driven Testing",
|
|
"description": "Test same logic with multiple input datasets",
|
|
"example": "test.each([\n ['valid@email.com', true],\n ['invalid-email', false],\n ['', false]\n])('validates email %s as %s', (email, expected) => {\n expect(isValidEmail(email)).toBe(expected);\n});"
|
|
}
|
|
},
|
|
"qualityGates": {
|
|
"coverage": {
|
|
"unit_tests": "80% minimum for business logic",
|
|
"integration_tests": "All critical integration points covered",
|
|
"e2e_tests": "All critical user journeys covered",
|
|
"mutation_testing": "70% mutation score for critical components"
|
|
},
|
|
"performance": {
|
|
"test_execution": "Unit tests <5 minutes, Integration <15 minutes",
|
|
"feedback_time": "Developer feedback within 10 minutes",
|
|
"parallel_execution": "Tests run in parallel where possible",
|
|
"flaky_tests": "<1% flaky test rate"
|
|
},
|
|
"quality": {
|
|
"test_reliability": "95% pass rate on consecutive runs",
|
|
"test_maintainability": "Tests updated with code changes",
|
|
"test_readability": "Tests serve as documentation",
|
|
"test_isolation": "Tests don't depend on each other"
|
|
}
|
|
},
|
|
"codeTemplates": {
|
|
"unitTest": {
|
|
"framework": "Jest/Vitest",
|
|
"template": "import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';\nimport { UserService } from './UserService';\nimport { MockUserRepository } from './__mocks__/UserRepository';\n\ndescribe('UserService', () => {\n let userService: UserService;\n let mockRepository: MockUserRepository;\n\n beforeEach(() => {\n mockRepository = new MockUserRepository();\n userService = new UserService(mockRepository);\n });\n\n afterEach(() => {\n vi.clearAllMocks();\n });\n\n describe('createUser', () => {\n it('should create user with valid data', async () => {\n // Arrange\n const userData = {\n name: 'John Doe',\n email: 'john@example.com',\n password: 'securePassword123'\n };\n const expectedUser = { id: '123', ...userData };\n mockRepository.create.mockResolvedValue(expectedUser);\n\n // Act\n const result = await userService.createUser(userData);\n\n // Assert\n expect(result).toEqual(expectedUser);\n expect(mockRepository.create).toHaveBeenCalledWith(userData);\n expect(mockRepository.create).toHaveBeenCalledTimes(1);\n });\n\n it('should throw error when email already exists', async () => {\n // Arrange\n const userData = {\n name: 'John Doe',\n email: 'existing@example.com',\n password: 'securePassword123'\n };\n mockRepository.findByEmail.mockResolvedValue({ id: '456', email: userData.email });\n\n // Act & Assert\n await expect(userService.createUser(userData))\n .rejects\n .toThrow('User with email already exists');\n \n expect(mockRepository.findByEmail).toHaveBeenCalledWith(userData.email);\n expect(mockRepository.create).not.toHaveBeenCalled();\n });\n\n it('should handle repository errors gracefully', async () => {\n // Arrange\n const userData = {\n name: 'John Doe',\n email: 'john@example.com',\n password: 'securePassword123'\n };\n const dbError = new Error('Database connection failed');\n mockRepository.create.mockRejectedValue(dbError);\n\n // Act & Assert\n await expect(userService.createUser(userData))\n .rejects\n .toThrow('Failed to create user');\n });\n });\n\n describe('getUserById', () => {\n it('should return user when found', async () => {\n // Arrange\n const userId = '123';\n const expectedUser = { id: userId, name: 'John Doe', email: 'john@example.com' };\n mockRepository.findById.mockResolvedValue(expectedUser);\n\n // Act\n const result = await userService.getUserById(userId);\n\n // Assert\n expect(result).toEqual(expectedUser);\n expect(mockRepository.findById).toHaveBeenCalledWith(userId);\n });\n\n it('should return null when user not found', async () => {\n // Arrange\n const userId = '999';\n mockRepository.findById.mockResolvedValue(null);\n\n // Act\n const result = await userService.getUserById(userId);\n\n // Assert\n expect(result).toBeNull();\n expect(mockRepository.findById).toHaveBeenCalledWith(userId);\n });\n });\n});"
|
|
},
|
|
"integrationTest": {
|
|
"framework": "Supertest + Jest",
|
|
"template": "import request from 'supertest';\nimport { Test, TestingModule } from '@nestjs/testing';\nimport { INestApplication } from '@nestjs/common';\nimport { AppModule } from '../src/app.module';\nimport { DatabaseService } from '../src/database/database.service';\nimport { createTestUser, cleanupTestData } from './fixtures/user.fixtures';\n\ndescribe('Users API Integration Tests', () => {\n let app: INestApplication;\n let databaseService: DatabaseService;\n let testUserId: string;\n\n beforeAll(async () => {\n const moduleFixture: TestingModule = await Test.createTestingModule({\n imports: [AppModule],\n }).compile();\n\n app = moduleFixture.createNestApplication();\n databaseService = moduleFixture.get<DatabaseService>(DatabaseService);\n \n await app.init();\n \n // Set up test data\n const testUser = await createTestUser(databaseService);\n testUserId = testUser.id;\n });\n\n afterAll(async () => {\n // Clean up test data\n await cleanupTestData(databaseService);\n await app.close();\n });\n\n describe('POST /users', () => {\n it('should create a new user', async () => {\n const newUser = {\n name: 'Jane Doe',\n email: 'jane@example.com',\n password: 'securePassword123'\n };\n\n const response = await request(app.getHttpServer())\n .post('/users')\n .send(newUser)\n .expect(201);\n\n expect(response.body).toMatchObject({\n id: expect.any(String),\n name: newUser.name,\n email: newUser.email,\n createdAt: expect.any(String)\n });\n expect(response.body.password).toBeUndefined();\n\n // Verify user was actually created in database\n const createdUser = await databaseService.user.findUnique({\n where: { id: response.body.id }\n });\n expect(createdUser).toBeTruthy();\n expect(createdUser.name).toBe(newUser.name);\n });\n\n it('should return 400 for invalid data', async () => {\n const invalidUser = {\n name: '', // Invalid: empty name\n email: 'invalid-email', // Invalid: bad email format\n password: '123' // Invalid: too short\n };\n\n const response = await request(app.getHttpServer())\n .post('/users')\n .send(invalidUser)\n .expect(400);\n\n expect(response.body.errors).toBeDefined();\n expect(response.body.errors).toContain(\n expect.objectContaining({\n field: 'email',\n message: expect.stringContaining('valid email')\n })\n );\n });\n\n it('should return 409 for duplicate email', async () => {\n const duplicateUser = {\n name: 'John Duplicate',\n email: 'existing@example.com', // Email already exists\n password: 'securePassword123'\n };\n\n await request(app.getHttpServer())\n .post('/users')\n .send(duplicateUser)\n .expect(409);\n });\n });\n\n describe('GET /users/:id', () => {\n it('should return user when found', async () => {\n const response = await request(app.getHttpServer())\n .get(`/users/${testUserId}`)\n .expect(200);\n\n expect(response.body).toMatchObject({\n id: testUserId,\n name: expect.any(String),\n email: expect.any(String),\n createdAt: expect.any(String)\n });\n expect(response.body.password).toBeUndefined();\n });\n\n it('should return 404 for non-existent user', async () => {\n const nonExistentId = '999999';\n \n await request(app.getHttpServer())\n .get(`/users/${nonExistentId}`)\n .expect(404);\n });\n\n it('should return 400 for invalid user id format', async () => {\n await request(app.getHttpServer())\n .get('/users/invalid-id')\n .expect(400);\n });\n });\n\n describe('PUT /users/:id', () => {\n it('should update user successfully', async () => {\n const updateData = {\n name: 'Updated Name',\n email: 'updated@example.com'\n };\n\n const response = await request(app.getHttpServer())\n .put(`/users/${testUserId}`)\n .send(updateData)\n .expect(200);\n\n expect(response.body).toMatchObject({\n id: testUserId,\n name: updateData.name,\n email: updateData.email,\n updatedAt: expect.any(String)\n });\n\n // Verify update in database\n const updatedUser = await databaseService.user.findUnique({\n where: { id: testUserId }\n });\n expect(updatedUser.name).toBe(updateData.name);\n expect(updatedUser.email).toBe(updateData.email);\n });\n });\n});"
|
|
},
|
|
"e2eTest": {
|
|
"framework": "Playwright",
|
|
"template": "import { test, expect } from '@playwright/test';\nimport { LoginPage } from '../pages/LoginPage';\nimport { DashboardPage } from '../pages/DashboardPage';\nimport { UserProfilePage } from '../pages/UserProfilePage';\n\ntest.describe('User Management E2E Tests', () => {\n let loginPage: LoginPage;\n let dashboardPage: DashboardPage;\n let userProfilePage: UserProfilePage;\n\n test.beforeEach(async ({ page }) => {\n loginPage = new LoginPage(page);\n dashboardPage = new DashboardPage(page);\n userProfilePage = new UserProfilePage(page);\n \n // Navigate to application\n await page.goto('/login');\n });\n\n test('complete user registration and profile update flow', async ({ page }) => {\n // Step 1: Register new user\n await loginPage.clickSignUpLink();\n \n const newUser = {\n name: 'Test User',\n email: `test${Date.now()}@example.com`,\n password: 'SecurePassword123!'\n };\n \n await loginPage.fillRegistrationForm(newUser);\n await loginPage.submitRegistration();\n \n // Verify registration success\n await expect(page.locator('[data-testid=\"registration-success\"]'))\n .toBeVisible();\n \n // Step 2: Login with new user\n await loginPage.login(newUser.email, newUser.password);\n \n // Verify successful login and dashboard access\n await expect(dashboardPage.welcomeMessage)\n .toContainText(`Welcome, ${newUser.name}`);\n \n // Step 3: Navigate to profile settings\n await dashboardPage.clickProfileMenu();\n await dashboardPage.clickProfileSettings();\n \n // Verify profile page loaded\n await expect(userProfilePage.profileForm).toBeVisible();\n \n // Step 4: Update profile information\n const updatedInfo = {\n name: 'Updated Test User',\n bio: 'This is my updated bio',\n phone: '+1234567890'\n };\n \n await userProfilePage.updateProfile(updatedInfo);\n await userProfilePage.saveChanges();\n \n // Verify update success\n await expect(userProfilePage.successMessage)\n .toContainText('Profile updated successfully');\n \n // Step 5: Verify changes persist after page reload\n await page.reload();\n \n await expect(userProfilePage.nameField)\n .toHaveValue(updatedInfo.name);\n await expect(userProfilePage.bioField)\n .toHaveValue(updatedInfo.bio);\n await expect(userProfilePage.phoneField)\n .toHaveValue(updatedInfo.phone);\n \n // Step 6: Test profile picture upload\n await userProfilePage.uploadProfilePicture('./fixtures/test-avatar.jpg');\n \n // Verify image upload\n await expect(userProfilePage.profileImage)\n .toBeVisible();\n \n // Step 7: Test account security settings\n await userProfilePage.clickSecurityTab();\n \n // Change password\n await userProfilePage.changePassword(\n newUser.password,\n 'NewSecurePassword123!'\n );\n \n await expect(userProfilePage.successMessage)\n .toContainText('Password changed successfully');\n \n // Step 8: Test logout and login with new password\n await dashboardPage.logout();\n \n await loginPage.login(newUser.email, 'NewSecurePassword123!');\n \n // Verify successful login with new password\n await expect(dashboardPage.welcomeMessage)\n .toContainText(`Welcome, ${updatedInfo.name}`);\n });\n\n test('should handle profile update errors gracefully', async ({ page }) => {\n // Login as existing user\n await loginPage.login('existing@example.com', 'password123');\n \n // Navigate to profile\n await dashboardPage.navigateToProfile();\n \n // Try to update with invalid data\n await userProfilePage.fillName(''); // Empty name should fail\n await userProfilePage.fillEmail('invalid-email'); // Invalid email\n await userProfilePage.saveChanges();\n \n // Verify error messages\n await expect(userProfilePage.nameError)\n .toContainText('Name is required');\n await expect(userProfilePage.emailError)\n .toContainText('Please enter a valid email');\n \n // Verify form wasn't submitted\n await expect(userProfilePage.successMessage)\n .not.toBeVisible();\n });\n\n test('should be accessible with keyboard navigation', async ({ page }) => {\n await loginPage.login('existing@example.com', 'password123');\n await dashboardPage.navigateToProfile();\n \n // Test keyboard navigation through form\n await page.keyboard.press('Tab'); // Focus name field\n await expect(userProfilePage.nameField).toBeFocused();\n \n await page.keyboard.press('Tab'); // Focus email field\n await expect(userProfilePage.emailField).toBeFocused();\n \n await page.keyboard.press('Tab'); // Focus bio field\n await expect(userProfilePage.bioField).toBeFocused();\n \n // Test form submission with keyboard\n await userProfilePage.fillName('Keyboard User');\n await page.keyboard.press('Enter'); // Should submit form\n \n await expect(userProfilePage.successMessage)\n .toContainText('Profile updated successfully');\n });\n});"
|
|
}
|
|
},
|
|
"testDataManagement": {
|
|
"fixtures": {
|
|
"description": "Pre-defined test data for consistent testing",
|
|
"patterns": "Factory pattern, Builder pattern, Object mothers",
|
|
"storage": "JSON files, Database seeds, In-memory objects"
|
|
},
|
|
"factories": {
|
|
"description": "Dynamic test data generation",
|
|
"tools": "Faker.js, Factory Bot, Factory Girl",
|
|
"benefits": "Unique data, Customizable, Realistic"
|
|
},
|
|
"mocking": {
|
|
"description": "Fake implementations for external dependencies",
|
|
"types": "API mocks, Database mocks, Service mocks",
|
|
"tools": "MSW, Nock, Sinon, Jest mocks"
|
|
},
|
|
"cleanup": {
|
|
"description": "Clean up test data after test execution",
|
|
"strategies": "Database transactions, Cleanup hooks, Isolated test databases",
|
|
"importance": "Prevent test interference, Maintain test isolation"
|
|
}
|
|
},
|
|
"bestPractices": [
|
|
"Write tests first (TDD) or alongside implementation",
|
|
"Keep tests independent and isolated from each other",
|
|
"Use descriptive test names that explain the scenario",
|
|
"Follow the AAA pattern (Arrange, Act, Assert)",
|
|
"Mock external dependencies to ensure test isolation",
|
|
"Write both positive and negative test cases",
|
|
"Keep tests simple and focused on one thing",
|
|
"Use test data factories for consistent test data",
|
|
"Clean up test data after test execution",
|
|
"Run tests frequently during development",
|
|
"Maintain tests as first-class code with proper refactoring",
|
|
"Use code coverage as a guide, not a target"
|
|
],
|
|
"antiPatterns": [
|
|
"Tests that depend on other tests or test order",
|
|
"Tests that test implementation details instead of behavior",
|
|
"Over-mocking that makes tests brittle",
|
|
"Tests with unclear or generic names",
|
|
"Tests that are too complex or test multiple things",
|
|
"Ignoring or commenting out failing tests",
|
|
"Tests that duplicate production logic",
|
|
"Hard-coded test data that becomes outdated",
|
|
"Tests that require manual setup or intervention",
|
|
"Flaky tests that pass/fail randomly",
|
|
"Tests that take too long to run",
|
|
"Tests without proper assertions"
|
|
]
|
|
} |