@wdio/electron-utils
Version:
Utilities for WebdriverIO Electron Service
258 lines (220 loc) • 8 kB
text/typescript
import fs from 'node:fs/promises';
import { normalize } from 'node:path';
import { expect, it, vi, describe, beforeEach } from 'vitest';
import { AppBuildInfo } from '@wdio/electron-types';
import log from '../src/log.js';
import { getBinaryPath } from '../src/binaryPath.js';
vi.mock('node:fs/promises', async (importActual) => {
const actual = await importActual<typeof import('node:fs/promises')>();
return {
default: {
...actual,
access: vi.fn(),
},
};
});
vi.mock('../src/log');
const pkgJSONPath = '/path/to/package.json';
const winProcess = { platform: 'win32', arch: 'x64' } as NodeJS.Process;
// Mock the current process for testing different platforms and architectures
let currentProcess = { ...winProcess };
/**
* Updates the mocked process with specified platform and architecture
* @param platform - The operating system platform (e.g., 'win32', 'darwin', 'linux')
* @param arch - The CPU architecture (e.g., 'x64', 'arm64')
*/
function mockProcess(platform: string, arch: string) {
currentProcess = { platform, arch } as NodeJS.Process;
}
/**
* Mocks the file system access to simulate executable binary paths
* @param expectedPath - The expected binary path(s) that should be accessible
*/
function mockBinaryPath(expectedPath: string | string[]) {
const target = Array.isArray(expectedPath) ? expectedPath.map((p) => normalize(p)) : [normalize(expectedPath)];
vi.mocked(fs.access).mockImplementation(async (path, _mode?) => {
if (target.includes(normalize(path.toString()))) {
return Promise.resolve();
} else {
return Promise.reject('Not executable');
}
});
}
/**
* Generates a mock AppBuildInfo object for testing
* @param isForge - Whether the app is built with Electron Forge
* @param isBuilder - Whether the app is built with electron-builder
*/
function generateAppBuildInfo(isForge: boolean, isBuilder: boolean) {
return {
appName: 'my-app',
isForge,
isBuilder,
config: { productName: 'my-app' },
} as AppBuildInfo;
}
type TestBinaryPathOptions = {
platform: string;
arch: string;
binaryPath: string;
isForge: boolean;
configObj: {
packagerConfig?: { name: string };
outDir?: string;
productName?: string;
directories?: { output?: string };
};
testName?: string;
skip?: boolean;
};
/**
* Helper function to create parameterized tests for binary path resolution
*/
function testBinaryPath(options: TestBinaryPathOptions) {
const { platform, arch, binaryPath, isForge, configObj, testName, skip } = options;
const buildType = isForge ? 'Forge' : 'Builder';
const hasCustomOutDir = configObj.outDir || (configObj.directories && configObj.directories.output);
const pkgJSONPath = '/path/to/package.json';
// Create descriptive test title based on configuration
const title =
testName ||
(hasCustomOutDir
? `should resolve binary path for ${buildType} build with custom output directory on ${platform}-${arch}`
: `should resolve binary path for ${buildType} build on ${platform}-${arch}`);
const testFn = skip ? it.skip : it;
testFn(`${title}`, async () => {
const currentProcess = { platform } as NodeJS.Process;
mockBinaryPath(binaryPath);
const result = await getBinaryPath(
pkgJSONPath,
{
appName: 'my-app',
isForge: isForge,
isBuilder: !isForge,
config: configObj,
} as AppBuildInfo,
'29.3.1',
currentProcess,
);
// Normalize path separators for cross-platform compatibility
const normalizedActual = result.replace(/\\/g, '/');
const normalizedExpected = binaryPath.replace(/\\/g, '/');
expect(normalizedActual).toBe(normalizedExpected);
});
}
/**
* Helper function to create tests specifically for Electron Forge builds
*/
function testForgeBinaryPath(options: Omit<TestBinaryPathOptions, 'isForge'>) {
testBinaryPath(Object.assign(options, { isForge: true }));
}
/**
* Helper function to create tests specifically for electron-builder builds
*/
function testBuilderBinaryPath(options: Omit<TestBinaryPathOptions, 'isForge'>) {
testBinaryPath(Object.assign(options, { isForge: false }));
}
describe('getBinaryPath', () => {
beforeEach(() => {
vi.mocked(log.info).mockClear();
});
it('should throw error for unsupported platform', async () => {
mockProcess('not-supported', 'x86');
mockBinaryPath('/path/to');
await expect(() =>
getBinaryPath(pkgJSONPath, generateAppBuildInfo(false, true), '29.3.1', currentProcess),
).rejects.toThrowError('Unsupported platform: not-supported');
});
it('should throw error for unsupported build tool configuration', async () => {
mockProcess('linux', 'arm64');
mockBinaryPath('/path/to');
await expect(() =>
getBinaryPath(pkgJSONPath, generateAppBuildInfo(false, false), '29.3.1', currentProcess),
).rejects.toThrowError('Configurations that are neither Forge nor Builder are not supported.');
});
describe('Electron Forge builds', () => {
testForgeBinaryPath({
platform: 'win32',
arch: 'x64',
binaryPath: '/path/to/out/my-app-win32-x64/my-app.exe',
configObj: { packagerConfig: { name: 'my-app' } },
});
testForgeBinaryPath({
platform: 'win32',
arch: 'x64',
binaryPath: '/path/to/custom-outdir/my-app-win32-x64/my-app.exe',
configObj: { packagerConfig: { name: 'my-app' }, outDir: 'custom-outdir' },
});
testForgeBinaryPath({
platform: 'darwin',
arch: 'arm64',
binaryPath: '/path/to/out/my-app-darwin-arm64/my-app.app/Contents/MacOS/my-app',
configObj: { packagerConfig: { name: 'my-app' } },
});
testForgeBinaryPath({
platform: 'darwin',
arch: 'x64',
binaryPath: '/path/to/out/my-app-darwin-x64/my-app.app/Contents/MacOS/my-app',
configObj: { packagerConfig: { name: 'my-app' } },
});
testForgeBinaryPath({
platform: 'linux',
arch: 'x64',
binaryPath: '/path/to/out/my-app-linux-x64/my-app',
configObj: { packagerConfig: { name: 'my-app' } },
});
});
describe('electron-builder builds', () => {
// Test all supported macOS architectures
testBuilderBinaryPath({
platform: 'darwin',
arch: 'arm64',
binaryPath: '/path/to/dist/mac-arm64/my-app.app/Contents/MacOS/my-app',
configObj: { productName: 'my-app' },
});
testBuilderBinaryPath({
platform: 'darwin',
arch: 'x64',
binaryPath: '/path/to/dist/mac/my-app.app/Contents/MacOS/my-app',
configObj: { productName: 'my-app' },
});
testBuilderBinaryPath({
platform: 'darwin',
arch: 'armv7l',
binaryPath: '/path/to/dist/mac-armv7l/my-app.app/Contents/MacOS/my-app',
configObj: { productName: 'my-app' },
});
testBuilderBinaryPath({
platform: 'darwin',
arch: 'ia32',
binaryPath: '/path/to/dist/mac-ia32/my-app.app/Contents/MacOS/my-app',
configObj: { productName: 'my-app' },
});
testBuilderBinaryPath({
platform: 'darwin',
arch: 'universal',
binaryPath: '/path/to/dist/mac-universal/my-app.app/Contents/MacOS/my-app',
configObj: { productName: 'my-app' },
});
// Test custom output directory configuration
testBuilderBinaryPath({
platform: 'darwin',
arch: 'arm64',
binaryPath: '/path/to/dist/custom/mac/my-app.app/Contents/MacOS/my-app',
configObj: { productName: 'my-app', directories: { output: 'dist/custom' } },
});
// Test Linux builds (note: all Linux architectures use the same output path)
testBuilderBinaryPath({
platform: 'linux',
arch: 'x64',
binaryPath: '/path/to/dist/linux-unpacked/my-app',
configObj: { productName: 'my-app' },
});
testBuilderBinaryPath({
platform: 'linux',
arch: 'arm64',
binaryPath: '/path/to/dist/linux-unpacked/my-app',
configObj: { productName: 'my-app' },
});
});
});