import { test, expect } from '@playwright/test'; /** * E2E Smoke Test Suite (P0) * Critical tests that must pass before any deployment * Should run in under 2 minutes */ test.describe('Smoke Suite - Critical Page Loads', () => { const criticalPages = [ { path: '/', name: 'Homepage' }, { path: '/about', name: 'About' }, { path: '/services', name: 'Services' }, { path: '/portfolio', name: 'Portfolio' }, { path: '/blog', name: 'Blog Listing' }, { path: '/blog/getting-started-with-astro', name: 'Blog Post' }, { path: '/contact', name: 'Contact' }, { path: '/privacy', name: 'Privacy Policy' }, { path: '/terms', name: 'Terms of Service' } ]; for (const page of criticalPages) { test(`${page.name} loads successfully`, async ({ page: browserPage }) => { const response = await browserPage.goto(page.path); // Page should return 200 expect(response?.status()).toBe(200); // Core layout elements must be present await expect(browserPage.locator('header')).toBeVisible(); await expect(browserPage.locator('main')).toBeVisible(); await expect(browserPage.locator('footer')).toBeVisible(); // Title should be set const title = await browserPage.title(); expect(title.length).toBeGreaterThan(0); }); } }); test.describe('Smoke Suite - Critical User Flows', () => { test('homepage to contact flow works', async ({ page }) => { await page.goto('/'); const contactLink = page.locator('a[href="/contact"]').first(); await contactLink.click(); await expect(page).toHaveURL('/contact'); await expect(page.locator('#contact-form')).toBeVisible(); }); test('navigation menu works', async ({ page }) => { await page.goto('/'); // Test main navigation links const navLinks = ['About', 'Services', 'Blog']; for (const linkText of navLinks) { await page.goto('/'); const link = page.locator(`a:has-text("${linkText}")`).first(); if (await link.isVisible()) { await link.click(); await page.waitForLoadState('domcontentloaded'); // Should navigate away from homepage (URL should contain the linked path) expect(page.url()).not.toMatch(/http:\/\/localhost:10000\/?$/); } } }); test('contact form submission works', async ({ page }) => { await page.goto('/contact'); await page.locator('input[name="name"]').fill('Smoke Test User'); await page.locator('#contact-form input[name="email"]').fill('smoke@test.com'); await page.locator('select[name="subject"]').selectOption('web-development'); await page.locator('textarea[name="message"]').fill('Automated smoke test message.'); await page.locator('#contact-form button[type="submit"]').click(); // Should show success state or an error toast (rate limit / server error both indicate API was reached) // Wait for either success state or a toast alert to become visible await page.waitForFunction(() => { const successState = document.getElementById('success-state'); const toastAlert = document.querySelector('#toast-container [role="alert"]'); const successVisible = successState && !successState.classList.contains('hidden'); const toastVisible = toastAlert && (toastAlert as HTMLElement).offsetParent !== null; return successVisible || toastVisible; }, { timeout: 10000 }); }); test('blog navigation works', async ({ page }) => { await page.goto('/blog'); const posts = page.locator('article, [class*="post"]'); expect(await posts.count()).toBeGreaterThan(0); // Click first post await posts.first().locator('a').first().click(); await expect(page).toHaveURL(/\/blog\//); // Content should load await expect(page.locator('h1')).toBeVisible(); }); }); test.describe('Smoke Suite - Performance', () => { test('homepage loads within acceptable time', async ({ page }) => { const startTime = Date.now(); await page.goto('/'); await page.waitForLoadState('domcontentloaded'); const loadTime = Date.now() - startTime; // Should load in under 3 seconds (generous for smoke test) expect(loadTime).toBeLessThan(3000); }); test('no JavaScript errors on homepage', async ({ page }) => { const errors: string[] = []; page.on('console', msg => { if (msg.type() === 'error') { errors.push(msg.text()); } }); await page.goto('/'); await page.waitForLoadState('networkidle'); // Should have no console errors expect(errors.length).toBe(0); }); }); test.describe('Smoke Suite - Responsiveness', () => { const viewports = [ { width: 375, height: 667, name: 'Mobile' }, { width: 768, height: 1024, name: 'Tablet' }, { width: 1920, height: 1080, name: 'Desktop' } ]; for (const viewport of viewports) { test(`homepage renders correctly on ${viewport.name}`, async ({ page }) => { await page.setViewportSize({ width: viewport.width, height: viewport.height }); await page.goto('/'); // Layout should not overflow const bodyWidth = await page.evaluate(() => document.body.scrollWidth); expect(bodyWidth).toBeLessThanOrEqual(viewport.width + 20); // Core elements visible await expect(page.locator('header')).toBeVisible(); await expect(page.locator('main')).toBeVisible(); }); } }); test.describe('Smoke Suite - SEO Basics', () => { test('homepage has proper meta tags', async ({ page }) => { await page.goto('/'); // Title const title = await page.title(); expect(title.length).toBeGreaterThan(0); expect(title.length).toBeLessThan(60); // Meta description const description = await page.locator('meta[name="description"]').getAttribute('content'); expect(description).toBeTruthy(); expect(description!.length).toBeGreaterThan(50); // Open Graph const ogTitle = await page.locator('meta[property="og:title"]').getAttribute('content'); expect(ogTitle).toBeTruthy(); }); test('blog post has proper structured data', async ({ page }) => { await page.goto('/blog/getting-started-with-astro'); // Check for JSON-LD structured data const jsonLd = page.locator('script[type="application/ld+json"]'); const count = await jsonLd.count(); if (count > 0) { const content = await jsonLd.first().textContent(); expect(content).toBeTruthy(); // Should be valid JSON const parsed = JSON.parse(content!); expect(parsed).toBeTruthy(); } }); }); test.describe('Smoke Suite - Security Headers', () => { test('security headers are present', async ({ page }) => { const response = await page.goto('/'); if (response) { const headers = response.headers(); // Check for basic security headers // Note: Some headers may only be present in production const hasCSP = 'content-security-policy' in headers; const hasXFrame = 'x-frame-options' in headers; // At least one should be present expect(hasCSP || hasXFrame).toBeTruthy(); } }); test('HTTPS redirect configured (production only)', async ({ page }) => { const response = await page.goto('/'); if (response) { const url = response.url(); // In production, should use HTTPS // In dev mode (localhost), HTTP is acceptable expect(url.startsWith('http://localhost') || url.startsWith('https://')).toBeTruthy(); } }); }); test.describe('Smoke Suite - Accessibility Basics', () => { test('homepage has no major accessibility violations', async ({ page }) => { await page.goto('/'); // Check for basic accessibility requirements // 1. Images have alt text const images = page.locator('img'); const imageCount = await images.count(); for (let i = 0; i < Math.min(imageCount, 5); i++) { const img = images.nth(i); if (await img.isVisible()) { const alt = await img.getAttribute('alt'); expect(alt !== null).toBeTruthy(); } } // 2. Links have text or aria-label const links = page.locator('a'); const linkCount = await links.count(); for (let i = 0; i < Math.min(linkCount, 10); i++) { const link = links.nth(i); const text = await link.textContent(); const ariaLabel = await link.getAttribute('aria-label'); expect((text && text.trim().length > 0) || ariaLabel).toBeTruthy(); } }); test('contact form is keyboard navigable', async ({ page }) => { await page.goto('/contact'); // Click on the name field directly to set focus, then tab to email const firstField = page.locator('input[name="name"]'); await firstField.click(); await expect(firstField).toBeFocused(); await page.keyboard.press('Tab'); const secondField = page.locator('#contact-form input[name="email"]'); await expect(secondField).toBeFocused(); }); }); test.describe('Smoke Suite - Critical Assets', () => { test('favicon loads', async ({ page }) => { await page.goto('/'); const favicon = page.locator('link[rel="icon"], link[rel="shortcut icon"]'); const count = await favicon.count(); expect(count).toBeGreaterThan(0); }); test('main stylesheet loads', async ({ page }) => { const response = await page.goto('/'); await page.waitForLoadState('networkidle'); // Check that some CSS is applied const bgColor = await page.locator('body').evaluate(el => { return window.getComputedStyle(el).backgroundColor; }); expect(bgColor).toBeTruthy(); }); });