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.

273 lines 12.3 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.VSBrowser = void 0; const path = __importStar(require("path")); const fs = __importStar(require("fs-extra")); const compare_versions_1 = require("compare-versions"); const page_objects_1 = require("@redhat-developer/page-objects"); const chrome_1 = require("selenium-webdriver/chrome"); const locators_1 = require("@redhat-developer/locators"); const codeUtil_1 = require("./util/codeUtil"); const extester_1 = require("./extester"); const driverUtil_1 = require("./util/driverUtil"); class VSBrowser { static baseVersion = '1.37.0'; static browserName = 'vscode'; storagePath; extensionsFolder; customSettings; _driver; codeVersion; releaseType; logLevel; static _instance; _startTimestamp; formatTimestamp(date) { const pad = (num) => num.toString().padStart(2, '0'); return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}T${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`; } constructor(codeVersion, releaseType, customSettings = {}, logLevel = page_objects_1.logging.Level.INFO) { this.storagePath = process.env.TEST_RESOURCES ? process.env.TEST_RESOURCES : path.resolve(extester_1.DEFAULT_STORAGE_FOLDER); this.extensionsFolder = process.env.EXTENSIONS_FOLDER ? process.env.EXTENSIONS_FOLDER : undefined; this.customSettings = customSettings; this.codeVersion = codeVersion; this.releaseType = releaseType; this.logLevel = logLevel; this._startTimestamp = this.formatTimestamp(new Date()); VSBrowser._instance = this; } /** * Starts the vscode browser from a given path * @param codePath path to code binary */ async start(codePath) { const userSettings = path.join(this.storagePath, 'settings', 'User'); if (fs.existsSync(userSettings)) { fs.removeSync(path.join(this.storagePath, 'settings')); } let defaultSettings = { 'workbench.editor.enablePreview': false, 'workbench.startupEditor': 'none', 'window.titleBarStyle': 'custom', 'window.commandCenter': false, 'window.dialogStyle': 'custom', 'window.restoreFullscreen': true, 'window.newWindowDimensions': 'maximized', 'security.workspace.trust.enabled': false, 'files.simpleDialog.enable': true, 'terminal.integrated.copyOnSelection': true, ...((0, compare_versions_1.satisfies)(this.codeVersion, '>=1.101.0') ? { 'window.menuStyle': 'custom' } : {}), }; if (Object.keys(this.customSettings).length > 0) { console.log('Detected user defined code settings'); defaultSettings = { ...defaultSettings, ...this.customSettings }; } fs.mkdirpSync(path.join(userSettings, 'globalStorage')); fs.writeJSONSync(path.join(userSettings, 'settings.json'), defaultSettings); console.log(`Writing code settings to ${path.join(userSettings, 'settings.json')}`); const args = ['--no-sandbox', '--disable-dev-shm-usage', `--user-data-dir=${path.join(this.storagePath, 'settings')}`]; if (this.extensionsFolder) { args.push(`--extensions-dir=${this.extensionsFolder}`); } if ((0, compare_versions_1.satisfies)(this.codeVersion, '<1.39.0')) { if (process.platform === 'win32') { fs.copyFileSync(path.resolve(__dirname, '..', '..', 'resources', 'state.vscdb'), path.join(userSettings, 'globalStorage', 'state.vscdb')); } args.push(`--extensionDevelopmentPath=${process.cwd()}`); } else if (process.env.EXTENSION_DEV_PATH) { args.push(`--extensionDevelopmentPath=${process.env.EXTENSION_DEV_PATH}`); } let options = new chrome_1.Options().setChromeBinaryPath(codePath).addArguments(...args); options['options_'].windowTypes = ['webview']; options = options; const prefs = new page_objects_1.logging.Preferences(); prefs.setLevel(page_objects_1.logging.Type.DRIVER, this.logLevel); options.setLoggingPrefs(prefs); const driverBinary = process.platform === 'win32' ? 'chromedriver.exe' : 'chromedriver'; let chromeDriverBinaryPath = path.join(this.storagePath, driverBinary); if ((0, compare_versions_1.satisfies)(this.codeVersion, '>=1.86.0')) { chromeDriverBinaryPath = path.join(this.storagePath, `chromedriver-${driverUtil_1.DriverUtil.getChromeDriverPlatform()}`, driverBinary); } console.log('Launching browser...'); this._driver = await new page_objects_1.Builder() .setChromeService(new chrome_1.ServiceBuilder(chromeDriverBinaryPath)) .forBrowser(page_objects_1.Browser.CHROME) .setChromeOptions(options) .build(); VSBrowser._instance = this; (0, page_objects_1.initPageObjects)(this.codeVersion, VSBrowser.baseVersion, (0, locators_1.getLocatorsPath)(), this._driver, VSBrowser.browserName); return this; } /** * Returns a reference to the underlying instance of Webdriver */ get driver() { return this._driver; } /** * Returns the vscode version as string */ get version() { return this.codeVersion; } /** * Returns an instance of VSBrowser */ static get instance() { return VSBrowser._instance; } /** * Waits for the VS Code workbench UI to be fully loaded and optionally performs * an additional async or sync check after the workbench appears. * * This method waits for the presence of the `.monaco-workbench` element within the specified timeout. * If a WebDriver error occurs (e.g. flaky startup), it retries after a short delay. * Additionally, a follow-up function (`waitForFn`) can be passed to perform custom * readiness checks (e.g. for UI elements, extensions, or custom content). * * @param timeout - Maximum time in milliseconds to wait for the workbench to appear (default: 30,000 ms). * @param waitForFn - Optional function (sync or async) to be executed after the workbench is located. * * @throws If the workbench is not found in time and no recoverable WebDriver error occurred. * * @example * // Wait for the workbench with default timeout * await waitForWorkbench(); * * @example * // Wait for the workbench and ensure a custom UI element is present * await waitForWorkbench(10000, async () => { * await driver.wait(until.elementLocated(By.id('my-element')), 5000); * }); */ async waitForWorkbench(timeout = 30_000, waitForFn) { // Workaround/patch for https://github.com/redhat-developer/vscode-extension-tester/issues/466 try { await this._driver.wait(page_objects_1.until.elementLocated(page_objects_1.By.className('monaco-workbench')), timeout, `Workbench was not loaded properly after ${timeout} ms.`); } catch (err) { if (err.name === 'WebDriverError') { await new Promise((res) => setTimeout(res, 3_000)); } else { throw err; } } if (waitForFn) { await waitForFn(); } } /** * Terminates the webdriver/browser */ async quit() { const entries = await this._driver.manage().logs().get(page_objects_1.logging.Type.DRIVER); const logFile = path.join(this.storagePath, 'test.log'); const stream = fs.createWriteStream(logFile, { flags: 'w' }); entries.forEach((entry) => { stream.write(`[${new Date(entry.timestamp).toLocaleTimeString()}][${entry.level.name}] ${entry.message}`); }); stream.end(); console.log('Shutting down the browser'); await this._driver.quit(); } /** * Take a screenshot of the browser * @param name file name of the screenshot without extension */ async takeScreenshot(name) { const data = await this._driver.takeScreenshot(); const dir = path.join(this.storagePath, 'screenshots', this._startTimestamp); fs.mkdirpSync(dir); fs.writeFileSync(path.join(dir, `${name}.png`), data, 'base64'); } /** * Get a screenshots folder path * @returns string path to the screenshots folder */ getScreenshotsDir() { return path.join(this.storagePath, 'screenshots', this._startTimestamp); } /** * Opens one or more resources in the editor and optionally performs a follow-up action. * * This method accepts a variable number of arguments. All string arguments are interpreted * as resource paths to be opened. Optionally, a single callback function (synchronous or asynchronous) * can be provided as the last argument. This callback will be invoked after all resources have been opened. * * @param args - A list of file paths to open followed optionally by a callback function. * The callback can be either synchronous or asynchronous. * * @example * // Open two files * await openResources('file1.ts', 'file2.ts'); * * @example * // Open one file and then wait for a condition * await openResources('file1.ts', async () => { * await waitForElementToLoad(); * }); */ async openResources(...args) { const paths = args.filter((arg) => typeof arg === 'string'); const waitForFn = args.find((arg) => typeof arg === 'function'); if (paths.length === 0) { return; } const code = new codeUtil_1.CodeUtil(this.storagePath, this.releaseType, this.extensionsFolder); code.open(...paths); await this.waitForWorkbench(undefined, waitForFn); } } exports.VSBrowser = VSBrowser; //# sourceMappingURL=browser.js.map