- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
121 lines
3.7 KiB
TypeScript
121 lines
3.7 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
|
|
/**
|
|
* Critical Path: Homepage Accessibility and Basic Functionality
|
|
*
|
|
* Tests the most critical user journey - accessing the homepage
|
|
*/
|
|
test.describe('Homepage', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.goto('/');
|
|
});
|
|
|
|
test('should load homepage successfully', async ({ page }) => {
|
|
// Check page loaded
|
|
await expect(page).toHaveTitle(/michaelschiemer/i);
|
|
|
|
// Check main content is visible
|
|
await expect(page.locator('main')).toBeVisible();
|
|
});
|
|
|
|
test('should have valid HTML structure', async ({ page }) => {
|
|
// Check essential meta tags
|
|
const metaViewport = page.locator('meta[name="viewport"]');
|
|
await expect(metaViewport).toHaveAttribute('content', /width=device-width/);
|
|
|
|
// Check for proper heading hierarchy
|
|
const h1 = page.locator('h1').first();
|
|
await expect(h1).toBeVisible();
|
|
});
|
|
|
|
test('should have working navigation', async ({ page }) => {
|
|
// Check navigation is present
|
|
const nav = page.locator('nav').first();
|
|
await expect(nav).toBeVisible();
|
|
|
|
// Check navigation links are clickable
|
|
const navLinks = page.locator('nav a');
|
|
const linkCount = await navLinks.count();
|
|
expect(linkCount).toBeGreaterThan(0);
|
|
|
|
// First link should be clickable
|
|
await expect(navLinks.first()).toBeVisible();
|
|
});
|
|
|
|
test('should load without JavaScript errors', async ({ page }) => {
|
|
const errors: string[] = [];
|
|
|
|
page.on('pageerror', (error) => {
|
|
errors.push(error.message);
|
|
});
|
|
|
|
await page.goto('/');
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Assert no JavaScript errors
|
|
expect(errors).toHaveLength(0);
|
|
});
|
|
|
|
test('should have proper security headers', async ({ page }) => {
|
|
const response = await page.goto('/');
|
|
|
|
// Check for security headers
|
|
const headers = response?.headers();
|
|
expect(headers).toBeDefined();
|
|
|
|
if (headers) {
|
|
// X-Frame-Options
|
|
expect(headers['x-frame-options']).toBeDefined();
|
|
|
|
// X-Content-Type-Options
|
|
expect(headers['x-content-type-options']).toBe('nosniff');
|
|
|
|
// Content-Security-Policy (if configured)
|
|
// expect(headers['content-security-policy']).toBeDefined();
|
|
}
|
|
});
|
|
|
|
test('should be responsive (mobile viewport)', async ({ page }) => {
|
|
// Set mobile viewport
|
|
await page.setViewportSize({ width: 375, height: 667 });
|
|
|
|
await page.goto('/');
|
|
|
|
// Check mobile menu is accessible
|
|
const mobileMenu = page.locator('[data-mobile-menu], .mobile-menu, button[aria-label*="menu"]');
|
|
|
|
// Mobile menu should either be visible or exist in DOM
|
|
const count = await mobileMenu.count();
|
|
expect(count).toBeGreaterThanOrEqual(0);
|
|
});
|
|
|
|
test('should have accessible landmarks', async ({ page }) => {
|
|
// Check for ARIA landmarks
|
|
await expect(page.locator('[role="main"], main')).toBeVisible();
|
|
await expect(page.locator('[role="navigation"], nav')).toBeVisible();
|
|
|
|
// Footer should exist
|
|
const footer = page.locator('[role="contentinfo"], footer');
|
|
await expect(footer).toBeVisible();
|
|
});
|
|
|
|
test('should load CSS and assets', async ({ page }) => {
|
|
const response = await page.goto('/');
|
|
|
|
// Check status code
|
|
expect(response?.status()).toBe(200);
|
|
|
|
// Wait for all network requests to complete
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Check if stylesheets loaded
|
|
const stylesheets = page.locator('link[rel="stylesheet"]');
|
|
const count = await stylesheets.count();
|
|
expect(count).toBeGreaterThan(0);
|
|
|
|
// Verify at least one stylesheet is loaded
|
|
const firstStylesheet = stylesheets.first();
|
|
await expect(firstStylesheet).toHaveAttribute('href', /.+/);
|
|
});
|
|
});
|