Establish a multi-layered testing strategy to ensure pixel-perfect replication of Spotify Portal with enterprise-grade reliability, performance, and security.
┌─────────────────┐
│ E2E Tests │
│ (10-15%) │
┌───┴─────────────────┴───┐
│ Integration Tests │
│ (20-25%) │
┌───┴─────────────────────────┴───┐
│ Unit Tests │
│ (60-70%) │
┌───┴─────────────────────────────────┴───┐
│ Static Analysis │
│ (Linting, Security, Types) │
└─────────────────────────────────────────┘
interface TestingFramework {
// Static analysis
staticAnalysis: StaticAnalysisConfig
// Unit testing
unitTesting: UnitTestConfig
// Integration testing
integrationTesting: IntegrationTestConfig
// End-to-end testing
e2eTesting: E2ETestConfig
// Performance testing
performanceTesting: PerformanceTestConfig
// Security testing
securityTesting: SecurityTestConfig
// Visual regression testing
visualTesting: VisualTestConfig
// Accessibility testing
accessibilityTesting: AccessibilityTestConfig
// Multi-tenant testing
multiTenantTesting: MultiTenantTestConfig
}// jest.config.js
export default {
preset: 'ts-jest',
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/src/test/setup.ts'],
moduleNameMapping: {
'^@/(.*)$': '<rootDir>/src/$1',
'\\.(css|less|scss|sass)$': 'identity-obj-proxy',
},
collectCoverageFrom: [
'src/**/*.{ts,tsx}',
'!src/**/*.d.ts',
'!src/**/*.stories.{ts,tsx}',
'!src/test/**/*',
],
coverageThreshold: {
global: {
branches: 90,
functions: 90,
lines: 90,
statements: 90,
},
},
testMatch: [
'<rootDir>/src/**/__tests__/**/*.{ts,tsx}',
'<rootDir>/src/**/*.{test,spec}.{ts,tsx}',
],
transform: {
'^.+\\.(ts|tsx)$': ['ts-jest', {
tsconfig: 'tsconfig.test.json',
}],
},
}// Example: PluginMarketplace.test.tsx
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { rest } from 'msw'
import { setupServer } from 'msw/node'
import PluginMarketplace from '@/components/plugins/PluginMarketplace'
import { mockPlugins } from '@/test/fixtures/plugins'
// Mock server setup
const server = setupServer(
rest.get('/api/plugins', (req, res, ctx) => {
return res(ctx.json({ plugins: mockPlugins }))
})
)
describe('PluginMarketplace', () => {
let queryClient: QueryClient
beforeEach(() => {
queryClient = new QueryClient({
defaultOptions: {
queries: { retry: false },
mutations: { retry: false },
},
})
})
const renderWithProviders = (ui: React.ReactElement) => {
return render(
<QueryClientProvider client={queryClient}>
{ui}
</QueryClientProvider>
)
}
it('displays plugin marketplace header', () => {
renderWithProviders(<PluginMarketplace />)
expect(screen.getByText('Plugin Marketplace')).toBeInTheDocument()
})
it('loads and displays plugins', async () => {
renderWithProviders(<PluginMarketplace />)
await waitFor(() => {
expect(screen.getByText('Test Plugin')).toBeInTheDocument()
})
})
it('filters plugins by category', async () => {
renderWithProviders(<PluginMarketplace />)
const cicdButton = screen.getByText('CI/CD')
fireEvent.click(cicdButton)
await waitFor(() => {
expect(screen.queryByText('Test Analytics Plugin')).not.toBeInTheDocument()
})
})
it('installs plugin successfully', async () => {
const mockOnPluginInstalled = jest.fn()
renderWithProviders(
<PluginMarketplace onPluginInstalled={mockOnPluginInstalled} />
)
const installButton = await screen.findByText('Install')
fireEvent.click(installButton)
await waitFor(() => {
expect(mockOnPluginInstalled).toHaveBeenCalled()
})
})
})// Example: TenantService.test.ts
import { TenantService } from '@/services/tenant/TenantService'
import { DatabaseTestHelper } from '@/test/helpers/DatabaseTestHelper'
import { mockTenant } from '@/test/fixtures/tenant'
describe('TenantService', () => {
let tenantService: TenantService
let dbHelper: DatabaseTestHelper
beforeAll(async () => {
dbHelper = new DatabaseTestHelper()
await dbHelper.setup()
tenantService = new TenantService(dbHelper.getConnection())
})
afterAll(async () => {
await dbHelper.teardown()
})
beforeEach(async () => {
await dbHelper.clearTables(['tenants', 'tenant_configurations'])
})
describe('createTenant', () => {
it('creates tenant with valid configuration', async () => {
const tenantRequest = {
name: 'Test Company',
slug: 'test-company',
plan: 'professional'
}
const tenant = await tenantService.createTenant(tenantRequest)
expect(tenant.id).toBeDefined()
expect(tenant.name).toBe(tenantRequest.name)
expect(tenant.status).toBe('active')
})
it('throws error for duplicate slug', async () => {
await tenantService.createTenant({
name: 'Test Company 1',
slug: 'test-company',
plan: 'starter'
})
await expect(
tenantService.createTenant({
name: 'Test Company 2',
slug: 'test-company',
plan: 'starter'
})
).rejects.toThrow('Tenant slug already exists')
})
})
describe('provisionTenant', () => {
it('provisions database schema for tenant', async () => {
const tenant = await tenantService.createTenant(mockTenant)
const result = await tenantService.provisionTenant(tenant.id)
expect(result.success).toBe(true)
expect(result.databaseSchema).toBe(`tenant_${tenant.id}`)
})
})
})// tests/integration/api/plugins.test.ts
import { NextRequest } from 'next/server'
import { testApiHandler } from 'next-test-api-route-handler'
import pluginsHandler from '@/app/api/plugins/route'
import { createMockTenantContext } from '@/test/helpers/tenantHelper'
describe('/api/plugins', () => {
beforeEach(() => {
// Reset database state
// Setup test tenant
})
it('GET /api/plugins returns plugin list', async () => {
await testApiHandler({
appHandler: pluginsHandler,
test: async ({ fetch }) => {
const res = await fetch({
method: 'GET',
headers: {
'x-tenant-id': 'test-tenant',
'authorization': 'Bearer test-token'
}
})
expect(res.status).toBe(200)
const data = await res.json()
expect(data.plugins).toBeInstanceOf(Array)
expect(data.plugins.length).toBeGreaterThan(0)
}
})
})
it('POST /api/plugins installs plugin', async () => {
await testApiHandler({
appHandler: pluginsHandler,
test: async ({ fetch }) => {
const res = await fetch({
method: 'POST',
headers: {
'content-type': 'application/json',
'x-tenant-id': 'test-tenant',
'authorization': 'Bearer test-token'
},
body: JSON.stringify({
action: 'install',
pluginId: 'backstage-plugin-soundcheck'
})
})
expect(res.status).toBe(200)
const data = await res.json()
expect(data.success).toBe(true)
expect(data.pluginId).toBe('backstage-plugin-soundcheck')
}
})
})
})// tests/integration/database/tenantIsolation.test.ts
import { DatabaseConnection } from '@/lib/database/connection'
import { TenantManager } from '@/services/tenant/TenantManager'
describe('Tenant Data Isolation', () => {
let db: DatabaseConnection
let tenantManager: TenantManager
beforeAll(async () => {
db = await DatabaseConnection.create('test')
tenantManager = new TenantManager(db)
})
afterAll(async () => {
await db.close()
})
it('ensures tenant data isolation', async () => {
// Create two tenants
const tenant1 = await tenantManager.createTenant({
name: 'Tenant 1',
slug: 'tenant-1',
plan: 'starter'
})
const tenant2 = await tenantManager.createTenant({
name: 'Tenant 2',
slug: 'tenant-2',
plan: 'professional'
})
// Add data to each tenant
await db.setTenantContext(tenant1.id)
await db.query('INSERT INTO entities (name, kind) VALUES ($1, $2)', ['Service A', 'Component'])
await db.setTenantContext(tenant2.id)
await db.query('INSERT INTO entities (name, kind) VALUES ($1, $2)', ['Service B', 'Component'])
// Verify isolation
await db.setTenantContext(tenant1.id)
const tenant1Entities = await db.query('SELECT * FROM entities')
expect(tenant1Entities.rows).toHaveLength(1)
expect(tenant1Entities.rows[0].name).toBe('Service A')
await db.setTenantContext(tenant2.id)
const tenant2Entities = await db.query('SELECT * FROM entities')
expect(tenant2Entities.rows).toHaveLength(1)
expect(tenant2Entities.rows[0].name).toBe('Service B')
})
})// playwright.config.ts
import { defineConfig, devices } from '@playwright/test'
export default defineConfig({
testDir: './tests/e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [
['html'],
['junit', { outputFile: 'test-results/junit.xml' }],
['github']
],
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
{
name: 'Mobile Chrome',
use: { ...devices['Pixel 5'] },
},
{
name: 'Mobile Safari',
use: { ...devices['iPhone 12'] },
},
],
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
})// tests/e2e/plugin-marketplace.spec.ts
import { test, expect } from '@playwright/test'
test.describe('Plugin Marketplace', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/plugins')
})
test('displays plugin marketplace', async ({ page }) => {
await expect(page.getByRole('heading', { name: 'Plugin Marketplace' })).toBeVisible()
await expect(page.getByText('Browse and install Backstage plugins')).toBeVisible()
})
test('searches for plugins', async ({ page }) => {
await page.getByPlaceholder('Search plugins by name').fill('soundcheck')
await page.getByPlaceholder('Search plugins by name').press('Enter')
await expect(page.getByText('Soundcheck')).toBeVisible()
})
test('installs plugin', async ({ page }) => {
// Find and click install button for first plugin
const installButton = page.getByRole('button', { name: 'Install' }).first()
await installButton.click()
// Wait for installation to complete
await expect(page.getByText('Plugin installed successfully')).toBeVisible()
// Verify plugin appears in installed section
await expect(page.getByText('Enabled')).toBeVisible()
})
test('configures plugin', async ({ page }) => {
// Click configure button
await page.getByRole('button', { name: 'Configure' }).first().click()
// Fill configuration form
await page.getByLabel('API Key').fill('test-api-key')
await page.getByLabel('Enable notifications').check()
// Save configuration
await page.getByRole('button', { name: 'Save Configuration' }).click()
await expect(page.getByText('Configuration saved successfully')).toBeVisible()
})
})// tests/e2e/user-journey.spec.ts
import { test, expect } from '@playwright/test'
test.describe('Complete User Journey', () => {
test('new user onboarding flow', async ({ page }) => {
// Start from signup
await page.goto('/signup')
// Fill signup form
await page.getByLabel('Organization Name').fill('Test Company')
await page.getByLabel('Email').fill('admin@testcompany.com')
await page.getByRole('button', { name: 'Start Free Trial' }).click()
// Complete GitHub integration
await expect(page.getByText('Connect your GitHub organization')).toBeVisible()
await page.getByRole('button', { name: 'Connect GitHub' }).click()
// Mock GitHub OAuth (in real test, would use GitHub's test tools)
await page.goto('/auth/github/callback?code=test-code&state=test-state')
// Complete catalog import
await expect(page.getByText('Import your services')).toBeVisible()
await page.getByRole('button', { name: 'Import Services' }).click()
// Wait for import to complete
await expect(page.getByText('Services imported successfully')).toBeVisible()
// Navigate to dashboard
await page.getByRole('link', { name: 'Dashboard' }).click()
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible()
// Verify service catalog
await page.getByRole('link', { name: 'Service Catalog' }).click()
await expect(page.getByText('Total Entities')).toBeVisible()
})
})// tests/performance/load-test.js
import http from 'k6/http'
import { check, sleep } from 'k6'
import { Rate } from 'k6/metrics'
export let errorRate = new Rate('errors')
export let options = {
stages: [
{ duration: '2m', target: 100 }, // Ramp up to 100 users
{ duration: '5m', target: 100 }, // Stay at 100 users
{ duration: '2m', target: 200 }, // Ramp up to 200 users
{ duration: '5m', target: 200 }, // Stay at 200 users
{ duration: '2m', target: 0 }, // Ramp down
],
thresholds: {
http_req_duration: ['p(99)<1500'], // 99% of requests under 1.5s
http_req_failed: ['rate<0.1'], // Error rate under 10%
errors: ['rate<0.1'], // Error rate under 10%
},
}
export default function () {
// Test plugin marketplace endpoint
let response = http.get('http://localhost:3000/api/plugins', {
headers: {
'x-tenant-id': 'load-test-tenant',
'authorization': 'Bearer load-test-token'
}
})
check(response, {
'status is 200': (r) => r.status === 200,
'response time < 500ms': (r) => r.timings.duration < 500,
'has plugins array': (r) => JSON.parse(r.body).plugins !== undefined,
}) || errorRate.add(1)
sleep(1)
}// tests/performance/metrics.test.ts
import { performance } from 'perf_hooks'
import { expect } from '@jest/globals'
describe('Performance Metrics', () => {
it('measures plugin installation time', async () => {
const start = performance.now()
// Simulate plugin installation
const result = await installPlugin('test-plugin')
const end = performance.now()
const duration = end - start
expect(duration).toBeLessThan(5000) // Should complete within 5 seconds
expect(result.success).toBe(true)
})
it('measures catalog loading performance', async () => {
const start = performance.now()
const entities = await loadCatalogEntities({ limit: 100 })
const end = performance.now()
const duration = end - start
expect(duration).toBeLessThan(1000) // Should load within 1 second
expect(entities.length).toBe(100)
})
})// .storybook/main.ts
export default {
stories: ['../src/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
addons: [
'@storybook/addon-essentials',
'@storybook/addon-a11y',
'@chromatic-com/storybook'
],
framework: {
name: '@storybook/nextjs',
options: {},
},
}// tests/visual/visual-regression.spec.ts
import { test, expect } from '@playwright/test'
test.describe('Visual Regression Tests', () => {
test('plugin marketplace layout', async ({ page }) => {
await page.goto('/plugins')
await page.waitForLoadState('networkidle')
await expect(page).toHaveScreenshot('plugin-marketplace.png')
})
test('catalog page layout', async ({ page }) => {
await page.goto('/catalog')
await page.waitForLoadState('networkidle')
await expect(page).toHaveScreenshot('catalog-page.png')
})
test('dark mode consistency', async ({ page }) => {
await page.goto('/plugins')
// Switch to dark mode
await page.getByRole('button', { name: 'Toggle dark mode' }).click()
await page.waitForTimeout(500)
await expect(page).toHaveScreenshot('plugin-marketplace-dark.png')
})
})// tests/security/security.test.ts
import { test, expect } from '@playwright/test'
describe('Security Tests', () => {
test('prevents XSS attacks', async ({ page }) => {
const maliciousScript = '<script>alert("XSS")</script>'
await page.goto('/plugins')
await page.getByPlaceholder('Search plugins').fill(maliciousScript)
await page.getByPlaceholder('Search plugins').press('Enter')
// Verify script is not executed
const alerts = []
page.on('dialog', dialog => {
alerts.push(dialog.message())
dialog.dismiss()
})
await page.waitForTimeout(1000)
expect(alerts).toHaveLength(0)
})
test('enforces CSRF protection', async ({ request }) => {
// Attempt to make request without CSRF token
const response = await request.post('/api/plugins', {
data: {
action: 'install',
pluginId: 'test-plugin'
}
})
expect(response.status()).toBe(403)
})
test('validates tenant isolation', async ({ request }) => {
// Try to access another tenant's data
const response = await request.get('/api/plugins', {
headers: {
'x-tenant-id': 'unauthorized-tenant',
'authorization': 'Bearer valid-token-for-different-tenant'
}
})
expect(response.status()).toBe(403)
})
})// tests/accessibility/a11y.test.ts
import { test, expect } from '@playwright/test'
import AxeBuilder from '@axe-core/playwright'
test.describe('Accessibility Tests', () => {
test('plugin marketplace is accessible', async ({ page }) => {
await page.goto('/plugins')
const accessibilityScanResults = await new AxeBuilder({ page }).analyze()
expect(accessibilityScanResults.violations).toEqual([])
})
test('keyboard navigation works', async ({ page }) => {
await page.goto('/plugins')
// Tab through interactive elements
await page.keyboard.press('Tab')
await expect(page.getByRole('searchbox')).toBeFocused()
await page.keyboard.press('Tab')
await expect(page.getByRole('button', { name: 'All Plugins' })).toBeFocused()
})
test('screen reader compatibility', async ({ page }) => {
await page.goto('/plugins')
// Check for proper ARIA labels
await expect(page.getByRole('main')).toHaveAttribute('aria-label', 'Plugin Marketplace')
await expect(page.getByRole('searchbox')).toHaveAttribute('aria-label', 'Search plugins')
})
})// tests/multi-tenant/isolation.test.ts
describe('Multi-Tenant Isolation', () => {
test('tenant data isolation', async () => {
const tenant1 = await createTestTenant('tenant-1')
const tenant2 = await createTestTenant('tenant-2')
// Add plugins to each tenant
await installPluginForTenant(tenant1.id, 'plugin-a')
await installPluginForTenant(tenant2.id, 'plugin-b')
// Verify isolation
const tenant1Plugins = await getPluginsForTenant(tenant1.id)
const tenant2Plugins = await getPluginsForTenant(tenant2.id)
expect(tenant1Plugins).toContain('plugin-a')
expect(tenant1Plugins).not.toContain('plugin-b')
expect(tenant2Plugins).toContain('plugin-b')
expect(tenant2Plugins).not.toContain('plugin-a')
})
})# .github/workflows/testing.yml
name: Comprehensive Testing
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- run: npm ci
- run: npm run test:unit
- run: npm run test:coverage
- uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
integration-tests:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- run: npm ci
- run: npm run test:integration
e2e-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- run: npm ci
- run: npx playwright install --with-deps
- run: npm run test:e2e
performance-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: grafana/k6-action@v0.2.0
with:
filename: tests/performance/load-test.js
security-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: securecodewarrior/github-action-add-sarif@v1
with:
sarif-file: security-scan-results.sarifThis comprehensive testing strategy ensures:
- 90%+ code coverage with meaningful tests
- Multi-browser and multi-device compatibility
- Performance benchmarks and load testing
- Security vulnerability scanning
- Accessibility compliance verification
- Visual regression detection
- Multi-tenant isolation validation
- Continuous integration and delivery