UNPKG

vscode-extension-tester

Version:

ExTester is a package that is designed to help you run UI tests for your Visual Studio Code extensions using selenium-webdriver.

497 lines 20 kB
"use strict"; /** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License", destination); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 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 () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.CodeUtil = exports.DEFAULT_RUN_OPTIONS = exports.ReleaseQuality = void 0; const childProcess = __importStar(require("child_process")); const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); const vsce = __importStar(require("@vscode/vsce")); const runner_1 = require("../suite/runner"); const unpack_1 = require("./unpack"); const selenium_webdriver_1 = require("selenium-webdriver"); const download_1 = require("./download"); const extester_1 = require("../extester"); const compare_versions_1 = require("compare-versions"); var ReleaseQuality; (function (ReleaseQuality) { ReleaseQuality["Stable"] = "stable"; ReleaseQuality["Insider"] = "insider"; })(ReleaseQuality || (exports.ReleaseQuality = ReleaseQuality = {})); /** defaults for the [[RunOptions]] */ exports.DEFAULT_RUN_OPTIONS = { vscodeVersion: 'latest', settings: '', logLevel: selenium_webdriver_1.logging.Level.INFO, offline: false, resources: [], noCache: false, }; /** * Handles the VS Code instance used for testing. * Includes downloading, unpacking, launching, and version checks. */ class CodeUtil { codeFolder; downloadPlatform; downloadFolder; releaseType; executablePath; cliPath; cliEnv; availableVersions; extensionsFolder; coverage; env = { ...process.env }; /** * Create an instance of code handler * @param folder Path to folder where all the artifacts will be stored. * @param extensionsFolder Path to use as extensions directory by VS Code */ constructor(folder = extester_1.DEFAULT_STORAGE_FOLDER, type = ReleaseQuality.Stable, extensionsFolder, coverage) { this.availableVersions = []; this.downloadPlatform = this.getPlatform(); this.downloadFolder = path.resolve(folder); this.extensionsFolder = extensionsFolder ? path.resolve(extensionsFolder) : undefined; this.coverage = coverage; this.releaseType = type; if (type === ReleaseQuality.Stable) { this.codeFolder = path.join(this.downloadFolder, process.platform === 'darwin' ? 'Visual Studio Code.app' : `VSCode-${this.downloadPlatform}`); } else { this.codeFolder = path.join(this.downloadFolder, process.platform === 'darwin' ? 'Visual Studio Code - Insiders.app' : `VSCode-${this.downloadPlatform}-insider`); } this.findExecutables(); // remove unsafe env variables from current process to avoid spam messages like: // Node.js environment variables are disabled because this process is invoked by other apps. // See https://github.com/microsoft/vscode/issues/204005 for (const key in this.env) { if (key.startsWith('NODE_')) { delete this.env[key]; } } } /** * Get all versions for the given release stream */ async getVSCodeVersions() { const apiUrl = `https://update.code.visualstudio.com/api/releases/${this.releaseType}`; const json = await download_1.Download.getText(apiUrl); return json; } /** * Download and unpack VS Code for testing purposes * * @param version VS Code version to get, default latest * @param noCache whether to skip using cached version */ async downloadVSCode(version = 'latest', noCache = false) { await this.checkCodeVersion(version); const literalVersion = version === 'latest' ? this.availableVersions[0] : version; if (this.releaseType === ReleaseQuality.Stable && literalVersion !== this.availableVersions[0]) { console.log('\x1b[33m%s\x1b[0m', `\n\nWARNING: You are using the outdated VS Code version '${literalVersion}'. The latest stable version is '${this.availableVersions[0]}'.\n\n`); } console.log(`Downloading VS Code: ${literalVersion} / ${this.releaseType}`); if (!fs.existsSync(this.executablePath) || this.getExistingCodeVersion() !== literalVersion || noCache) { fs.mkdirpSync(this.downloadFolder); const url = ['https://update.code.visualstudio.com', version, this.downloadPlatform, this.releaseType].join('/'); const isTarGz = this.downloadPlatform.indexOf('linux') > -1; const fileExtension = isTarGz ? 'tar.gz' : 'zip'; let versionPart = ''; if (this.releaseType === ReleaseQuality.Stable) { versionPart = `-${this.releaseType}`; } let fileName; if (noCache) { fileName = this.releaseType + '.' + fileExtension; } else { fileName = literalVersion + versionPart + '.' + fileExtension; } const zipPath = path.join(this.downloadFolder, fileName); if (!noCache && fs.existsSync(zipPath)) { console.log(`VS Code archive ${fileName} already exists in storage folder, skipping download`); } else { console.log(`Downloading VS Code from: ${url}`); await download_1.Download.getFile(url, zipPath, true); console.log(`Downloaded VS Code into ${zipPath}`); } const tempPrefix = path.join(this.downloadFolder, 'vscode-temp-'); console.log(`Unpacking VS Code into ${this.downloadFolder}`); const target = await fs.mkdtemp(tempPrefix); try { await unpack_1.Unpack.unpack(zipPath, target); let rootDir = target; const files = await fs.readdir(target); if (files.length === 1) { rootDir = path.join(target, files[0]); } await fs.move(rootDir, this.codeFolder, { overwrite: true }); console.log('Success!'); if (noCache) { await fs.remove(zipPath); console.log('Removed downloaded archive as --no_cache is active'); } } finally { await fs.remove(target); } } else { console.log('VS Code exists in local cache, skipping download'); } } /** * Install your extension into the test instance of VS Code */ installExtension(vsix, id, preRelease) { const pjson = require(path.resolve('package.json')); if (id) { return this.installExt(id, preRelease); } const vsixPath = path.resolve(vsix ? vsix : `${pjson.name}-${pjson.version}.vsix`); this.installExt(vsixPath); } /** * Install extension dependencies from marketplace */ installDependencies() { const pjson = require(path.resolve('package.json')); const deps = pjson.extensionDependencies; if (!deps) { return; } for (const id of deps) { this.installExt(id); } } getCliInitCommand() { const cli = `${this.cliEnv} "${this.executablePath}" "${this.cliPath}"`; if ((0, compare_versions_1.satisfies)(this.getExistingCodeVersion(), '>=1.86.0')) { return cli; } else { return `${cli} --ms-enable-electron-run-as-node`; } } installExt(pathOrID, preRelease) { let command = `${this.getCliInitCommand()} --force --install-extension "${pathOrID}"`; if (preRelease) { command += ' --pre-release'; } if (this.extensionsFolder) { command += ` --extensions-dir=${this.extensionsFolder}`; } childProcess.execSync(command, { stdio: 'inherit' }); } /** * Open files/folders in running vscode * @param paths vararg paths to files or folders to open */ open(...paths) { const segments = paths.map((f) => `"${f}"`).join(' '); const command = `${this.getCliInitCommand()} -r ${segments} --user-data-dir="${path.join(this.downloadFolder, 'settings')}"`; childProcess.execSync(command); } /** * Download a vsix file * @param vsixURL URL of the vsix file */ async downloadExtension(vsixURL) { fs.mkdirpSync(this.downloadFolder); const fileName = path.basename(vsixURL); const target = path.join(this.downloadFolder, fileName); if (!fileName.endsWith('.vsix')) { throw new Error('The URL does not point to a vsix file'); } console.log(`Downloading ${fileName}`); await download_1.Download.getFile(vsixURL, target); console.log('Success!'); return target; } /** * Package extension into a vsix file * @param useYarn false to use npm as packaging system, true to use yarn instead */ async packageExtension(useYarn) { await vsce.createVSIX({ useYarn, }); } /** * Uninstall the tested extension from the test instance of VS Code * * @param cleanup remove the extension's directory as well. */ uninstallExtension(cleanup) { const pjson = require(path.resolve('package.json')); const extension = `${pjson.publisher}.${pjson.name}`; if (cleanup) { let command = `${this.getCliInitCommand()} --uninstall-extension "${extension}"`; if (this.extensionsFolder) { command += ` --extensions-dir=${this.extensionsFolder}`; } childProcess.execSync(command, { stdio: 'inherit' }); } } /** * Run tests in your test environment using mocha * * @param testFilesPattern glob pattern of test files to run * @param runOptions additional options for customizing the test run * * @return The exit code of the mocha process */ async runTests(testFilesPattern, runOptions = exports.DEFAULT_RUN_OPTIONS) { if (!runOptions.offline) { await this.checkCodeVersion(runOptions.vscodeVersion ?? exports.DEFAULT_RUN_OPTIONS.vscodeVersion); } else { this.availableVersions = [this.getExistingCodeVersion()]; } const literalVersion = runOptions.vscodeVersion === undefined || runOptions.vscodeVersion === 'latest' ? this.availableVersions[0] : runOptions.vscodeVersion; // add chromedriver to process' path const finalEnv = {}; Object.assign(finalEnv, process.env); const key = 'PATH'; finalEnv[key] = [this.downloadFolder, process.env[key]].join(path.delimiter); process.env = finalEnv; process.env.TEST_RESOURCES = this.downloadFolder; process.env.EXTENSIONS_FOLDER = this.extensionsFolder; process.env.EXTENSION_DEV_PATH = this.coverage ? process.cwd() : undefined; const runner = new runner_1.VSRunner(this.executablePath, literalVersion, this.parseSettings(runOptions.settings ?? exports.DEFAULT_RUN_OPTIONS.settings), runOptions.cleanup, this.releaseType, runOptions.config); return await runner.runTests(testFilesPattern, this, runOptions.logLevel, runOptions.resources); } /** * Finds the version of chromium used for given VS Code version. * Works only for versions 1.30+, older versions need to be checked manually * * @param codeVersion version of VS Code, default latest * @param quality release stream, default stable */ async getChromiumVersion(codeVersion = 'latest') { await this.checkCodeVersion(codeVersion); const literalVersion = codeVersion === 'latest' ? this.availableVersions[0] : codeVersion; let revision = literalVersion; if (literalVersion.endsWith('-insider')) { if (codeVersion === 'latest') { revision = 'main'; } else { revision = literalVersion.substring(0, literalVersion.indexOf('-insider')); revision = `release/${revision.substring(0, revision.lastIndexOf('.'))}`; } } else { revision = `release/${revision.substring(0, revision.lastIndexOf('.'))}`; } const fileName = 'manifest.json'; const url = `https://raw.githubusercontent.com/Microsoft/vscode/${revision}/cgmanifest.json`; await download_1.Download.getFile(url, path.join(this.downloadFolder, fileName)); try { const manifest = require(path.join(this.downloadFolder, fileName)); return manifest.registrations[0].version; } catch (err) { let version = ''; if (await fs.pathExists(this.codeFolder)) { version = this.getChromiumVersionOffline(); } if (version === '') { throw new Error('Unable to determine required ChromeDriver version'); } return version; } } /** * Check if VS Code exists in local cache along with an appropriate version of chromedriver * without internet connection */ checkOfflineRequirements() { try { this.getExistingCodeVersion(); } catch (err) { console.log('ERROR: Cannot find a local copy of VS Code in offline mode, exiting.'); throw err; } return this.getChromiumVersionOffline(); } /** * Attempt to get chromium version from a downloaded copy of vs code */ getChromiumVersionOffline() { const manifestPath = path.join(this.codeFolder, 'resources', 'app', 'ThirdPartyNotices.txt'); const text = fs.readFileSync(manifestPath).toString(); const matches = text.match(/chromium\sversion\s(.*)\s\(/); if (matches && matches[1]) { return matches[1]; } return ''; } /** * Get the root folder of VS Code instance */ getCodeFolder() { return this.codeFolder; } /** * Getter for coverage enablement option */ get coverageEnabled() { return this.coverage; } /** * Check if given version is available in the given stream */ async checkCodeVersion(vscodeVersion) { if (this.availableVersions.length < 1) { this.availableVersions = await this.getVSCodeVersions(); } if (vscodeVersion !== 'latest' && this.availableVersions.indexOf(vscodeVersion) < 0) { throw new Error(`Version ${vscodeVersion} is not available in ${this.releaseType} stream`); } } /** * Check what VS Code version is present in the testing folder */ getExistingCodeVersion() { const command = `${this.cliEnv} "${this.executablePath}" "${this.cliPath}"`; let out; try { out = childProcess.execSync(`${command} -v`, { env: this.env }); } catch (error) { out = childProcess.execSync(`${command} --ms-enable-electron-run-as-node -v`, { env: this.env }); } return out.toString().split('\n')[0]; } /** * Construct the platform string based on OS */ getPlatform() { let platform = process.platform; const arch = process.arch; this.cliEnv = 'ELECTRON_RUN_AS_NODE=1'; if (platform === 'linux') { platform += arch === 'ia32' ? '-ia32' : `-${arch}`; } else if (platform === 'win32') { switch (arch) { case 'arm64': { platform += '-arm64'; break; } case 'x64': { platform += '-x64'; break; } default: { throw new Error(`Unknown Platform: ${arch}`); } } platform += '-archive'; this.cliEnv = `set ${this.cliEnv} &&`; } else if (platform === 'darwin') { platform += '-universal'; } return platform; } /** * Setup paths specific to used OS */ findExecutables() { this.cliPath = path.join(this.codeFolder, 'resources', 'app', 'out', 'cli.js'); switch (process.platform) { case 'darwin': this.executablePath = path.join(this.codeFolder, 'Contents', 'MacOS', 'Electron'); this.cliPath = path.join(this.codeFolder, 'Contents', 'Resources', 'app', 'out', 'cli.js'); break; case 'win32': this.executablePath = path.join(this.codeFolder, 'Code.exe'); if (this.releaseType === ReleaseQuality.Insider) { this.executablePath = path.join(this.codeFolder, 'Code - Insiders.exe'); } break; case 'linux': this.executablePath = path.join(this.codeFolder, 'code'); if (this.releaseType === ReleaseQuality.Insider) { this.executablePath = path.join(this.codeFolder, 'code-insiders'); } break; } } /** * Parse JSON from a file * @param path path to json file */ parseSettings(path) { if (!path) { return {}; } let text = ''; try { text = fs.readFileSync(path).toString(); } catch (err) { throw new Error(`Unable to read settings from ${path}:\n ${err}`); } try { return JSON.parse(text); } catch (err) { throw new Error(`Error parsing the settings file from ${path}:\n ${err}`); } } } exports.CodeUtil = CodeUtil; //# sourceMappingURL=codeUtil.js.map