UNPKG

electron-playwright-helpers

Version:

Helper functions for Electron end-to-end testing using Playwright

356 lines 14.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.parseElectronApp = exports.findLatestBuild = void 0; const path_1 = __importDefault(require("path")); const fs_1 = __importDefault(require("fs")); const ASAR = __importStar(require("@electron/asar")); /** * Parses the `out` directory to find the latest build of your Electron project. * Use `npm run package` (or similar) to build your app prior to testing. * * Assumptions: We assume that your build will be in the `out` directory, and that * the build directory will be named with a hyphen-delimited platform name, e.g. * `out/my-app-win-x64`. If your build directory is not `out`, you can * pass the name of the directory as the `buildDirectory` parameter. If your * build directory is not named with a hyphen-delimited platform name, this * function will not work. However, you can pass the build path into * `parseElectronApp()` directly. * * @see parseElectronApp * * @param buildDirectory {string} - optional - the directory to search for the latest build * (path/name relative to package root or full path starting with /). Defaults to `out`. * @returns {string} - path to the most recently modified build directory */ function findLatestBuild(buildDirectory = 'out') { // root of your project const rootDir = path_1.default.resolve('./'); // directory where the builds are stored const outDir = path_1.default.resolve(rootDir, buildDirectory); // list of files in the out directory const builds = fs_1.default.readdirSync(outDir); const platforms = [ 'win32', 'win', 'windows', 'darwin', 'mac', 'macos', 'osx', 'linux', 'ubuntu', 'debian', ]; const latestBuild = builds .map((fileName) => { // make sure it's a directory with "-" delimited platform in its name const stats = fs_1.default.statSync(path_1.default.join(outDir, fileName)); const isBuild = fileName .toLocaleLowerCase() .split('-') .some((part) => platforms.includes(part)); if (stats.isDirectory() && isBuild) { return { name: fileName, time: fs_1.default.statSync(path_1.default.join(outDir, fileName)).mtimeMs, }; } }) .sort((a, b) => { const aTime = a ? a.time : 0; const bTime = b ? b.time : 0; return bTime - aTime; }) .map((file) => { if (file) { return file.name; } })[0]; if (!latestBuild) { throw new Error('No build found in out directory'); } return path_1.default.join(outDir, latestBuild); } exports.findLatestBuild = findLatestBuild; /** * Given a directory containing an Electron app build, * or the path to the app itself (directory on Mac, executable on Windows), * return a bunch of metadata, including the path to the app's executable * and the path to the app's main file. * * Format of the data returned is an object with the following properties: * - executable: path to the app's executable file * - main: path to the app's main (JS) file * - name: name of the app * - resourcesDir: path to the app's resources directory * - asar: true if the app is using asar * - platform: OS platform * - arch: architecture * - packageJson: the JSON.parse()'d contents of the package.json file. * * @param buildDir {string} - absolute path to the build directory or the app itself * @returns {ElectronAppInfo} metadata about the app */ function parseElectronApp(buildDir) { // The platform of the app let platform; // in case the buildDir is the path to the app itself if (buildDir.endsWith('.app')) { buildDir = path_1.default.dirname(buildDir); platform = 'darwin'; } else if (buildDir.endsWith('.exe')) { buildDir = path_1.default.dirname(buildDir); platform = 'win32'; } else { // equivalent for Linux? } // The name of the build directory CONVERTED TO LOWERCASE const baseNameLc = path_1.default.basename(buildDir).toLowerCase(); if (!platform) { // parse the directory name to figure out the platform if (baseNameLc.includes('win')) { platform = 'win32'; } if (baseNameLc.includes('linux') || baseNameLc.includes('ubuntu') || baseNameLc.includes('debian')) { platform = 'linux'; } if (baseNameLc.includes('darwin') || baseNameLc.includes('mac') || baseNameLc.includes('osx')) { platform = 'darwin'; } } if (!platform) { throw new Error(`Platform not found in directory name: ${baseNameLc}`); } let arch; if (baseNameLc.includes('x32') || baseNameLc.includes('i386')) { arch = 'x32'; } if (baseNameLc.includes('x64')) { arch = 'x64'; } if (baseNameLc.includes('arm64')) { arch = 'arm64'; } if (baseNameLc.includes('universal')) { arch = 'universal'; } if (!arch) { // we still haven't figured out architecture // let's get a little more desperate if (baseNameLc.includes('x86')) { arch = 'x32'; } else if (baseNameLc.includes('arm')) { arch = 'arm64'; } else if (baseNameLc.includes('univ')) { arch = 'universal'; } } let executable; let main; let name; let asar; let resourcesDir; let packageJson; if (platform === 'darwin') { // MacOS Structure // <buildDir>/ // <appName>.app/ // Contents/ // MacOS/ // <appName> (executable) // Info.plist // PkgInfo // Resources/ // electron.icns // file.icns // app.asar (asar bundle) - or - // app // package.json // (your app structure) const list = fs_1.default.readdirSync(buildDir); const appBundle = list.find((fileName) => { return fileName.endsWith('.app'); }); if (!appBundle) { throw new Error(`Could not find app bundle in ${buildDir}`); } const appDir = path_1.default.join(buildDir, appBundle, 'Contents', 'MacOS'); const appName = fs_1.default.readdirSync(appDir)[0]; executable = path_1.default.join(appDir, appName); resourcesDir = path_1.default.join(buildDir, appBundle, 'Contents', 'Resources'); const resourcesList = fs_1.default.readdirSync(resourcesDir); asar = resourcesList.includes('app.asar'); if (asar) { const asarPath = path_1.default.join(resourcesDir, 'app.asar'); packageJson = JSON.parse(ASAR.extractFile(asarPath, 'package.json').toString('utf8')); main = path_1.default.join(asarPath, packageJson.main); } else { packageJson = JSON.parse(fs_1.default.readFileSync(path_1.default.join(resourcesDir, 'app', 'package.json'), 'utf8')); main = path_1.default.join(resourcesDir, 'app', packageJson.main); } name = packageJson.name; } else if (platform === 'win32') { // Windows Structure // <buildDir>/ // <appName>.exe (executable) // resources/ // app.asar (asar bundle) - or - // app // package.json // (your app structure) const list = fs_1.default.readdirSync(buildDir); // !! assume the executable is the only .exe file in the directory const exe = list.find((fileName) => { return fileName.endsWith('.exe'); }); if (!exe) { throw new Error(`Could not find executable in ${buildDir}`); } executable = path_1.default.join(buildDir, exe); resourcesDir = path_1.default.join(buildDir, 'resources'); const resourcesList = fs_1.default.readdirSync(resourcesDir); asar = resourcesList.includes('app.asar'); if (asar) { const asarPath = path_1.default.join(resourcesDir, 'app.asar'); packageJson = JSON.parse(ASAR.extractFile(asarPath, 'package.json').toString('utf8')); main = path_1.default.join(asarPath, packageJson.main); } else { packageJson = JSON.parse(fs_1.default.readFileSync(path_1.default.join(resourcesDir, 'app', 'package.json'), 'utf8')); main = path_1.default.join(resourcesDir, 'app', packageJson.main); } name = packageJson.name; } else if (platform === 'linux') { // Linux Structure // <buildDir>/ // <appName> (executable) // resources/ // app.asar (asar bundle) - or - // app --- (untested - we're making assumptions here) // package.json // (your app structure) const list = fs_1.default.readdirSync(buildDir); const exeCandidates = list.filter((fileName) => { // Assume the executable is the only file in the directory that... // ...does not have one of these suffixes const ignoreSuffixes = [ '.so', '.so.1', '.so.2', '.bin', '.pak', '.dat', '.json', ]; // ...does not have one of these names const ignoreNames = ['resources', 'locales', 'version', 'LICENSE']; // ...does not start with one of these names const ignoreStartsWith = ['chrome-', 'chrome_', 'lib', 'LICENSE']; if (ignoreSuffixes.some((suffix) => fileName.endsWith(suffix))) { return false; } if (ignoreNames.some((name) => fileName === name)) { return false; } if (ignoreStartsWith.some((name) => fileName.startsWith(name))) { return false; } const filePath = path_1.default.join(buildDir, fileName); const stats = fs_1.default.statSync(filePath); // ...is not a directory if (stats.isDirectory()) { return false; } // ...is not a symlink if (stats.isSymbolicLink()) { return false; } // ...is executable try { fs_1.default.accessSync(filePath, fs_1.default.constants.X_OK); return true; } catch (err) { return false; } }); if (exeCandidates.length > 1) { console.warn(`Found ${exeCandidates.length} executable files in ${buildDir}. Will use the first: ${exeCandidates[0]}. If this is not the correct executable, please file an issue at https://github.com/spaceagetv/electron-playwright-helpers/issues`); } if (exeCandidates.length < 1) { throw new Error(`Could not find executable file in ${buildDir}. Please check your build directory. If file exists, please make sure it is executable. If file is executable, please file an issue at https://github.com/spaceagetv/electron-playwright-helpers/issues`); } executable = path_1.default.join(buildDir, exeCandidates[0]); resourcesDir = path_1.default.join(buildDir, 'resources'); const resourcesList = fs_1.default.readdirSync(resourcesDir); asar = resourcesList.includes('app.asar'); if (asar) { const asarPath = path_1.default.join(resourcesDir, 'app.asar'); packageJson = JSON.parse(ASAR.extractFile(asarPath, 'package.json').toString('utf8')); main = path_1.default.join(asarPath, packageJson.main); } else { try { packageJson = JSON.parse(fs_1.default.readFileSync(path_1.default.join(resourcesDir, 'app', 'package.json'), 'utf8')); main = path_1.default.join(resourcesDir, 'app', packageJson.main); } catch (err) { throw new Error(`Could not find package.json in ${resourcesDir}. Apparently we don't quite know how Electron works on Linux yet. Please submit a bug report or pull request!`); } } // get the name field from package.json name = packageJson.name; } else { throw new Error(`Platform not supported: ${platform}`); } return { executable, main, asar, name, platform, resourcesDir, arch, packageJson, }; } exports.parseElectronApp = parseElectronApp; //# sourceMappingURL=find_parse_builds.js.map