Adding a New Testing Preset
Comprehensive guide to adding new testing frameworks and configurations (e.g., Cypress, Playwright, Testing Library variants, custom Jest configurations) to React Kickstart's testing options.
Current Options: React Kickstart currently supports Vitest and Jest. This guide shows how to add additional testing frameworks and presets.
๐ฏ Integration Overview
๐ Complete Integration Guide
Declare Testing Dependencies
File: src/builders/dependencies.js
Add your testing framework dependencies:
// Cypress E2E testing dependencies
export const testing = {
// ...existing testing dependencies
cypress: "^13.6.0",
cypressRealEvents: "^1.11.0",
cypressAxeCore: "^4.8.0" // Accessibility testing
}
export function getCypressDependencies(includePlugins = true) {
const deps = {
cypress: testing.cypress
}
if (includePlugins) {
deps["cypress-real-events"] = testing.cypressRealEvents
deps["cypress-axe"] = testing.cypressAxeCore
}
return deps
}
Create Testing Setup Module
File: src/features/testing/your-testing-setup.js
Create comprehensive setup modules:
// src/features/testing/cypress-setup.js
import { CORE_UTILS } from '../../utils/index.js'
import path from 'path'
export class CypressSetup {
async setup(projectPath, userChoices) {
await this.createCypressConfig(projectPath, userChoices)
await this.createCypressSupport(projectPath, userChoices)
await this.createExampleTests(projectPath, userChoices)
await this.createCommands(projectPath, userChoices)
await this.updatePackageScripts(projectPath, userChoices)
}
async createCypressConfig(projectPath, userChoices) {
const { framework, typescript } = userChoices
const configContent = `
import { defineConfig } from 'cypress'
export default defineConfig({
e2e: {
baseUrl: 'http://localhost:${framework === 'vite' ? '5173' : '3000'}',
viewportWidth: 1280,
viewportHeight: 720,
video: false,
screenshotOnRunFailure: true,
setupNodeEvents(on, config) {
// implement node event listeners here
},
specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
supportFile: 'cypress/support/e2e.${typescript ? 'ts' : 'js'}'
},
component: {
devServer: {
framework: '${framework}',
bundler: '${framework === 'vite' ? 'vite' : 'webpack'}',
},
specPattern: 'src/**/*.cy.{js,jsx,ts,tsx}',
supportFile: 'cypress/support/component.${typescript ? 'ts' : 'js'}'
}
})
`
const configPath = path.join(projectPath, `cypress.config.${typescript ? 'ts' : 'js'}`)
await CORE_UTILS.writeFile(configPath, configContent.trim())
}
async createCypressSupport(projectPath, userChoices) {
const { typescript } = userChoices
// E2E Support
const e2eSupportContent = `
import './commands'
import 'cypress-axe'
// Hide fetch/XHR requests from command log
Cypress.on('window:before:load', (win) => {
cy.stub(win.console, 'error').callsFake((message) => {
// Filter out specific errors you want to ignore
if (!message.includes('ResizeObserver loop limit exceeded')) {
Cypress.log({
name: 'console.error',
message: message
})
}
})
})
// Global before hook
beforeEach(() => {
// Inject axe-core for accessibility testing
cy.injectAxe()
})
`
const e2eSupportPath = path.join(projectPath, 'cypress', 'support', `e2e.${typescript ? 'ts' : 'js'}`)
await CORE_UTILS.ensureDirectory(path.dirname(e2eSupportPath))
await CORE_UTILS.writeFile(e2eSupportPath, e2eSupportContent.trim())
// Component Support
const componentSupportContent = `
import './commands'
import { mount } from 'cypress/react18'
// Augment the Cypress namespace to include type definitions for custom commands
declare global {
namespace Cypress {
interface Chainable {
mount: typeof mount
}
}
}
Cypress.Commands.add('mount', mount)
`
const componentSupportPath = path.join(projectPath, 'cypress', 'support', `component.${typescript ? 'ts' : 'js'}`)
await CORE_UTILS.writeFile(componentSupportPath, componentSupportContent.trim())
}
async createCommands(projectPath, userChoices) {
const { typescript } = userChoices
const commandsContent = `
// Custom Cypress commands
// Login command
Cypress.Commands.add('login', (email = 'test@example.com', password = 'password') => {
cy.visit('/login')
cy.get('[data-testid="email-input"]').type(email)
cy.get('[data-testid="password-input"]').type(password)
cy.get('[data-testid="login-button"]').click()
cy.url().should('not.include', '/login')
})
// Custom assertion for accessibility
Cypress.Commands.add('checkA11y', (context = null, options = {}) => {
cy.checkA11y(context, {
includedImpacts: ['critical', 'serious'],
...options
})
})
// Wait for React to be ready
Cypress.Commands.add('waitForReact', () => {
cy.window().should('have.property', 'React')
})
// Custom command for API mocking
Cypress.Commands.add('mockApi', (method, url, response, statusCode = 200) => {
cy.intercept(method, url, {
statusCode,
body: response
}).as('apiCall')
})
${typescript ? `
// TypeScript declarations
declare global {
namespace Cypress {
interface Chainable {
login(email?: string, password?: string): Chainable<void>
checkA11y(context?: any, options?: any): Chainable<void>
waitForReact(): Chainable<void>
mockApi(method: string, url: string, response: any, statusCode?: number): Chainable<void>
}
}
}
` : ''}
`
const commandsPath = path.join(projectPath, 'cypress', 'support', `commands.${typescript ? 'ts' : 'js'}`)
await CORE_UTILS.writeFile(commandsPath, commandsContent.trim())
}
async createExampleTests(projectPath, userChoices) {
const { typescript } = userChoices
// E2E Test
const e2eTestContent = `
describe('App E2E Tests', () => {
beforeEach(() => {
cy.visit('/')
})
it('should display the welcome message', () => {
cy.contains('Welcome to React Kickstart!')
cy.get('h1').should('be.visible')
})
it('should be accessible', () => {
cy.checkA11y()
})
it('should navigate correctly', () => {
// Add navigation tests based on your app structure
cy.get('[data-testid="nav-link"]').first().click()
cy.url().should('include', '/')
})
it('should handle responsive design', () => {
// Test mobile viewport
cy.viewport('iphone-x')
cy.get('h1').should('be.visible')
// Test desktop viewport
cy.viewport(1280, 720)
cy.get('h1').should('be.visible')
})
})
`
const e2eTestPath = path.join(projectPath, 'cypress', 'e2e', `app.cy.${typescript ? 'ts' : 'js'}`)
await CORE_UTILS.ensureDirectory(path.dirname(e2eTestPath))
await CORE_UTILS.writeFile(e2eTestPath, e2eTestContent.trim())
// Component Test
const componentTestContent = `
import React from 'react'
import App from '../../src/App'
describe('App Component Tests', () => {
it('should render the app component', () => {
cy.mount(<App />)
cy.contains('Welcome to React Kickstart!')
})
it('should handle user interactions', () => {
cy.mount(<App />)
// Add interaction tests based on your components
})
})
`
const componentTestPath = path.join(projectPath, 'src', 'components', `App.cy.${typescript ? 'tsx' : 'jsx'}`)
await CORE_UTILS.ensureDirectory(path.dirname(componentTestPath))
await CORE_UTILS.writeFile(componentTestPath, componentTestContent.trim())
}
async updatePackageScripts(projectPath, userChoices) {
const packageJsonPath = path.join(projectPath, 'package.json')
const packageJson = JSON.parse(await CORE_UTILS.readFile(packageJsonPath))
packageJson.scripts = {
...packageJson.scripts,
'cypress:open': 'cypress open',
'cypress:run': 'cypress run',
'cypress:run:headless': 'cypress run --headless',
'test:e2e': 'cypress run',
'test:e2e:dev': 'cypress open'
}
await CORE_UTILS.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2))
}
}
Update Prompt System
File: src/prompts/steps/testing-step.js
Add your testing options:
getChoices(previousAnswers) {
const { framework } = previousAnswers
return [
{
name: 'Vitest (Fast, modern testing framework)',
value: 'vitest',
short: 'Vitest'
},
{
name: 'Jest (Traditional, mature testing framework)',
value: 'jest',
short: 'Jest'
},
{
name: 'Cypress (E2E and component testing)',
value: 'cypress',
short: 'Cypress'
},
{
name: 'Playwright (Modern E2E testing)',
value: 'playwright',
short: 'Playwright'
},
{
name: 'Enhanced RTL (React Testing Library with MSW)',
value: 'enhanced-rtl',
short: 'Enhanced RTL'
},
{
name: 'None (No testing setup)',
value: 'none',
short: 'None'
}
].map(choice => {
// Add framework-specific recommendations
if (choice.value === 'vitest' && framework === 'vite') {
choice.name += ' (Recommended for Vite)'
}
if (choice.value === 'jest' && framework === 'nextjs') {
choice.name += ' (Recommended for Next.js)'
}
return choice
})
}
Integrate with Feature System
File: src/features/testing/index.js
Update the main testing integration:
import { VitestSetup } from "./vitest-setup.js";
import { JestSetup } from "./jest-setup.js";
import { CypressSetup } from "./cypress-setup.js";
import { PlaywrightSetup } from "./playwright-setup.js";
import { EnhancedRTLSetup } from "./enhanced-rtl-setup.js";
export async function setupTesting(projectPath, userChoices) {
const { testing } = userChoices;
switch (testing) {
case "vitest":
const vitestSetup = new VitestSetup();
await vitestSetup.setup(projectPath, userChoices);
break;
case "jest":
const jestSetup = new JestSetup();
await jestSetup.setup(projectPath, userChoices);
break;
case "cypress":
const cypressSetup = new CypressSetup();
await cypressSetup.setup(projectPath, userChoices);
break;
case "playwright":
const playwrightSetup = new PlaywrightSetup();
await playwrightSetup.setup(projectPath, userChoices);
break;
case "enhanced-rtl":
const enhancedRTLSetup = new EnhancedRTLSetup();
await enhancedRTLSetup.setup(projectPath, userChoices);
break;
case "none":
default:
// No testing setup
break;
}
}
Add to QA System
File: qa-automation/test-matrix-generator.js
Add testing options to the test matrix:
const CONFIG_OPTIONS = {
framework: ["vite", "nextjs"],
typescript: [true, false],
testing: ["vitest", "jest", "cypress", "playwright", "enhanced-rtl", "none"],
// ... other options
};
// Add testing-specific validation
const TESTING_VALIDATION = {
cypress: {
requiredFiles: ["cypress.config.ts", "cypress/support/e2e.ts"],
requiredDependencies: ["cypress"],
},
playwright: {
requiredFiles: ["playwright.config.ts", "tests/app.spec.ts"],
requiredDependencies: ["@playwright/test"],
},
"enhanced-rtl": {
requiredFiles: ["src/test/setup.ts", "src/test/utils.tsx"],
requiredDependencies: ["@testing-library/react", "msw"],
},
};
๐งช Testing Your Integration
Manual Testing
Test your testing preset:
# Test Cypress integration
node bin/react-kickstart.js test-cypress --yes --framework vite --testing cypress --typescript
# Test Playwright integration
node bin/react-kickstart.js test-playwright --yes --framework nextjs --testing playwright
# Test Enhanced RTL
node bin/react-kickstart.js test-rtl --yes --framework vite --testing enhanced-rtl --state redux
Verify Testing Works
cd test-cypress
npm install
npm run test # Should run tests successfully
npm run cypress:open # Should open Cypress
๐ Best Practices
Testing Strategy: Provide comprehensive testing setups that cover unit, integration, and E2E testing needs.
- Test Organization - Structure tests logically with clear naming conventions
- Utilities - Provide helpful testing utilities and custom matchers
- Mocking - Include proper mocking strategies for APIs and external dependencies
- Accessibility - Integrate accessibility testing where appropriate
- Performance - Consider test execution performance and parallelization
๐ Next Steps
Related Guides:
- Adding Features โ - General feature addition guide
- Architecture Overview โ - Understanding the system design
- Contributing โ - Contribution guidelines and workflow
Testing Ecosystem: Your testing integration expands React Kickstart's quality assurance capabilities and helps developers build more reliable applications.