Deploy to Production / Build & Verify (push) Failing after 5m56s
Ping Search Engines / Notify Search Engines (push) Successful in 2s
Deploy to Production / Pre-Deploy Tests (push) Has been skipped
Deploy to Production / Deploy to Railway (push) Has been skipped
Deploy to Production / Deploy to Render (push) Has been skipped
Deploy to Production / Deploy to VPS (PM2) (push) Has been skipped
Deploy to Production / Deploy to Fly.io (push) Has been skipped
Deploy to Production / Post-Deploy Verification (push) Has been skipped
Deploy to Production / Notify on Failure (push) Successful in 2s
E2E Test Suite / Critical User Journeys (push) Has been skipped
E2E Test Suite / API Integration Tests (push) Has been skipped
E2E Test Suite / Smoke Tests (P0) (push) Failing after 11m26s
E2E Test Suite / Form Interaction Tests (push) Failing after 11m42s
E2E Test Suite / Destructive & Chaos Tests (push) Failing after 12m2s
E2E Test Suite / Cross-Browser Regression (chromium) (push) Failing after 16m14s
E2E Test Suite / Cross-Browser Regression (webkit) (push) Failing after 17m45s
E2E Test Suite / Cross-Browser Regression (firefox) (push) Failing after 25m23s
E2E Test Suite / Security Header Tests (push) Failing after 7m55s
E2E Test Suite / Test Report Summary (push) Failing after 20s
E2E Test Suite / Mobile Device Tests (push) Failing after 2h49m9s
Uptime Monitor / Health & Response Time (push) Failing after 2s
Uptime Monitor / SSL Certificate (push) Successful in 2s
Uptime Monitor / Send Alerts (push) Failing after 3s
Uptime Monitor / Record Uptime Success (push) Has been skipped
288 lines
8.8 KiB
TypeScript
288 lines
8.8 KiB
TypeScript
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('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
|
|
expect(page.url()).not.toContain('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('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('button[type="submit"]').click();
|
|
|
|
// Should show success
|
|
await expect(page.locator('text=/sent successfully/i')).toBeVisible({ timeout: 5000 });
|
|
});
|
|
|
|
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');
|
|
|
|
// Should be able to tab through form
|
|
await page.keyboard.press('Tab');
|
|
const firstField = page.locator('input[name="name"]');
|
|
await expect(firstField).toBeFocused();
|
|
|
|
await page.keyboard.press('Tab');
|
|
const secondField = page.locator('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();
|
|
});
|
|
});
|