veza/tests/e2e/24-cross-browser.spec.ts
senke 6fad0ad68d fix: stabilize frontend — 98 TS errors to 0, align API endpoints, optimize bundle
- Fix 98 TypeScript errors across 37 files:
  - Service layer double-unwrapping (subscriptionService, distributionService, gearService)
  - Self-referencing variables in SearchPageResults
  - FeedView/ExploreView .posts→.items alignment
  - useQueueSync Zustand subscribe API
  - AdminAuditLogsView missing interface fields
  - Toast proxy type, interceptor type narrowing
  - 22 unused imports/variables removed
  - 5 storybook mock data fixes

- Align frontend API calls with backend endpoints:
  - Analytics: useAnalyticsView now calls /creator/analytics/dashboard (was /analytics)
  - Chat: chatService uses /conversations (was mock data), WS URL from backend token
  - Dashboard StatsSection: uses real /dashboard API data (was hardcoded zeros)
  - Settings: suppress 2FA toast error when endpoint unavailable

- Fix marketplace products: seed uses 'active' status (was 'published')
- Enrich seed: admin follows all creators (feed has content)

- Optimize bundle: vendor catch-all 793KB→318KB gzip (-60%)
  Split into vendor-charts, vendor-emoji, vendor-swagger, vendor-media, etc.

- Clean repo: remove ~100 orphaned screenshots, audit reports, logs from root

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 21:18:49 +01:00

382 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');
console.log(`Login successful on ${browserName}`);
});
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 });
console.log(`Login form displayed correctly on ${browserName}`);
});
});
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);
console.log(`Navigation works on ${browserName}`);
});
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);
console.log(`Browser navigation works on ${browserName}`);
});
});
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');
console.log(`Buttons render correctly on ${browserName}`);
});
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);
console.log(`Forms render correctly on ${browserName}`);
});
});
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);
console.log(`ES6+ features supported on ${browserName}`);
});
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);
console.log(`Web APIs supported on ${browserName}`);
});
});
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);
console.log(`Modern CSS features supported on ${browserName}`);
});
});
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 });
console.log(`Responsive design works on ${browserName}`);
});
});
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();
console.log(`Error handling works on ${browserName}`);
});
});
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);
console.log(`Page loaded in ${loadTime}ms on ${browserName}`);
});
});
});