@salesforce/salesforcedx-vscode-test-tools
Version:
Test automation framework for Salesforce Extensions for VS Code
764 lines • 38.1 kB
JavaScript
;
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 });
const vscode_extension_tester_1 = require("vscode-extension-tester");
const environmentSettings_1 = require("./environmentSettings");
const path_1 = __importDefault(require("path"));
const promises_1 = __importDefault(require("fs/promises"));
const extensionUtils_1 = require("./testing/extensionUtils");
const yargs_1 = __importDefault(require("yargs"));
const helpers_1 = require("yargs/helpers");
const codeUtil_1 = require("vscode-extension-tester/out/util/codeUtil");
const chai_1 = require("chai");
const console_1 = require("console");
const cliCommands_1 = require("./system-operations/cliCommands");
const system_operations_1 = require("./system-operations");
const helpers_2 = require("./core/helpers");
const authorization_1 = require("./salesforce-components/authorization");
const retryUtils_1 = require("./retryUtils");
// Set Ubuntu Chrome arguments immediately when module loads
if (process.platform === 'linux') {
const uniqueId = `${Date.now()}-${process.pid}`;
const tempDir = path_1.default.join(process.cwd(), 'test-resources', 'chrome-user-data', uniqueId);
const ubuntuChromeArgs = [
`--user-data-dir=${tempDir}`,
'--no-sandbox',
'--disable-dev-shm-usage',
'--disable-web-security',
'--disable-features=VizDisplayCompositor',
'--disable-gpu',
'--disable-software-rasterizer',
'--disable-background-timer-throttling',
'--disable-backgrounding-occluded-windows',
'--disable-renderer-backgrounding',
'--disable-field-trial-config',
'--disable-ipc-flooding-protection',
'--single-process',
'--no-zygote'
].join(' ');
process.env.VSCODE_EXTENSION_TESTER_CHROMEDRIVER_ARGS = ubuntuChromeArgs;
process.env.CHROME_OPTIONS = ubuntuChromeArgs;
process.env.CHROMIUM_FLAGS = ubuntuChromeArgs;
(0, console_1.log)(`Early Ubuntu Chrome setup: ${ubuntuChromeArgs}`);
}
// Set Chrome-related environment variables early
process.env.CHROME_OPTIONS = '--no-sandbox --disable-dev-shm-usage --disable-gpu --remote-debugging-port=9222 --disable-web-security --disable-features=VizDisplayCompositor --single-process --no-zygote --disable-background-timer-throttling --disable-backgrounding-occluded-windows --disable-renderer-backgrounding --user-data-dir=/tmp/chrome-user-data-' + Date.now();
process.env.CHROMIUM_FLAGS = '--no-sandbox --disable-dev-shm-usage --disable-gpu --remote-debugging-port=9222 --disable-web-security --disable-features=VizDisplayCompositor --single-process --no-zygote --disable-background-timer-throttling --disable-backgrounding-occluded-windows --disable-renderer-backgrounding --user-data-dir=/tmp/chrome-user-data-' + Date.now();
process.env.CHROME_BIN = '/usr/bin/google-chrome';
process.env.CHROMIUM_BIN = '/usr/bin/google-chrome';
// Skip Husky installation during test setup
process.env.HUSKY_SKIP_INSTALL = '1';
// Set NPM to use legacy peer dependencies
process.env.NPM_CONFIG_LEGACY_PEER_DEPS = 'true';
// For Ubuntu CI environments
if (process.platform === 'linux') {
// Set up virtual display for headless operation
process.env.DISPLAY = ':99';
process.env.CHROME_NO_SANDBOX = 'true';
process.env.PUPPETEER_SKIP_CHROMIUM_DOWNLOAD = 'true';
process.env.PUPPETEER_EXECUTABLE_PATH = '/usr/bin/google-chrome';
// Additional Chrome flags for CI
process.env.CHROME_DEVEL_SANDBOX = '/usr/lib/chromium-browser/chrome-sandbox';
}
class TestSetupAndRunner extends vscode_extension_tester_1.ExTester {
static _exTestor;
testConfig;
spec;
static MAX_RETRIES = 3;
static RETRY_DELAY = 5000;
constructor(testConfig, spec) {
(0, console_1.log)(`Init testConfig with testConfig: ${JSON.stringify(testConfig)}`);
(0, console_1.log)(`Init testConfig with spec: ${spec}`);
// Create config with defaults and overrides
const config = (0, helpers_2.createDefaultTestConfig)(testConfig);
// Validate config and set defaults for missing values
(0, helpers_2.validateTestConfig)(config);
super(config?.testResources, codeUtil_1.ReleaseQuality.Stable);
this.testConfig = config;
this.spec = spec;
}
/**
* Helper method to log important test information using standard terminology
*/
logTestEnvironment() {
(0, console_1.log)(`Setting up test environment with:`);
(0, console_1.log)(`- Test Resources: ${this.testConfig.testResources}`);
(0, console_1.log)(`- Extensions Folder: ${this.testConfig.extensionsFolder}`);
(0, console_1.log)(`- VS Code Version: ${this.testConfig.codeVersion}`);
// Log Chrome driver arguments if available
const chromeDriverArgs = environmentSettings_1.EnvironmentSettings.getInstance().chromeDriverArgs;
if (chromeDriverArgs) {
(0, console_1.log)(`- ChromeDriver Arguments: ${chromeDriverArgs}`);
}
// Log the throttle factor
const throttleFactor = environmentSettings_1.EnvironmentSettings.getInstance().throttleFactor;
(0, console_1.log)(`- Throttle Factor: ${throttleFactor}`);
}
/**
* Sets up Ubuntu-specific Chrome driver arguments to avoid user data directory conflicts
*/
async setupUbuntuChromeArgs() {
// Only apply Ubuntu-specific settings if we're on Linux and no custom args are already set
if (process.platform !== 'linux') {
console.log(`Not on Linux, skipping setupUbuntuChromeArgs: ${process.platform}`);
return;
}
const existingArgs = environmentSettings_1.EnvironmentSettings.getInstance().chromeDriverArgs;
if (existingArgs) {
(0, console_1.log)(`Chrome driver arguments already set: ${existingArgs}`);
return;
}
// Generate a unique user data directory for this test run
const uniqueId = `${Date.now()}-${process.pid}`;
const tempDir = path_1.default.join(this.testConfig.testResources || 'test-resources', 'chrome-user-data', uniqueId);
// Ensure the directory exists
try {
await promises_1.default.mkdir(tempDir, { recursive: true });
(0, console_1.log)(`Created Chrome user data directory: ${tempDir}`);
}
catch (error) {
(0, console_1.log)(`Warning: Could not create Chrome user data directory: ${error}`);
}
const ubuntuChromeArgs = [
`--user-data-dir=${tempDir}`,
'--no-sandbox',
'--disable-dev-shm-usage',
'--disable-web-security',
'--disable-features=VizDisplayCompositor',
'--disable-gpu',
'--disable-software-rasterizer',
'--disable-background-timer-throttling',
'--disable-backgrounding-occluded-windows',
'--disable-renderer-backgrounding',
'--disable-field-trial-config',
'--disable-ipc-flooding-protection',
'--single-process',
'--no-zygote'
].join(' ');
// Set the environment variable for vscode-extension-tester to pick up
process.env.VSCODE_EXTENSION_TESTER_CHROMEDRIVER_ARGS = ubuntuChromeArgs;
// Also try other possible environment variables
process.env.CHROME_OPTIONS = ubuntuChromeArgs;
process.env.CHROMIUM_FLAGS = ubuntuChromeArgs;
process.env.GOOGLE_CHROME_OPTS = ubuntuChromeArgs;
(0, console_1.log)(`Set Ubuntu Chrome driver arguments: ${ubuntuChromeArgs}`);
(0, console_1.log)(`Environment variable VSCODE_EXTENSION_TESTER_CHROMEDRIVER_ARGS: ${process.env.VSCODE_EXTENSION_TESTER_CHROMEDRIVER_ARGS}`);
// Also try setting the user data dir in a more explicit way
const userDataDir = tempDir;
process.env.CHROME_USER_DATA_DIR = userDataDir;
process.env.CHROMIUM_USER_DATA_DIR = userDataDir;
(0, console_1.log)(`Set CHROME_USER_DATA_DIR: ${userDataDir}`);
}
/**
* Kills existing Chrome processes that might be using the user data directory
*/
async killExistingChromeProcesses() {
try {
const { exec } = await Promise.resolve().then(() => __importStar(require('child_process')));
const { promisify } = await Promise.resolve().then(() => __importStar(require('util')));
const execAsync = promisify(exec);
(0, console_1.log)('Attempting to kill existing Chrome processes...');
// Kill Chrome processes
const commands = [
'pkill -f "chrome.*--test-type"',
'pkill -f "chromium.*--test-type"',
'pkill -f "google-chrome.*--test-type"',
'pkill -f "chrome.*--user-data-dir"',
'pkill -f "chromium.*--user-data-dir"'
];
for (const command of commands) {
try {
await execAsync(command);
(0, console_1.log)(`Executed: ${command}`);
}
catch (error) {
// It's normal for pkill to exit with code 1 if no processes are found
(0, console_1.log)(`Command ${command} completed (processes may not have been running)`);
}
}
// Wait a moment for processes to fully terminate
await new Promise(resolve => setTimeout(resolve, 2000));
(0, console_1.log)('Chrome process cleanup completed');
}
catch (error) {
(0, console_1.log)(`Warning: Could not kill Chrome processes: ${error}`);
}
}
/**
* Kills existing VS Code processes on Windows to prevent file locking issues during extension installation
*/
async killExistingVSCodeProcesses() {
if (process.platform !== 'win32') {
return;
}
try {
const { exec } = await Promise.resolve().then(() => __importStar(require('child_process')));
const { promisify } = await Promise.resolve().then(() => __importStar(require('util')));
const execAsync = promisify(exec);
(0, console_1.log)('Attempting to kill existing VS Code processes on Windows...');
// Kill VS Code processes on Windows
const commands = [
'taskkill /f /im Code.exe',
'taskkill /f /im code.exe',
'taskkill /f /im VSCode.exe',
'taskkill /f /im electron.exe /fi "WINDOWTITLE eq Visual Studio Code*"',
'taskkill /f /im node.exe /fi "COMMANDLINE eq *vscode*"'
];
for (const command of commands) {
try {
await execAsync(command);
(0, console_1.log)(`Executed: ${command}`);
}
catch (error) {
// It's normal for taskkill to exit with code 1 if no processes are found
(0, console_1.log)(`Command ${command} completed (processes may not have been running)`);
}
}
// Wait a moment for processes to fully terminate and release file locks
await new Promise(resolve => setTimeout(resolve, 3000));
(0, console_1.log)('VS Code process cleanup completed');
}
catch (error) {
(0, console_1.log)(`Warning: Could not kill VS Code processes: ${error}`);
}
}
/**
* Cleans up Windows VS Code extension temporary files that may cause locking issues
*/
async cleanupWindowsExtensionTempFiles() {
if (process.platform !== 'win32') {
return;
}
try {
const { exec } = await Promise.resolve().then(() => __importStar(require('child_process')));
const { promisify } = await Promise.resolve().then(() => __importStar(require('util')));
const execAsync = promisify(exec);
(0, console_1.log)('Cleaning up Windows VS Code extension temporary files...');
// Clean up VS Code extension temporary files
const cleanupCommands = [
'del /f /q "%USERPROFILE%\\.vscode\\extensions\\*.vsctmp" 2>nul',
'del /f /q "%USERPROFILE%\\.vscode\\extensions\\*.tmp" 2>nul',
'rmdir /s /q "%TEMP%\\vscode-*" 2>nul',
'rmdir /s /q "%TEMP%\\Code-*" 2>nul'
];
for (const command of cleanupCommands) {
try {
await execAsync(command);
(0, console_1.log)(`Executed cleanup: ${command}`);
}
catch (error) {
// It's normal for cleanup commands to fail if files don't exist
(0, console_1.log)(`Cleanup command completed: ${command}`);
}
}
(0, console_1.log)('Windows extension temp file cleanup completed');
}
catch (error) {
(0, console_1.log)(`Warning: Could not cleanup Windows extension temp files: ${error}`);
}
}
/**
* Creates a Chrome wrapper script that forces Ubuntu-specific arguments
*/
async createChromeWrapper() {
try {
const { exec } = await Promise.resolve().then(() => __importStar(require('child_process')));
const { promisify } = await Promise.resolve().then(() => __importStar(require('util')));
const execAsync = promisify(exec);
// Find Chrome executable
const chromeCommands = [
'which google-chrome',
'which google-chrome-stable',
'which chromium-browser',
'which chromium'
];
let chromeExePath = '';
for (const command of chromeCommands) {
try {
const result = await execAsync(command);
chromeExePath = result.stdout.trim();
if (chromeExePath) {
(0, console_1.log)(`Found Chrome at: ${chromeExePath}`);
break;
}
}
catch (error) {
// Command failed, try next one
}
}
if (!chromeExePath) {
(0, console_1.log)('Warning: Could not find Chrome executable for wrapper');
return;
}
// Create unique user data directory
const uniqueId = `${Date.now()}-${process.pid}`;
const tempDir = path_1.default.join(this.testConfig.testResources || 'test-resources', 'chrome-user-data', uniqueId);
await promises_1.default.mkdir(tempDir, { recursive: true });
// Create wrapper script
const wrapperPath = path_1.default.join(this.testConfig.testResources || 'test-resources', 'chrome-wrapper.sh');
const wrapperScript = `#!/bin/bash
# Chrome wrapper script for Ubuntu testing
exec "${chromeExePath}" \\
--user-data-dir="${tempDir}" \\
--no-sandbox \\
--disable-dev-shm-usage \\
--disable-web-security \\
--disable-features=VizDisplayCompositor \\
--disable-gpu \\
--disable-software-rasterizer \\
--disable-background-timer-throttling \\
--disable-backgrounding-occluded-windows \\
--disable-renderer-backgrounding \\
--disable-field-trial-config \\
--disable-ipc-flooding-protection \\
--single-process \\
--no-zygote \\
"$@"
`;
(0, system_operations_1.createOrOverwriteFile)(wrapperPath, wrapperScript);
await execAsync(`chmod +x "${wrapperPath}"`);
// Override Chrome path in environment
process.env.CHROME_BIN = wrapperPath;
process.env.CHROMIUM_BIN = wrapperPath;
process.env.GOOGLE_CHROME_BIN = wrapperPath;
// Create wrapper scripts for common Chrome names in our own directory
const wrapperDir = path_1.default.join(this.testConfig.testResources || 'test-resources', 'chrome-wrappers');
await promises_1.default.mkdir(wrapperDir, { recursive: true });
const chromeNames = ['google-chrome', 'google-chrome-stable', 'chromium-browser', 'chromium'];
for (const name of chromeNames) {
const nameWrapperPath = path_1.default.join(wrapperDir, name);
(0, system_operations_1.createOrOverwriteFile)(nameWrapperPath, wrapperScript);
await execAsync(`chmod +x "${nameWrapperPath}"`);
(0, console_1.log)(`Created ${name} wrapper at: ${nameWrapperPath}`);
}
// Prepend our wrapper directory to PATH so our wrappers are found first
process.env.PATH = `${wrapperDir}:${process.env.PATH}`;
(0, console_1.log)(`Prepended ${wrapperDir} to PATH`);
(0, console_1.log)(`Created Chrome wrapper at: ${wrapperPath}`);
(0, console_1.log)(`Set CHROME_BIN to: ${wrapperPath}`);
}
catch (error) {
(0, console_1.log)(`Warning: Could not create Chrome wrapper: ${error}`);
}
}
async setup() {
// Log the test environment configuration
this.logTestEnvironment();
// Set Ubuntu-specific Chrome driver arguments if needed
await this.setupUbuntuChromeArgs();
// Kill any existing Chrome processes on Linux
if (process.platform === 'linux') {
await this.killExistingChromeProcesses();
}
await this.downloadCode(this.testConfig.codeVersion);
await this.downloadChromeDriver(this.testConfig.codeVersion);
// Create Chrome wrapper script for Ubuntu
if (process.platform === 'linux') {
await this.createChromeWrapper();
}
// Kill any existing VS Code processes on Windows before installing extensions
if (process.platform === 'win32') {
await this.killExistingVSCodeProcesses();
await this.cleanupWindowsExtensionTempFiles();
}
try {
await this.installExtensions();
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
(0, console_1.log)(`Warning: Failed to install extensions: ${errorMessage}. Continuing setup.`);
}
await this.setupAndAuthorizeOrg();
}
async runTests() {
const useExistingProject = environmentSettings_1.EnvironmentSettings.getInstance().useExistingProject;
const resources = useExistingProject ? [useExistingProject] : [];
(0, console_1.log)(`starting runTests with useExistingProject: ${useExistingProject}`);
(0, console_1.log)(`starting runTests with resources: ${resources}`);
(0, console_1.log)(`starting runTests with spec: ${this.spec}`);
// Set Ubuntu Chrome arguments just before running tests
const isLinux = process.platform === 'linux';
if (isLinux) {
await this.setupUbuntuChromeArgs();
await this.killExistingChromeProcesses();
// Use Ubuntu-specific test runner
(0, console_1.log)('Using Ubuntu-specific test runner...');
return await this.runTestsUbuntu();
}
// Try to pass additional launch arguments for Ubuntu
const runOptions = { resources };
if (isLinux) {
runOptions.launchArgs = [
'--disable-web-security',
'--disable-features=VizDisplayCompositor',
'--no-sandbox',
'--disable-dev-shm-usage'
];
}
const exitCode = await super.runTests(this.spec || environmentSettings_1.EnvironmentSettings.getInstance().specFiles, runOptions);
await this.validateTestExecution(exitCode);
return exitCode;
}
async installExtension(extension) {
(0, console_1.log)(`SetUp - Started Install extension ${path_1.default.basename(extension)}`);
await this.installVsix({ useYarn: false, vsixFile: extension });
}
async installExtensions(excludeExtensions = []) {
const vsixToInstallDir = this.testConfig.vsixToInstallDir || environmentSettings_1.EnvironmentSettings.getInstance().vsixToInstallDir;
(0, console_1.log)(`Installing extension from vsixToInstallDir: ${vsixToInstallDir}`);
if (!vsixToInstallDir) {
(0, console_1.log)(`No VSIX_TO_INSTALL directory specified, the tests will run without installing any extensions`);
return;
}
// Check if the directory exists
try {
await promises_1.default.access(vsixToInstallDir);
}
catch (error) {
throw new Error(`VSIX_TO_INSTALL directory does not exist or is not accessible: ${vsixToInstallDir}`);
}
// Check for already installed extensions
const extensionPattern = /^(?<publisher>.+?)\.(?<extensionId>.+?)-(?<version>\d+\.\d+\.\d+)(?:\.\d+)*$/;
const extensionsDirEntries = (await promises_1.default.readdir(vsixToInstallDir)).map(entry => path_1.default.resolve((0, helpers_2.normalizePath)(path_1.default.join(vsixToInstallDir, entry))));
const foundInstalledExtensions = await Promise.all(extensionsDirEntries
.filter(async (entry) => {
try {
const stats = await promises_1.default.stat(entry);
return stats.isDirectory();
}
catch (e) {
(0, console_1.log)(`stat failed for file ${entry}`);
return false;
}
})
.map(entry => {
const match = path_1.default.basename(entry).match(extensionPattern);
if (match?.groups) {
return {
publisher: match.groups.publisher,
extensionId: match.groups.extensionId,
version: match.groups.version,
path: entry
};
}
return null;
})
.filter(Boolean)
.filter(ext => extensionUtils_1.extensions.find(refExt => {
return refExt.extensionId === ext?.extensionId;
})));
if (foundInstalledExtensions.length > 0 &&
foundInstalledExtensions.every(ext => extensionUtils_1.extensions.find(refExt => refExt.extensionId === ext?.extensionId))) {
(0, console_1.log)(`Found the following pre-installed extensions in dir ${vsixToInstallDir}, skipping installation of vsix`);
foundInstalledExtensions.forEach(ext => {
(0, console_1.log)(`Extension ${ext?.extensionId} version ${ext?.version}`);
});
return;
}
const extensionsVsixs = (0, system_operations_1.getVsixFilesFromDir)(vsixToInstallDir);
if (extensionsVsixs.length === 0) {
(0, console_1.log)(`No vsix files were found in dir ${vsixToInstallDir}, skipping extension installation`);
return; // Skip installation instead of throwing an error
}
(0, console_1.log)(`VSIX files count: ${extensionsVsixs.length} were found in dir ${vsixToInstallDir}, skipping extension installation`);
const mergeExcluded = Array.from(new Set([
...excludeExtensions,
...extensionUtils_1.extensions.filter(ext => ext.shouldInstall === 'never').map(ext => ext.extensionId)
]));
// Refactored part to use the extensions array
extensionsVsixs.forEach(vsix => {
const match = path_1.default.basename(vsix).match(/^(?<extension>.*?)(-(?<version>\d+\.\d+\.\d+))?\.vsix$/);
(0, console_1.log)(`Found extension: ${vsix} with match ${match}`);
if (match?.groups) {
const { extension, version } = match.groups;
const foundExtension = extensionUtils_1.extensions.find(e => e.extensionId === extension);
if (foundExtension) {
foundExtension.vsixPath = vsix;
// assign 'never' to this extension if its id is included in excluedExtensions
foundExtension.shouldInstall = mergeExcluded.includes(foundExtension.extensionId) ? 'never' : 'always';
// if not installing, don't verify, otherwise use default value
foundExtension.shouldVerifyActivation =
foundExtension.shouldInstall === 'never' ? false : foundExtension.shouldVerifyActivation;
(0, console_1.log)(`SetUp - Found extension ${extension} version ${version} with vsixPath ${foundExtension.vsixPath}`);
}
else {
(0, console_1.log)(`SetUp - Not found extension ${extension} version ${version}`);
}
}
});
if (extensionUtils_1.extensions.length === 0) {
(0, console_1.log)(`No extensions found, cannot proceed with install`);
}
// Iterate over the extensions array to install extensions
for (const extensionObj of extensionUtils_1.extensions.filter(ext => ext.vsixPath !== '' && ext.shouldInstall !== 'never')) {
let vsixPath = path_1.default.resolve(extensionObj.vsixPath);
// Avoid Windows paths being parsed as URLs by the tester
if (process.platform === 'win32') {
vsixPath = vsixPath.replace(/\\/g, '/');
if (/^[a-zA-Z]:\//.test(vsixPath)) {
vsixPath = '/' + vsixPath; // Prevent `new URL()` from treating it as a URL
}
}
// Use retry logic for Windows to handle file locking issues
if (process.platform === 'win32') {
await (0, retryUtils_1.retryOperation)(async () => {
// Add a small delay before each retry attempt to allow file handles to be released
await new Promise(resolve => setTimeout(resolve, TestSetupAndRunner.RETRY_DELAY));
await this.installExtension(vsixPath);
}, TestSetupAndRunner.MAX_RETRIES, `Failed to install extension ${path_1.default.basename(vsixPath)} after retries due to Windows file locking issues`);
}
else {
await this.installExtension(vsixPath);
}
}
}
async setupAndAuthorizeOrg() {
const environmentSettings = environmentSettings_1.EnvironmentSettings.getInstance();
const devHubUserName = environmentSettings.devHubUserName;
const devHubAliasName = environmentSettings.devHubAliasName;
const SFDX_AUTH_URL = environmentSettings.sfdxAuthUrl;
try {
// First try to verify if the org with alias and username exists
(0, console_1.log)('Checking if org with matching alias and username is already authenticated...');
await (0, authorization_1.verifyAliasAndUserName)();
(0, console_1.log)(`Org with alias ${devHubAliasName} and username ${devHubUserName} is already authenticated`);
return;
}
catch (error) {
// If verification fails, continue with SFDX_AUTH_URL
const errorMessage = error instanceof Error ? error.message : String(error);
(0, console_1.log)(`No existing authenticated org found: ${errorMessage}`);
// If no SFDX_AUTH_URL is provided, rethrow the error
if (!SFDX_AUTH_URL) {
throw new Error('No SFDX_AUTH_URL provided and no existing authentication found. Unable to authorize org.');
}
}
// The verifyAliasAndUserName function already checks that both alias and username are set,
// so at this point we know they are defined (or we would have thrown earlier).
// TypeScript doesn't know this though, so we'll add the non-null assertion
if (!devHubUserName || !devHubAliasName) {
throw new Error('DevHub username or alias name is not set. This should not happen as verifyAliasAndUserName should have caught this.');
}
// At this point, neither alias nor username matched an existing org,
// and we have an SFDX_AUTH_URL to use
(0, console_1.log)('Authenticating using SFDX_AUTH_URL from environment variable...');
// Step 1: Authorize to Testing Org
const authorizeOrg = await (0, cliCommands_1.orgLoginSfdxUrl)();
(0, chai_1.expect)(authorizeOrg.stdout).to.contain(`Successfully authorized ${devHubUserName}`);
(0, console_1.log)(`Successfully authorized ${devHubUserName}`);
// Step 2: Set Alias for the Org
const setAliasResult = await (0, cliCommands_1.setAlias)(devHubAliasName, devHubUserName);
(0, chai_1.expect)(setAliasResult.stdout).to.contain(devHubAliasName);
(0, chai_1.expect)(setAliasResult.stdout).to.contain(devHubUserName);
(0, chai_1.expect)(setAliasResult.stdout).to.contain('true');
(0, console_1.log)(`Successfully setAliasResult ${setAliasResult.stdout}`);
}
async downloadCode(version = 'latest') {
await super.downloadCode(version);
}
static get exTester() {
if (TestSetupAndRunner.exTester) {
return TestSetupAndRunner._exTestor;
}
TestSetupAndRunner._exTestor = new TestSetupAndRunner();
return TestSetupAndRunner._exTestor;
}
async setupVirtualDisplay() {
if (process.platform !== 'linux') {
return;
}
try {
console.log('Setting up virtual display for Ubuntu...');
// Start Xvfb virtual display
await this.execShellCommand('Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &').catch(() => {
console.log('Xvfb might already be running or not available');
});
// Wait for display to be ready
await new Promise(resolve => setTimeout(resolve, 2000));
// Verify display is working
await this.execShellCommand('export DISPLAY=:99 && xdpyinfo > /dev/null 2>&1').catch(() => {
console.log('Virtual display verification failed, continuing anyway');
});
console.log('Virtual display setup completed');
}
catch (error) {
console.warn('Failed to setup virtual display:', error);
}
}
async execShellCommand(command) {
const { exec } = await Promise.resolve().then(() => __importStar(require('child_process')));
const { promisify } = await Promise.resolve().then(() => __importStar(require('util')));
const execAsync = promisify(exec);
await execAsync(command);
}
async runTestsUbuntu() {
// Setup virtual display for Ubuntu
await this.setupVirtualDisplay();
console.log('Running tests...');
// Kill any existing Chrome processes before running
if (process.platform === 'linux') {
await this.killChromeProcesses();
}
// Create a unique user data directory
const uniqueUserDataDir = `/tmp/vscode-chrome-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
// Update environment variables with unique directory
process.env.CHROME_USER_DATA_DIR = uniqueUserDataDir;
// Call the existing runTests method
const useExistingProject = environmentSettings_1.EnvironmentSettings.getInstance().useExistingProject;
const resources = useExistingProject ? [useExistingProject] : [];
const exitCode = await super.runTests(this.spec || environmentSettings_1.EnvironmentSettings.getInstance().specFiles, { resources });
await this.validateTestExecution(exitCode);
if (exitCode === 0) {
console.log('Tests completed successfully!');
}
return exitCode;
}
/**
* Validates that tests were actually executed by checking for test output
* Throws an error if no tests were found or executed
*/
async validateTestExecution(exitCode) {
if (exitCode !== 0) {
return; // If tests failed, don't check for test count (tests were found but failed)
}
// Check if any spec files were provided
const specFiles = this.spec || environmentSettings_1.EnvironmentSettings.getInstance().specFiles;
if (!specFiles || (Array.isArray(specFiles) && specFiles.length === 0)) {
throw new Error('No E2E test spec files were provided. Please specify test files to run.');
}
// Check if spec files actually exist
const specsToCheck = Array.isArray(specFiles) ? specFiles : [specFiles];
let foundTestFiles = false;
for (const spec of specsToCheck) {
try {
// Check if the spec file/pattern matches any files
if (spec.includes('*') || spec.includes('?')) {
// It's a glob pattern - we can't easily check without implementing glob matching
// For now, assume it's valid if it contains wildcards
(0, console_1.log)(`Spec pattern provided: ${spec}`);
foundTestFiles = true;
}
else {
// It's a specific file path
await promises_1.default.access(spec);
foundTestFiles = true;
(0, console_1.log)(`Found test file: ${spec}`);
}
}
catch (error) {
(0, console_1.log)(`Test file not found: ${spec}`);
}
}
if (!foundTestFiles && exitCode === 0) {
throw new Error(`No E2E test files were found for the provided spec: ${JSON.stringify(specFiles)}. This likely indicates a configuration issue - tests should not pass when no test files exist.`);
}
(0, console_1.log)('Test execution validation completed - test files were found and executed');
}
async killChromeProcesses() {
try {
console.log('Killing existing Chrome processes...');
const commands = [
'pkill -f "chrome.*--test-type"',
'pkill -f "chrome.*--remote-debugging-port"',
'pkill -f "chrome.*--user-data-dir"',
'pkill -f "chrome.*--disable-dev-shm-usage"',
'pkill -f chromium',
'pkill -f google-chrome',
'pkill -f chrome',
'pkill -f "Code.*--extensionDevelopmentPath"',
'pkill -f "code.*--extensionDevelopmentPath"'
];
for (const cmd of commands) {
try {
await this.execShellCommand(cmd);
}
catch (error) {
// Ignore errors - processes might not exist
}
}
// Clean up any leftover user data directories and lock files
await this.execShellCommand('rm -rf /tmp/chrome-user-data-* /tmp/vscode-chrome-* ~/.config/Code/SingletonLock ~/.vscode/extensions/*/node_modules/.bin/chromedriver').catch(() => {
console.log('Failed to clean up leftover files, continuing anyway');
});
// Wait a moment for processes to fully terminate
await new Promise(resolve => setTimeout(resolve, 3000));
}
catch (error) {
console.warn('Failed to kill Chrome processes:', error);
}
}
}
// Parse command-line arguments
const argv = (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
.option('spec', {
alias: 's',
type: 'string',
description: 'Glob pattern for test files or list of test files',
demandOption: false,
array: true
})
.option('workspace-path', {
alias: 'w',
type: 'string',
description: 'Path to workspace directory',
demandOption: false
})
.option('vscode-version', {
alias: 'v',
type: 'string',
description: 'VS Code version to use (e.g., latest, stable, 1.85.0)',
demandOption: false
})
.help().argv;
// Create test config from command line arguments
const testConfig = {};
if (argv.workspacePath) {
testConfig.workspacePath = argv.workspacePath;
testConfig.extensionsPath = path_1.default.join(argv.workspacePath, 'extensions');
}
if (argv.vscodeVersion) {
testConfig.codeVersion = argv.vscodeVersion;
}
if (argv.spec) {
(0, console_1.log)(`Spec passed in: ${argv.spec}`);
}
const testSetupAndRunner = new TestSetupAndRunner(testConfig, argv.spec);
async function run() {
try {
await testSetupAndRunner.setup();
const result = await testSetupAndRunner.runTests();
console.log(result);
process.exit(result);
}
catch (error) {
console.error(error);
process.exit(1);
}
}
void run();
//# sourceMappingURL=test-setup-and-runner.js.map