112 lines
3.5 KiB
TypeScript
112 lines
3.5 KiB
TypeScript
/**
|
|
* Module Loading Smoke Tests
|
|
*
|
|
* Verifies that JavaScript modules load correctly without MIME type errors.
|
|
* Catches issues like nginx misconfiguration for .vite-cache paths.
|
|
*/
|
|
|
|
import { test, expect } from '@playwright/test';
|
|
|
|
interface ConsoleErrorInfo {
|
|
text: string;
|
|
type: string;
|
|
}
|
|
|
|
/**
|
|
* Patterns that indicate module loading failures
|
|
*/
|
|
const MODULE_ERROR_PATTERNS = [
|
|
/MIME type/i,
|
|
/Failed to load module/i,
|
|
/Loading module.*was blocked/i,
|
|
/dynamically imported module/i,
|
|
/SyntaxError.*Unexpected token/i, // Often indicates wrong MIME type returning HTML
|
|
/Failed to fetch dynamically imported module/i,
|
|
];
|
|
|
|
/**
|
|
* Patterns that should be ignored (expected warnings, not errors)
|
|
*/
|
|
const IGNORED_PATTERNS = [
|
|
/Download the React DevTools/i,
|
|
/Accessing urls\.base before config initialized/i,
|
|
/^\[vite\]/i, // Vite dev server messages
|
|
];
|
|
|
|
/**
|
|
* Check if an error matches module loading failure patterns
|
|
*/
|
|
function isModuleLoadingError(text: string): boolean {
|
|
return MODULE_ERROR_PATTERNS.some((pattern) => pattern.test(text));
|
|
}
|
|
|
|
/**
|
|
* Check if a message should be ignored
|
|
*/
|
|
function shouldIgnore(text: string): boolean {
|
|
return IGNORED_PATTERNS.some((pattern) => pattern.test(text));
|
|
}
|
|
|
|
test.describe('Module Loading', () => {
|
|
test('homepage loads without module errors', async ({ page }) => {
|
|
const errors: ConsoleErrorInfo[] = [];
|
|
|
|
// Capture page errors (uncaught exceptions)
|
|
page.on('pageerror', (error) => {
|
|
if (!shouldIgnore(error.message) && isModuleLoadingError(error.message)) {
|
|
errors.push({ text: error.message, type: 'pageerror' });
|
|
}
|
|
});
|
|
|
|
// Capture console errors
|
|
page.on('console', (msg) => {
|
|
const text = msg.text();
|
|
if (msg.type() === 'error' && !shouldIgnore(text) && isModuleLoadingError(text)) {
|
|
errors.push({ text, type: 'console.error' });
|
|
}
|
|
});
|
|
|
|
// Navigate and wait for full page load
|
|
await page.goto('/');
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Give time for dynamic imports to complete
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Assert no module loading errors
|
|
expect(errors, `Module loading errors detected:\n${errors.map((e) => ` [${e.type}] ${e.text}`).join('\n')}`).toHaveLength(0);
|
|
});
|
|
|
|
test('critical resources have correct MIME types', async ({ page }) => {
|
|
const mimeErrors: string[] = [];
|
|
|
|
// Monitor network requests for MIME type issues
|
|
page.on('response', (response) => {
|
|
const url = response.url();
|
|
const contentType = response.headers()['content-type'] || '';
|
|
|
|
// Check JavaScript files
|
|
if (url.endsWith('.js') || url.endsWith('.mjs') || url.includes('.vite-cache/deps/')) {
|
|
if (!contentType.includes('javascript') && !contentType.includes('application/javascript')) {
|
|
mimeErrors.push(`${url} has Content-Type: ${contentType || '(empty)'}`);
|
|
}
|
|
}
|
|
|
|
// Check TypeScript files (in dev mode, served as JS)
|
|
if ((url.endsWith('.ts') || url.endsWith('.tsx')) && response.status() === 200) {
|
|
if (!contentType.includes('javascript') && !contentType.includes('typescript')) {
|
|
// In dev mode, Vite serves .ts/.tsx as transformed JS
|
|
// If content-type is html, something went wrong
|
|
if (contentType.includes('text/html')) {
|
|
mimeErrors.push(`${url} returned HTML instead of JavaScript`);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
await page.goto('/');
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
expect(mimeErrors, `MIME type errors:\n${mimeErrors.join('\n')}`).toHaveLength(0);
|
|
});
|
|
});
|