Files
CompanySite/tests/newsletter-subscription.spec.ts
Clintchiz d402256547
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
First Init
2026-03-21 16:46:46 +05:30

387 lines
14 KiB
TypeScript

import { test, expect } from '@playwright/test';
/**
* Newsletter Subscription E2E Tests
* Tests the newsletter form in the footer across all pages
* - Happy path subscription
* - Validation (empty, invalid email)
* - Loading states
* - Error handling (network, server)
* - Rate limiting behavior
* - Accessibility
*/
test.describe('Newsletter Subscription - Form Presence', () => {
const pagesWithNewsletter = [
{ path: '/', name: 'Homepage' },
{ path: '/about', name: 'About' },
{ path: '/services', name: 'Services' },
{ path: '/blog', name: 'Blog' },
{ path: '/contact', name: 'Contact' },
];
for (const pg of pagesWithNewsletter) {
test(`newsletter form is present on ${pg.name}`, async ({ page }) => {
await page.goto(pg.path);
await page.waitForLoadState('domcontentloaded');
const form = page.locator('#newsletter-form');
await expect(form).toBeVisible();
const emailInput = page.locator('#newsletter-email');
await expect(emailInput).toBeVisible();
const submitButton = form.locator('button[type="submit"]');
await expect(submitButton).toBeVisible();
});
}
});
test.describe('Newsletter Subscription - Happy Path', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.waitForLoadState('networkidle');
// Scroll to footer where newsletter form is
await page.locator('footer').scrollIntoViewIfNeeded();
});
test('valid email subscription shows success message', async ({ page }) => {
const emailInput = page.locator('#newsletter-email');
const submitButton = page.locator('#newsletter-form button[type="submit"]');
const messageEl = page.locator('#newsletter-message');
await emailInput.fill('subscriber@example.com');
await submitButton.click();
// Message should appear
await expect(messageEl).not.toHaveClass(/hidden/, { timeout: 10000 });
const messageText = await messageEl.textContent();
expect(messageText).toBeTruthy();
expect(messageText!.length).toBeGreaterThan(0);
});
test('form resets after successful subscription', async ({ page }) => {
const emailInput = page.locator('#newsletter-email');
const submitButton = page.locator('#newsletter-form button[type="submit"]');
await emailInput.fill('reset.test@example.com');
await submitButton.click();
// Wait for response
await page.waitForTimeout(3000);
// Check if message is shown (success or configured error)
const messageEl = page.locator('#newsletter-message');
const isVisible = await messageEl.evaluate((el) => !el.classList.contains('hidden'));
// If successful, form should be reset
if (isVisible) {
const messageText = await messageEl.textContent();
if (messageText?.toLowerCase().includes('subscrib') || messageText?.toLowerCase().includes('thanks')) {
await expect(emailInput).toHaveValue('');
}
}
});
test('submit button shows loading state during submission', async ({ page }) => {
const emailInput = page.locator('#newsletter-email');
const submitButton = page.locator('#newsletter-form button[type="submit"]');
await emailInput.fill('loading.test@example.com');
await submitButton.click();
// Button should be disabled during submission
await expect(submitButton).toBeDisabled();
});
test('submit button re-enables after submission completes', async ({ page }) => {
const emailInput = page.locator('#newsletter-email');
const submitButton = page.locator('#newsletter-form button[type="submit"]');
await emailInput.fill('reenable.test@example.com');
await submitButton.click();
// Wait for the submission to complete
await page.waitForTimeout(4000);
// Button should be re-enabled
await expect(submitButton).toBeEnabled();
});
});
test.describe('Newsletter Subscription - Validation', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.locator('footer').scrollIntoViewIfNeeded();
});
test('email field is required - HTML5 validation', async ({ page }) => {
const emailInput = page.locator('#newsletter-email');
const submitButton = page.locator('#newsletter-form button[type="submit"]');
// Submit without email
await submitButton.click();
// HTML5 required validation should prevent submission
const isInvalid = await emailInput.evaluate((el: HTMLInputElement) => !el.checkValidity());
expect(isInvalid).toBeTruthy();
});
test('email field rejects invalid email format - HTML5', async ({ page }) => {
const emailInput = page.locator('#newsletter-email');
const submitButton = page.locator('#newsletter-form button[type="submit"]');
await emailInput.fill('not-an-email');
await submitButton.click();
// HTML5 type="email" should reject non-email
const isInvalid = await emailInput.evaluate((el: HTMLInputElement) => !el.checkValidity());
expect(isInvalid).toBeTruthy();
});
test('email field accepts valid email format', async ({ page }) => {
const emailInput = page.locator('#newsletter-email');
const validEmails = [
'user@example.com',
'user.name+tag@example.co.uk',
'user123@subdomain.domain.org',
];
for (const email of validEmails) {
await emailInput.fill(email);
const isValid = await emailInput.evaluate((el: HTMLInputElement) => el.checkValidity());
expect(isValid).toBeTruthy();
}
});
test('email field has correct input type', async ({ page }) => {
const emailInput = page.locator('#newsletter-email');
const inputType = await emailInput.getAttribute('type');
expect(inputType).toBe('email');
});
test('email field has autocomplete attribute', async ({ page }) => {
const emailInput = page.locator('#newsletter-email');
const autocomplete = await emailInput.getAttribute('autocomplete');
expect(autocomplete).toBe('email');
});
});
test.describe('Newsletter Subscription - API Integration', () => {
test('newsletter API endpoint responds to valid subscription', async ({ request }) => {
const response = await request.post('/api/newsletter', {
headers: { 'Content-Type': 'application/json' },
data: { email: 'api.test@example.com' },
});
// Should be 200 (success) or 429 (rate limited in test env)
expect([200, 429]).toContain(response.status());
if (response.status() === 200) {
const body = await response.json() as { success: boolean; message?: string };
expect(body.success).toBe(true);
expect(body.message).toBeTruthy();
}
});
test('newsletter API rejects invalid email', async ({ request }) => {
const response = await request.post('/api/newsletter', {
headers: { 'Content-Type': 'application/json' },
data: { email: 'invalid-email' },
});
expect(response.status()).toBe(422);
const body = await response.json() as { success: boolean; error: string };
expect(body.success).toBe(false);
expect(body.error).toBeTruthy();
});
test('newsletter API rejects empty email', async ({ request }) => {
const response = await request.post('/api/newsletter', {
headers: { 'Content-Type': 'application/json' },
data: { email: '' },
});
expect(response.status()).toBe(422);
const body = await response.json() as { success: boolean; error: string };
expect(body.success).toBe(false);
});
test('newsletter API returns rate limit headers', async ({ request }) => {
const response = await request.post('/api/newsletter', {
headers: { 'Content-Type': 'application/json' },
data: { email: 'ratelimit.headers@example.com' },
});
const headers = response.headers();
expect(headers['x-ratelimit-limit']).toBeDefined();
expect(headers['x-ratelimit-remaining']).toBeDefined();
expect(headers['x-ratelimit-reset']).toBeDefined();
});
test('newsletter API accepts form-encoded data', async ({ request }) => {
const response = await request.post('/api/newsletter', {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
form: { email: 'formencoded@example.com' },
});
// Should be 200 or 429 (not 400 bad request)
expect([200, 429]).toContain(response.status());
});
});
test.describe('Newsletter Subscription - Network Error Handling', () => {
test('newsletter form shows error on network failure', async ({ page }) => {
await page.goto('/');
await page.locator('footer').scrollIntoViewIfNeeded();
// Intercept the API call and make it fail
await page.route('/api/newsletter', async (route) => {
await route.abort('failed');
});
const emailInput = page.locator('#newsletter-email');
const submitButton = page.locator('#newsletter-form button[type="submit"]');
const messageEl = page.locator('#newsletter-message');
await emailInput.fill('network.error@example.com');
await submitButton.click();
// Should show error message
await expect(messageEl).not.toHaveClass(/hidden/, { timeout: 5000 });
const messageText = await messageEl.textContent();
expect(messageText?.toLowerCase()).toMatch(/network|error|check/);
});
test('newsletter form shows server error response', async ({ page }) => {
await page.goto('/');
await page.locator('footer').scrollIntoViewIfNeeded();
// Intercept and return server error
await page.route('/api/newsletter', async (route) => {
await route.fulfill({
status: 500,
contentType: 'application/json',
body: JSON.stringify({ success: false, error: 'Server temporarily unavailable.' }),
});
});
const emailInput = page.locator('#newsletter-email');
const submitButton = page.locator('#newsletter-form button[type="submit"]');
const messageEl = page.locator('#newsletter-message');
await emailInput.fill('server.error@example.com');
await submitButton.click();
// Should show error
await expect(messageEl).not.toHaveClass(/hidden/, { timeout: 5000 });
const messageText = await messageEl.textContent();
expect(messageText).toBeTruthy();
});
test('newsletter button re-enables after network failure', async ({ page }) => {
await page.goto('/');
await page.locator('footer').scrollIntoViewIfNeeded();
await page.route('/api/newsletter', async (route) => {
await route.abort('failed');
});
const emailInput = page.locator('#newsletter-email');
const submitButton = page.locator('#newsletter-form button[type="submit"]');
await emailInput.fill('recovery@example.com');
await submitButton.click();
// Wait for error handling
await page.waitForTimeout(3000);
// Button should be re-enabled
await expect(submitButton).toBeEnabled();
});
});
test.describe('Newsletter Subscription - Accessibility', () => {
test('newsletter email field has accessible label', async ({ page }) => {
await page.goto('/');
await page.locator('footer').scrollIntoViewIfNeeded();
const emailInput = page.locator('#newsletter-email');
// Check for label or placeholder
const hasLabel = await page.locator('label[for="newsletter-email"]').count() > 0;
const placeholder = await emailInput.getAttribute('placeholder');
const ariaLabel = await emailInput.getAttribute('aria-label');
expect(hasLabel || placeholder || ariaLabel).toBeTruthy();
});
test('newsletter submit button has accessible text', async ({ page }) => {
await page.goto('/');
await page.locator('footer').scrollIntoViewIfNeeded();
const submitButton = page.locator('#newsletter-form button[type="submit"]');
const text = await submitButton.textContent();
const ariaLabel = await submitButton.getAttribute('aria-label');
expect((text && text.trim().length > 0) || ariaLabel).toBeTruthy();
});
test('newsletter form is keyboard accessible', async ({ page }) => {
await page.goto('/');
// Tab to newsletter form
const emailInput = page.locator('#newsletter-email');
await emailInput.scrollIntoViewIfNeeded();
await emailInput.focus();
// Should be focusable
await expect(emailInput).toBeFocused();
// Tab to submit button
await page.keyboard.press('Tab');
const submitButton = page.locator('#newsletter-form button[type="submit"]');
await expect(submitButton).toBeFocused();
});
});
test.describe('Newsletter Subscription - Mobile Experience', () => {
test.use({ viewport: { width: 375, height: 667 } });
test('newsletter form is visible and usable on mobile', async ({ page }) => {
await page.goto('/');
await page.waitForLoadState('networkidle');
const form = page.locator('#newsletter-form');
await form.scrollIntoViewIfNeeded();
await expect(form).toBeVisible();
const emailInput = page.locator('#newsletter-email');
await emailInput.tap();
await emailInput.fill('mobile.newsletter@example.com');
await expect(emailInput).toHaveValue('mobile.newsletter@example.com');
});
test('newsletter email input triggers email keyboard on mobile', async ({ page }) => {
await page.goto('/');
const emailInput = page.locator('#newsletter-email');
const inputType = await emailInput.getAttribute('type');
expect(inputType).toBe('email'); // This triggers email keyboard
});
test('newsletter form submit button is tappable on mobile', async ({ page }) => {
await page.goto('/');
const submitButton = page.locator('#newsletter-form button[type="submit"]');
await submitButton.scrollIntoViewIfNeeded();
await expect(submitButton).toBeVisible();
// Check the button is large enough to tap (min 44x44 per WCAG)
const box = await submitButton.boundingBox();
expect(box).toBeTruthy();
expect(box!.height).toBeGreaterThanOrEqual(40); // Slightly lenient
});
});