Files
gridpilot.gg/tests/smoke/electron-build.smoke.test.ts
2025-12-04 17:07:59 +01:00

113 lines
3.9 KiB
TypeScript

import { test, expect } from '@playwright/test';
import { execSync } from 'child_process';
/**
* Electron Build Smoke Test
*
* Purpose: Detect browser context errors during Electron build
*
* This test catches bundling issues where Node.js modules are imported
* in the renderer process, causing runtime errors.
*
* RED Phase: This test MUST FAIL due to externalized modules
*/
test.describe('Electron Build Smoke Tests', () => {
test('should build Electron app without browser context errors', () => {
// When: Building the Electron companion app
let buildOutput: string;
try {
buildOutput = execSync('npm run companion:build', {
cwd: process.cwd(),
encoding: 'utf-8',
stdio: 'pipe',
});
} catch (error: any) {
buildOutput = error.stdout + error.stderr;
}
// Then: Build should not contain externalized module warnings
const foundErrors: string[] = [];
// Split output into lines and check each line
const lines = buildOutput.split('\n');
lines.forEach((line: string) => {
if (line.includes('has been externalized for browser compatibility')) {
foundErrors.push(line.trim());
}
if (line.includes('Cannot access') && line.includes('in client code')) {
foundErrors.push(line.trim());
}
});
// This WILL FAIL in RED phase due to electron/fs/path being externalized
expect(
foundErrors.length,
`Browser context errors detected during build:\n\n${foundErrors.map((e, i) => `${i + 1}. ${e}`).join('\n')}\n\n` +
`These indicate Node.js modules (electron, fs, path) are being imported in renderer code.\n` +
`This will cause runtime errors when the app launches.`
).toBe(0);
});
test('should not import Node.js modules in renderer source code', () => {
// Given: Renderer source code
const fs = require('fs');
const path = require('path');
const rendererPath = path.join(
process.cwd(),
'apps/companion/renderer'
);
// When: Checking renderer source for forbidden imports
const forbiddenPatterns = [
{ pattern: /from\s+['"]electron['"]/, name: 'electron' },
{ pattern: /require\(['"]electron['"]\)/, name: 'electron' },
{ pattern: /from\s+['"]fs['"]/, name: 'fs' },
{ pattern: /require\(['"]fs['"]\)/, name: 'fs' },
{ pattern: /from\s+['"]path['"]/, name: 'path' },
{ pattern: /require\(['"]path['"]\)/, name: 'path' },
];
const violations: Array<{ file: string; line: number; import: string; module: string }> = [];
function scanDirectory(dir: string) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
entries.forEach((entry: any) => {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
scanDirectory(fullPath);
} else if (entry.name.endsWith('.tsx') || entry.name.endsWith('.ts')) {
const content = fs.readFileSync(fullPath, 'utf-8');
const lines = content.split('\n');
lines.forEach((line: string, index: number) => {
forbiddenPatterns.forEach(({ pattern, name }) => {
if (pattern.test(line)) {
violations.push({
file: path.relative(process.cwd(), fullPath),
line: index + 1,
import: line.trim(),
module: name,
});
}
});
});
}
});
}
scanDirectory(rendererPath);
// Then: No Node.js modules should be imported in renderer
expect(
violations.length,
`Found Node.js module imports in renderer source code:\n\n${
violations.map(v => `${v.file}:${v.line}\n Module: ${v.module}\n Code: ${v.import}`).join('\n\n')
}\n\nRenderer code must use the preload script or IPC to access Node.js APIs.`
).toBe(0);
});
});