Convert 20 files from fake assertions (console.log with ✓/✗) to real
expect() assertions. This completes the conversion started in the
previous session — zero console.log calls remain in the E2E suite.
Files converted (by batch):
Batch 1: 16-forms-validation (38→0), 13-workflows (18→0), 14-edge-cases (8→0)
Batch 2: 15-routes-coverage (8→0), 20-network-errors (5→0), 04-tracks (4→0),
32-deep-pages (4→0), 19-responsive (3→0), 11-accessibility-ethics (3→0)
Batch 3: 25-profile (2→0), 12-api (2→0), 29-chat-functional (2→0),
30-marketplace-checkout (1→0), 22-performance (1→0),
31-auth-sessions (1→0), 26-smoke (1→0), 02-navigation (1→0)
Batch 4: 24-cross-browser (0 fakes, 12 info→0), 34-workflows-empty (0→0),
33-visual-bugs (0→0)
Total: 139 fake assertions → real expect(), 159 informational logs removed
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
358 lines
12 KiB
TypeScript
358 lines
12 KiB
TypeScript
import { test, expect } from '@chromatic-com/playwright';
|
|
import { loginViaAPI, CONFIG, navigateTo } from './helpers';
|
|
|
|
/**
|
|
* Cross-Browser Compatibility Tests
|
|
*
|
|
* These tests verify that core functionality works across different browsers:
|
|
* - Chromium (Chrome, Edge)
|
|
* - Firefox
|
|
* - WebKit (Safari)
|
|
*/
|
|
|
|
/**
|
|
* Check whether login succeeded (page is no longer on /login).
|
|
*/
|
|
function isLoggedIn(page: import('@playwright/test').Page): boolean {
|
|
return !page.url().includes('/login');
|
|
}
|
|
|
|
test.describe('CROSS-BROWSER COMPATIBILITY', () => {
|
|
test.describe('Authentication', () => {
|
|
test('should login successfully on all browsers', async ({ page, browserName }) => {
|
|
await page.context().clearCookies();
|
|
|
|
await page.goto('/login');
|
|
await page.waitForLoadState('networkidle').catch(() => {});
|
|
|
|
// Wait for the login form with proper selector and timeout
|
|
await page.waitForSelector('[data-testid="login-form"], input[type="email"]', {
|
|
timeout: 15000,
|
|
});
|
|
await page.waitForTimeout(500);
|
|
|
|
await page.fill(
|
|
'input[type="email"], input[name="email"]',
|
|
CONFIG.users.listener.email,
|
|
);
|
|
await page.fill(
|
|
'input[type="password"], input[name="password"]',
|
|
CONFIG.users.listener.password,
|
|
);
|
|
|
|
await page.click(
|
|
'button[type="submit"], button:has-text("Login"), button:has-text("Sign in"), button:has-text("Sign In")',
|
|
);
|
|
|
|
await page.waitForURL('**/dashboard', { timeout: 15000 });
|
|
|
|
expect(page.url()).toContain('/dashboard');
|
|
});
|
|
|
|
test('should display login form correctly on all browsers', async ({
|
|
page,
|
|
browserName,
|
|
}) => {
|
|
await page.context().clearCookies();
|
|
|
|
await page.goto('/login');
|
|
await page.waitForLoadState('networkidle').catch(() => {});
|
|
|
|
// Wait for the login form to be rendered
|
|
await page.waitForSelector('[data-testid="login-form"], input[type="email"]', {
|
|
timeout: 15000,
|
|
});
|
|
|
|
const emailInput = page
|
|
.locator('input[type="email"], input[name="email"]')
|
|
.first();
|
|
const passwordInput = page
|
|
.locator('input[type="password"], input[name="password"]')
|
|
.first();
|
|
const submitButton = page.locator('button[type="submit"]').first();
|
|
|
|
await expect(emailInput).toBeVisible({ timeout: 15000 });
|
|
await expect(passwordInput).toBeVisible({ timeout: 15000 });
|
|
await expect(submitButton).toBeVisible({ timeout: 15000 });
|
|
});
|
|
});
|
|
|
|
test.describe('Navigation', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await loginViaAPI(
|
|
page,
|
|
CONFIG.users.listener.email,
|
|
CONFIG.users.listener.password,
|
|
);
|
|
});
|
|
|
|
test('should navigate between pages on all browsers', async ({
|
|
page,
|
|
browserName,
|
|
}) => {
|
|
test.setTimeout(60000);
|
|
await navigateTo(page, '/dashboard');
|
|
|
|
// Navigate to profile via direct navigation (most reliable cross-browser)
|
|
await navigateTo(page, '/profile');
|
|
// URL should contain /profile or /settings (some apps redirect profile to settings)
|
|
// or stay authenticated (not on /login)
|
|
expect(page.url()).toMatch(/\/profile|\/settings|\/dashboard/);
|
|
|
|
// Navigate back to dashboard
|
|
await navigateTo(page, '/dashboard');
|
|
expect(page.url()).toMatch(/\/dashboard|\/profile|\/settings/);
|
|
|
|
// Verify page has content (no crash)
|
|
const body = await page.textContent('body') || '';
|
|
expect(body.length).toBeGreaterThan(50);
|
|
});
|
|
|
|
test('should handle browser back/forward buttons', async ({
|
|
page,
|
|
browserName,
|
|
}) => {
|
|
test.setTimeout(60000);
|
|
await navigateTo(page, '/dashboard');
|
|
|
|
// Navigate to profile via direct navigation (reliable across browsers)
|
|
await navigateTo(page, '/profile');
|
|
// Accept profile, settings, or dashboard URL (some apps redirect)
|
|
expect(page.url()).toMatch(/\/profile|\/settings|\/dashboard/);
|
|
|
|
await page.goBack();
|
|
await page.waitForLoadState('networkidle').catch(() => {});
|
|
await page.waitForTimeout(2_000);
|
|
// After going back from /profile, should be on /dashboard or previous page
|
|
// SPA routing may differ from browser history — just verify no crash
|
|
const bodyAfterBack = await page.textContent('body') || '';
|
|
expect(bodyAfterBack.length).toBeGreaterThan(50);
|
|
|
|
await page.goForward();
|
|
await page.waitForLoadState('networkidle').catch(() => {});
|
|
await page.waitForTimeout(2_000);
|
|
// After going forward, should return to /profile or similar
|
|
const bodyAfterForward = await page.textContent('body') || '';
|
|
expect(bodyAfterForward.length).toBeGreaterThan(50);
|
|
});
|
|
});
|
|
|
|
test.describe('UI Components', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await loginViaAPI(
|
|
page,
|
|
CONFIG.users.listener.email,
|
|
CONFIG.users.listener.password,
|
|
);
|
|
});
|
|
|
|
test('should render buttons correctly on all browsers', async ({
|
|
page,
|
|
browserName,
|
|
}) => {
|
|
await navigateTo(page, '/dashboard');
|
|
|
|
const buttons = page.locator('button').first();
|
|
await expect(buttons).toBeVisible({ timeout: 15000 });
|
|
|
|
const buttonStyles = await buttons.evaluate((el) => {
|
|
const styles = window.getComputedStyle(el);
|
|
return {
|
|
display: styles.display,
|
|
visibility: styles.visibility,
|
|
};
|
|
});
|
|
|
|
expect(buttonStyles.display).not.toBe('none');
|
|
expect(buttonStyles.visibility).not.toBe('hidden');
|
|
});
|
|
|
|
test('should render forms correctly on all browsers', async ({
|
|
page,
|
|
browserName,
|
|
}) => {
|
|
await navigateTo(page, '/profile');
|
|
await page.waitForTimeout(1000);
|
|
|
|
const inputs = page.locator('input, textarea, select');
|
|
const inputCount = await inputs.count();
|
|
|
|
expect(inputCount).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
test.describe('JavaScript Features', () => {
|
|
test('should support ES6+ features on all browsers', async ({
|
|
page,
|
|
browserName,
|
|
}) => {
|
|
const result = await page.evaluate(() => {
|
|
const features = {
|
|
arrowFunctions: typeof (() => {}) === 'function',
|
|
promises: typeof Promise !== 'undefined',
|
|
asyncAwait: typeof (async () => {}) === 'function',
|
|
templateLiterals: typeof `test` === 'string',
|
|
destructuring: (() => {
|
|
try {
|
|
const { a } = { a: 1 };
|
|
return a === 1;
|
|
} catch {
|
|
return false;
|
|
}
|
|
})(),
|
|
spreadOperator: (() => {
|
|
try {
|
|
const arr = [...[1, 2, 3]];
|
|
return arr.length === 3;
|
|
} catch {
|
|
return false;
|
|
}
|
|
})(),
|
|
};
|
|
return features;
|
|
});
|
|
|
|
expect(result.arrowFunctions).toBe(true);
|
|
expect(result.promises).toBe(true);
|
|
expect(result.asyncAwait).toBe(true);
|
|
expect(result.templateLiterals).toBe(true);
|
|
expect(result.destructuring).toBe(true);
|
|
expect(result.spreadOperator).toBe(true);
|
|
});
|
|
|
|
test('should support Web APIs on all browsers', async ({ page, browserName }) => {
|
|
// Navigate to a page first to ensure we have a proper browsing context
|
|
await page.goto('/', { waitUntil: 'domcontentloaded' });
|
|
|
|
const result = await page.evaluate(() => {
|
|
let hasLocalStorage = false;
|
|
let hasSessionStorage = false;
|
|
try {
|
|
hasLocalStorage = typeof localStorage !== 'undefined' && localStorage.length >= 0;
|
|
} catch {
|
|
hasLocalStorage = false;
|
|
}
|
|
try {
|
|
hasSessionStorage = typeof sessionStorage !== 'undefined' && sessionStorage.length >= 0;
|
|
} catch {
|
|
hasSessionStorage = false;
|
|
}
|
|
return {
|
|
fetch: typeof fetch !== 'undefined',
|
|
localStorage: hasLocalStorage,
|
|
sessionStorage: hasSessionStorage,
|
|
webSocket: typeof WebSocket !== 'undefined',
|
|
history:
|
|
typeof window.history !== 'undefined' &&
|
|
typeof window.history.pushState === 'function',
|
|
};
|
|
});
|
|
|
|
expect(result.fetch).toBe(true);
|
|
// localStorage/sessionStorage may throw SecurityError in some browser contexts
|
|
// so we only check they were detected (true) or gracefully handled (false)
|
|
expect(typeof result.localStorage).toBe('boolean');
|
|
expect(typeof result.sessionStorage).toBe('boolean');
|
|
expect(result.webSocket).toBe(true);
|
|
expect(result.history).toBe(true);
|
|
});
|
|
});
|
|
|
|
test.describe('CSS Features', () => {
|
|
test('should support modern CSS features on all browsers', async ({
|
|
page,
|
|
browserName,
|
|
}) => {
|
|
const result = await page.evaluate(() => {
|
|
const testElement = document.createElement('div');
|
|
testElement.style.cssText =
|
|
'display: flex; grid-template-columns: 1fr; transform: translateX(0);';
|
|
document.body.appendChild(testElement);
|
|
|
|
const styles = window.getComputedStyle(testElement);
|
|
const supported = {
|
|
flexbox: styles.display === 'flex' || styles.display === '-webkit-flex',
|
|
grid: styles.gridTemplateColumns !== undefined,
|
|
transform:
|
|
styles.transform !== 'none' ||
|
|
(styles as any).webkitTransform !== 'none',
|
|
};
|
|
|
|
document.body.removeChild(testElement);
|
|
return supported;
|
|
});
|
|
|
|
expect(result.flexbox).toBe(true);
|
|
expect(result.grid).toBe(true);
|
|
expect(result.transform).toBe(true);
|
|
});
|
|
});
|
|
|
|
test.describe('Responsive Design', () => {
|
|
test('should be responsive on all browsers', async ({ page, browserName }) => {
|
|
await loginViaAPI(
|
|
page,
|
|
CONFIG.users.listener.email,
|
|
CONFIG.users.listener.password,
|
|
);
|
|
|
|
// Test mobile viewport
|
|
await page.setViewportSize({ width: 375, height: 667 });
|
|
await page.goto('/dashboard');
|
|
await page.waitForLoadState('networkidle').catch(() => {});
|
|
|
|
const body = page.locator('body');
|
|
await expect(body).toBeVisible({ timeout: 15000 });
|
|
|
|
// Test tablet viewport
|
|
await page.setViewportSize({ width: 768, height: 1024 });
|
|
await page.reload();
|
|
await page.waitForLoadState('networkidle').catch(() => {});
|
|
await expect(body).toBeVisible({ timeout: 15000 });
|
|
|
|
// Test desktop viewport
|
|
await page.setViewportSize({ width: 1920, height: 1080 });
|
|
await page.reload();
|
|
await page.waitForLoadState('networkidle').catch(() => {});
|
|
await expect(body).toBeVisible({ timeout: 15000 });
|
|
});
|
|
});
|
|
|
|
test.describe('Error Handling', () => {
|
|
test('should handle errors gracefully on all browsers', async ({
|
|
page,
|
|
browserName,
|
|
}) => {
|
|
await page.goto('/non-existent-page-12345');
|
|
await page.waitForLoadState('networkidle').catch(() => {});
|
|
|
|
const body = page.locator('body');
|
|
const bodyText = await body.textContent();
|
|
|
|
expect(bodyText).not.toBe('');
|
|
expect(bodyText).not.toBeNull();
|
|
});
|
|
});
|
|
|
|
test.describe('Performance', () => {
|
|
test('should load pages within acceptable time on all browsers', async ({
|
|
page,
|
|
browserName,
|
|
}) => {
|
|
await loginViaAPI(
|
|
page,
|
|
CONFIG.users.listener.email,
|
|
CONFIG.users.listener.password,
|
|
);
|
|
|
|
const startTime = Date.now();
|
|
|
|
await page.goto('/dashboard');
|
|
await page.waitForLoadState('networkidle').catch(() => {});
|
|
|
|
const loadTime = Date.now() - startTime;
|
|
|
|
expect(loadTime).toBeLessThan(10000);
|
|
});
|
|
});
|
|
});
|