mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-08 09:08:48 +01:00
Implement more sanity tests (#286914)
This commit is contained in:
+12
-20
@@ -92,47 +92,39 @@ export function setup(context: TestContext) {
|
||||
process.chdir(workspaceDir);
|
||||
context.log(`Changed current directory to: ${workspaceDir}`);
|
||||
|
||||
const cliDataDir = context.createTempDir();
|
||||
const userDataDir = context.createTempDir();
|
||||
const serverDataDir = context.createTempDir();
|
||||
const extensionsDir = context.createTempDir();
|
||||
const args = [
|
||||
'--cli-data-dir', cliDataDir,
|
||||
'--user-data-dir', userDataDir,
|
||||
'--cli-data-dir', context.createTempDir(),
|
||||
'--user-data-dir', context.createTempDir(),
|
||||
'tunnel',
|
||||
'--accept-server-license-terms',
|
||||
'--server-data-dir', serverDataDir,
|
||||
'--extensions-dir', extensionsDir,
|
||||
'--server-data-dir', context.createTempDir(),
|
||||
'--extensions-dir', context.createTempDir(),
|
||||
];
|
||||
|
||||
context.log(`Running CLI ${entryPoint} with args: ${args.join(' ')}`);
|
||||
const cli = spawn(entryPoint, args);
|
||||
context.log(`Running CLI ${entryPoint} with args ${args.join(' ')}`);
|
||||
const cli = spawn(entryPoint, args, { detached: true });
|
||||
|
||||
cli.stderr.on('data', (data) => {
|
||||
context.error(`[CLI Error] ${data.toString().trim()}`);
|
||||
});
|
||||
|
||||
let tunnelUrl: string | undefined = undefined;
|
||||
cli.stdout.on('data', (data) => {
|
||||
const text = data.toString();
|
||||
text.trim().split('\n').forEach((line: string) => {
|
||||
const text = data.toString().trim();
|
||||
text.split('\n').forEach((line: string) => {
|
||||
context.log(`[CLI Output] ${line}`);
|
||||
});
|
||||
|
||||
const match = /Open this link in your browser (https:\/\/.+)/.exec(text);
|
||||
const match = /Using GitHub for authentication/.exec(text);
|
||||
if (match !== null) {
|
||||
tunnelUrl = context.getTunnelUrl(match[1]);
|
||||
context.log(`Tunnel URL: ${tunnelUrl}`);
|
||||
cli.kill();
|
||||
context.log(`CLI started successfully and is waiting for authentication`);
|
||||
context.killProcessTree(cli.pid!);
|
||||
}
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
cli.on('error', reject);
|
||||
cli.on('exit', () => resolve());
|
||||
cli.on('exit', resolve);
|
||||
});
|
||||
|
||||
assert.ok(tunnelUrl, 'Expected to receive a tunnel URL from the CLI');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+109
-9
@@ -9,6 +9,7 @@ import fs from 'fs';
|
||||
import fetch, { Response } from 'node-fetch';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import { Browser, chromium } from 'playwright';
|
||||
|
||||
/**
|
||||
* Response from https://update.code.visualstudio.com/api/versions/commit:<commit>/<target>/<quality>
|
||||
@@ -282,6 +283,43 @@ export class TestContext {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kills a process and all its child processes.
|
||||
* @param pid The process ID to kill.
|
||||
*/
|
||||
public killProcessTree(pid: number): void {
|
||||
this.log(`Killing process tree for PID: ${pid}`);
|
||||
if (os.platform() === 'win32') {
|
||||
spawnSync('taskkill', ['/T', '/F', '/PID', pid.toString()]);
|
||||
} else {
|
||||
process.kill(-pid, 'SIGKILL');
|
||||
}
|
||||
this.log(`Killed process tree for PID: ${pid}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Windows installation directory for VS Code based on the installation type and quality.
|
||||
* @param type The type of installation ('user' or 'system').
|
||||
* @returns The path to the VS Code installation directory.
|
||||
*/
|
||||
private getWindowsInstallDir(type: 'user' | 'system'): string {
|
||||
let parentDir: string;
|
||||
if (type === 'system') {
|
||||
parentDir = process.env['PROGRAMFILES'] || '';
|
||||
} else {
|
||||
parentDir = path.join(process.env['LOCALAPPDATA'] || '', 'Programs');
|
||||
}
|
||||
|
||||
switch (this.quality) {
|
||||
case 'stable':
|
||||
return path.join(parentDir, 'Microsoft VS Code');
|
||||
case 'insider':
|
||||
return path.join(parentDir, 'Microsoft VS Code Insiders');
|
||||
case 'exploration':
|
||||
return path.join(parentDir, 'Microsoft VS Code Exploration');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs a Microsoft Installer package silently.
|
||||
* @param installerPath The path to the installer executable.
|
||||
@@ -292,22 +330,17 @@ export class TestContext {
|
||||
this.runNoErrors(installerPath, '/silent', '/mergetasks=!runcode');
|
||||
this.log(`Installed ${installerPath} successfully`);
|
||||
|
||||
const varName = type === 'system' ? 'PROGRAMFILES' : 'LOCALAPPDATA';
|
||||
const parentDir = process.env[varName];
|
||||
if (parentDir === undefined) {
|
||||
this.error(`Environment variable ${varName} is not defined`);
|
||||
}
|
||||
|
||||
const appDir = this.getWindowsInstallDir(type);
|
||||
let entryPoint: string;
|
||||
switch (this.quality) {
|
||||
case 'stable':
|
||||
entryPoint = path.join(parentDir, 'Microsoft VS Code', 'Code.exe');
|
||||
entryPoint = path.join(appDir, 'Code.exe');
|
||||
break;
|
||||
case 'insider':
|
||||
entryPoint = path.join(parentDir, 'Microsoft VS Code Insiders', 'Code - Insiders.exe');
|
||||
entryPoint = path.join(appDir, 'Code - Insiders.exe');
|
||||
break;
|
||||
case 'exploration':
|
||||
entryPoint = path.join(parentDir, 'Microsoft VS Code Exploration', 'Code - Exploration.exe');
|
||||
entryPoint = path.join(appDir, 'Code - Exploration.exe');
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -319,6 +352,27 @@ export class TestContext {
|
||||
return entryPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstalls a Windows application silently.
|
||||
* @param type The type of installation ('user' or 'system').
|
||||
*/
|
||||
public async uninstallWindowsApp(type: 'user' | 'system'): Promise<void> {
|
||||
const appDir = this.getWindowsInstallDir(type);
|
||||
const uninstallerPath = path.join(appDir, 'unins000.exe');
|
||||
if (!fs.existsSync(uninstallerPath)) {
|
||||
this.error(`Uninstaller does not exist: ${uninstallerPath}`);
|
||||
}
|
||||
|
||||
this.log(`Uninstalling VS Code from ${appDir} in silent mode`);
|
||||
this.runNoErrors(uninstallerPath, '/silent');
|
||||
this.log(`Uninstalled VS Code from ${appDir} successfully`);
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
if (fs.existsSync(appDir)) {
|
||||
this.error(`Installation directory still exists after uninstall: ${appDir}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a macOS .app bundle for execution by removing the quarantine attribute.
|
||||
* @param bundleDir The directory containing the .app bundle.
|
||||
@@ -426,6 +480,42 @@ export class TestContext {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the entry point executable for the VS Code server in the specified directory.
|
||||
* @param dir The directory containing unpacked server files.
|
||||
* @returns The path to the server entry point executable.
|
||||
*/
|
||||
public getServerEntryPoint(dir: string): string {
|
||||
const serverDir = fs.readdirSync(dir, { withFileTypes: true }).filter(o => o.isDirectory()).at(0)?.name;
|
||||
if (!serverDir) {
|
||||
this.error(`No subdirectories found in server directory: ${dir}`);
|
||||
}
|
||||
|
||||
let filename: string;
|
||||
switch (this.quality) {
|
||||
case 'stable':
|
||||
filename = 'code-server';
|
||||
break;
|
||||
case 'insider':
|
||||
filename = 'code-server-insiders';
|
||||
break;
|
||||
case 'exploration':
|
||||
filename = 'code-server-exploration';
|
||||
break;
|
||||
}
|
||||
|
||||
if (os.platform() === 'win32') {
|
||||
filename += '.cmd';
|
||||
}
|
||||
|
||||
const entryPoint = path.join(dir, serverDir, 'bin', filename);
|
||||
if (!fs.existsSync(entryPoint)) {
|
||||
this.error(`Server entry point does not exist: ${entryPoint}`);
|
||||
}
|
||||
|
||||
return entryPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tunnel URL for the VS Code server including vscode-version parameter.
|
||||
* @param baseUrl The base URL for the VS Code server.
|
||||
@@ -436,4 +526,14 @@ export class TestContext {
|
||||
url.searchParams.set('vscode-version', this.commit);
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches a web browser for UI testing.
|
||||
* @returns The launched Browser instance.
|
||||
*/
|
||||
public async launchBrowser(): Promise<Browser> {
|
||||
this.log(`Launching web browser`);
|
||||
const channel = os.platform() === 'win32' ? 'msedge' : 'chrome';
|
||||
return await chromium.launch({ channel, headless: false });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,16 +3,15 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import assert from 'assert';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { _electron } from 'playwright';
|
||||
import { TestContext } from './context';
|
||||
import { UITest } from './uiTest';
|
||||
|
||||
export function setup(context: TestContext) {
|
||||
describe('Desktop', () => {
|
||||
if (context.platform === 'darwin-x64') {
|
||||
it('darwin', async () => {
|
||||
it('desktop-darwin', async () => {
|
||||
const dir = await context.downloadAndUnpack('darwin');
|
||||
const entryPoint = context.installMacApp(dir);
|
||||
await testDesktopApp(entryPoint);
|
||||
@@ -20,7 +19,7 @@ export function setup(context: TestContext) {
|
||||
}
|
||||
|
||||
if (context.platform === 'darwin-arm64') {
|
||||
it('darwin-arm64', async () => {
|
||||
it('desktop-darwin-arm64', async () => {
|
||||
const dir = await context.downloadAndUnpack('darwin-arm64');
|
||||
const entryPoint = context.installMacApp(dir);
|
||||
await testDesktopApp(entryPoint);
|
||||
@@ -28,7 +27,7 @@ export function setup(context: TestContext) {
|
||||
}
|
||||
|
||||
if (context.platform.startsWith('darwin-')) {
|
||||
it('darwin-universal', async () => {
|
||||
it('desktop-darwin-universal', async () => {
|
||||
const dir = await context.downloadAndUnpack('darwin-universal');
|
||||
const entryPoint = context.installMacApp(dir);
|
||||
await testDesktopApp(entryPoint);
|
||||
@@ -36,7 +35,7 @@ export function setup(context: TestContext) {
|
||||
}
|
||||
|
||||
if (context.platform === 'linux-arm64') {
|
||||
it('linux-arm64', async () => {
|
||||
it('desktop-linux-arm64', async () => {
|
||||
const dir = await context.downloadAndUnpack('linux-arm64');
|
||||
const entryPoint = context.getEntryPoint('desktop', dir);
|
||||
await testDesktopApp(entryPoint);
|
||||
@@ -44,7 +43,7 @@ export function setup(context: TestContext) {
|
||||
}
|
||||
|
||||
if (context.platform === 'linux-arm') {
|
||||
it('linux-armhf', async () => {
|
||||
it('desktop-linux-armhf', async () => {
|
||||
const dir = await context.downloadAndUnpack('linux-armhf');
|
||||
const entryPoint = context.getEntryPoint('desktop', dir);
|
||||
await testDesktopApp(entryPoint);
|
||||
@@ -52,7 +51,7 @@ export function setup(context: TestContext) {
|
||||
}
|
||||
|
||||
if (context.platform === 'linux-arm64') {
|
||||
it('linux-deb-arm64', async () => {
|
||||
it('desktop-linux-deb-arm64', async () => {
|
||||
const packagePath = await context.downloadTarget('linux-deb-arm64');
|
||||
const entryPoint = context.installDeb(packagePath);
|
||||
await testDesktopApp(entryPoint);
|
||||
@@ -60,7 +59,7 @@ export function setup(context: TestContext) {
|
||||
}
|
||||
|
||||
if (context.platform === 'linux-arm') {
|
||||
it('linux-deb-armhf', async () => {
|
||||
it('desktop-linux-deb-armhf', async () => {
|
||||
const packagePath = await context.downloadTarget('linux-deb-armhf');
|
||||
const entryPoint = context.installDeb(packagePath);
|
||||
await testDesktopApp(entryPoint);
|
||||
@@ -68,7 +67,7 @@ export function setup(context: TestContext) {
|
||||
}
|
||||
|
||||
if (context.platform === 'linux-x64') {
|
||||
it('linux-deb-x64', async () => {
|
||||
it('desktop-linux-deb-x64', async () => {
|
||||
const packagePath = await context.downloadTarget('linux-deb-x64');
|
||||
const entryPoint = context.installDeb(packagePath);
|
||||
await testDesktopApp(entryPoint);
|
||||
@@ -76,7 +75,7 @@ export function setup(context: TestContext) {
|
||||
}
|
||||
|
||||
if (context.platform === 'linux-arm64') {
|
||||
it('linux-rpm-arm64', async () => {
|
||||
it('desktop-linux-rpm-arm64', async () => {
|
||||
const packagePath = await context.downloadTarget('linux-rpm-arm64');
|
||||
const entryPoint = context.installRpm(packagePath);
|
||||
await testDesktopApp(entryPoint);
|
||||
@@ -84,7 +83,7 @@ export function setup(context: TestContext) {
|
||||
}
|
||||
|
||||
if (context.platform === 'linux-arm') {
|
||||
it('linux-rpm-armhf', async () => {
|
||||
it('desktop-linux-rpm-armhf', async () => {
|
||||
const packagePath = await context.downloadTarget('linux-rpm-armhf');
|
||||
const entryPoint = context.installRpm(packagePath);
|
||||
await testDesktopApp(entryPoint);
|
||||
@@ -92,7 +91,7 @@ export function setup(context: TestContext) {
|
||||
}
|
||||
|
||||
if (context.platform === 'linux-x64') {
|
||||
it('linux-rpm-x64', async () => {
|
||||
it('desktop-linux-rpm-x64', async () => {
|
||||
const packagePath = await context.downloadTarget('linux-rpm-x64');
|
||||
const entryPoint = context.installRpm(packagePath);
|
||||
await testDesktopApp(entryPoint);
|
||||
@@ -100,7 +99,7 @@ export function setup(context: TestContext) {
|
||||
}
|
||||
|
||||
if (context.platform === 'linux-x64') {
|
||||
it('linux-snap-x64', async () => {
|
||||
it('desktop-linux-snap-x64', async () => {
|
||||
const packagePath = await context.downloadTarget('linux-snap-x64');
|
||||
const entryPoint = context.installSnap(packagePath);
|
||||
await testDesktopApp(entryPoint);
|
||||
@@ -108,7 +107,7 @@ export function setup(context: TestContext) {
|
||||
}
|
||||
|
||||
if (context.platform === 'linux-x64') {
|
||||
it('linux-x64', async () => {
|
||||
it('desktop-linux-x64', async () => {
|
||||
const dir = await context.downloadAndUnpack('linux-x64');
|
||||
const entryPoint = context.getEntryPoint('desktop', dir);
|
||||
await testDesktopApp(entryPoint);
|
||||
@@ -116,17 +115,18 @@ export function setup(context: TestContext) {
|
||||
}
|
||||
|
||||
if (context.platform === 'win32-arm64') {
|
||||
it('win32-arm64', async () => {
|
||||
it('desktop-win32-arm64', async () => {
|
||||
const packagePath = await context.downloadTarget('win32-arm64');
|
||||
context.validateSignature(packagePath);
|
||||
const entryPoint = context.installWindowsApp('system', packagePath);
|
||||
context.validateAllSignatures(path.dirname(entryPoint));
|
||||
await testDesktopApp(entryPoint);
|
||||
await context.uninstallWindowsApp('system');
|
||||
});
|
||||
}
|
||||
|
||||
if (context.platform === 'win32-arm64') {
|
||||
it('win32-arm64-archive', async () => {
|
||||
it('desktop-win32-arm64-archive', async () => {
|
||||
const dir = await context.downloadAndUnpack('win32-arm64-archive');
|
||||
context.validateAllSignatures(dir);
|
||||
const entryPoint = context.getEntryPoint('desktop', dir);
|
||||
@@ -135,27 +135,29 @@ export function setup(context: TestContext) {
|
||||
}
|
||||
|
||||
if (context.platform === 'win32-arm64') {
|
||||
it('win32-arm64-user', async () => {
|
||||
it('desktop-win32-arm64-user', async () => {
|
||||
const packagePath = await context.downloadTarget('win32-arm64-user');
|
||||
context.validateSignature(packagePath);
|
||||
const entryPoint = context.installWindowsApp('user', packagePath);
|
||||
context.validateAllSignatures(path.dirname(entryPoint));
|
||||
await testDesktopApp(entryPoint);
|
||||
await context.uninstallWindowsApp('user');
|
||||
});
|
||||
}
|
||||
|
||||
if (context.platform === 'win32-x64') {
|
||||
it('win32-x64', async () => {
|
||||
it('desktop-win32-x64', async () => {
|
||||
const packagePath = await context.downloadTarget('win32-x64');
|
||||
context.validateSignature(packagePath);
|
||||
const entryPoint = context.installWindowsApp('system', packagePath);
|
||||
context.validateAllSignatures(path.dirname(entryPoint));
|
||||
await testDesktopApp(entryPoint);
|
||||
await context.uninstallWindowsApp('system');
|
||||
});
|
||||
}
|
||||
|
||||
if (context.platform === 'win32-x64') {
|
||||
it('win32-x64-archive', async () => {
|
||||
it('desktop-win32-x64-archive', async () => {
|
||||
const dir = await context.downloadAndUnpack('win32-x64-archive');
|
||||
context.validateAllSignatures(dir);
|
||||
const entryPoint = context.getEntryPoint('desktop', dir);
|
||||
@@ -164,73 +166,34 @@ export function setup(context: TestContext) {
|
||||
}
|
||||
|
||||
if (context.platform === 'win32-x64') {
|
||||
it('win32-x64-user', async () => {
|
||||
it('desktop-win32-x64-user', async () => {
|
||||
const packagePath = await context.downloadTarget('win32-x64-user');
|
||||
context.validateSignature(packagePath);
|
||||
const entryPoint = context.installWindowsApp('user', packagePath);
|
||||
context.validateAllSignatures(path.dirname(entryPoint));
|
||||
await testDesktopApp(entryPoint);
|
||||
await context.uninstallWindowsApp('user');
|
||||
});
|
||||
}
|
||||
|
||||
async function testDesktopApp(executablePath: string) {
|
||||
const extensionsDir = context.createTempDir();
|
||||
const dataDir = context.createTempDir();
|
||||
const workspaceDir = context.createTempDir();
|
||||
const args = ['--extensions-dir', extensionsDir, '--user-data-dir', dataDir, workspaceDir];
|
||||
async function testDesktopApp(entryPoint: string) {
|
||||
const test = new UITest(context);
|
||||
const args = [
|
||||
'--extensions-dir', test.extensionsDir,
|
||||
'--user-data-dir', test.userDataDir,
|
||||
test.workspaceDir
|
||||
];
|
||||
|
||||
context.log(`Start VS Code: ${executablePath} with args: ${args.join(' ')}`);
|
||||
const app = await _electron.launch({ executablePath, args });
|
||||
context.log(`Starting VS Code ${entryPoint} with args ${args.join(' ')}`);
|
||||
const app = await _electron.launch({ executablePath: entryPoint, args });
|
||||
const window = await app.firstWindow();
|
||||
|
||||
context.log('Dismiss workspace trust dialog');
|
||||
await window.getByText('Yes, I trust the authors').click();
|
||||
await test.run(window);
|
||||
|
||||
context.log('Focus Explorer view');
|
||||
await window.keyboard.press('Control+Shift+E');
|
||||
|
||||
context.log('Click New File button');
|
||||
await window.getByLabel('New File...').click();
|
||||
|
||||
context.log('Type file name');
|
||||
await window.locator('input').fill('helloWorld.txt');
|
||||
await window.keyboard.press('Enter');
|
||||
|
||||
context.log('Focus the code editor');
|
||||
await window.getByText(/Start typing/).focus();
|
||||
|
||||
context.log('Type some content into the file');
|
||||
await window.keyboard.type('Hello, World!');
|
||||
|
||||
context.log('Save the file');
|
||||
await window.keyboard.press('Control+S');
|
||||
|
||||
context.log('Open Extensions view');
|
||||
await window.keyboard.press('Control+Shift+X');
|
||||
await window.waitForSelector('.extension-list-item');
|
||||
|
||||
context.log('Type extension name to search for');
|
||||
await window.keyboard.type('GitHub Pull Requests');
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
context.log('Click Install on the first extension in the list');
|
||||
await window.locator('.extension-action:not(.disabled)', { hasText: /Install/ }).first().click();
|
||||
|
||||
context.log('Wait for extension to be installed');
|
||||
await window.locator('.extension-action:not(.disabled)', { hasText: /Uninstall/ }).waitFor();
|
||||
|
||||
context.log('Close the application');
|
||||
context.log('Closing the application');
|
||||
await app.close();
|
||||
|
||||
context.log('Verify file contents');
|
||||
const filePath = `${workspaceDir}/helloWorld.txt`;
|
||||
const fileContents = fs.readFileSync(filePath, 'utf-8');
|
||||
assert.strictEqual(fileContents, 'Hello, World!');
|
||||
|
||||
context.log('Verify extension is installed');
|
||||
const extensions = fs.readdirSync(extensionsDir);
|
||||
const hasExtension = extensions.some(ext => ext.startsWith('github.vscode-pull-request-github'));
|
||||
assert.strictEqual(hasExtension, true);
|
||||
test.validate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,49 +3,65 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import assert from 'assert';
|
||||
import { spawn } from 'child_process';
|
||||
import { TestContext } from './context';
|
||||
|
||||
export function setup(context: TestContext) {
|
||||
describe('Server', () => {
|
||||
if (context.platform === 'linux-arm64') {
|
||||
it('server-alpine-arm64', async () => {
|
||||
await context.downloadAndUnpack('server-alpine-arm64');
|
||||
const dir = await context.downloadAndUnpack('server-alpine-arm64');
|
||||
const entryPoint = context.getServerEntryPoint(dir);
|
||||
await testServer(entryPoint);
|
||||
});
|
||||
}
|
||||
|
||||
if (context.platform === 'linux-x64') {
|
||||
it('server-alpine-x64', async () => {
|
||||
await context.downloadAndUnpack('server-linux-alpine');
|
||||
const dir = await context.downloadAndUnpack('server-linux-alpine');
|
||||
const entryPoint = context.getServerEntryPoint(dir);
|
||||
await testServer(entryPoint);
|
||||
});
|
||||
}
|
||||
|
||||
if (context.platform === 'darwin-arm64') {
|
||||
it('server-darwin-arm64', async () => {
|
||||
await context.downloadAndUnpack('server-darwin-arm64');
|
||||
const dir = await context.downloadAndUnpack('server-darwin-arm64');
|
||||
const entryPoint = context.getServerEntryPoint(dir);
|
||||
await testServer(entryPoint);
|
||||
});
|
||||
}
|
||||
|
||||
if (context.platform === 'darwin-x64') {
|
||||
it('server-darwin-x64', async () => {
|
||||
await context.downloadAndUnpack('server-darwin');
|
||||
const dir = await context.downloadAndUnpack('server-darwin');
|
||||
const entryPoint = context.getServerEntryPoint(dir);
|
||||
await testServer(entryPoint);
|
||||
});
|
||||
}
|
||||
|
||||
if (context.platform === 'linux-arm64') {
|
||||
it('server-linux-arm64', async () => {
|
||||
await context.downloadAndUnpack('server-linux-arm64');
|
||||
const dir = await context.downloadAndUnpack('server-linux-arm64');
|
||||
const entryPoint = context.getServerEntryPoint(dir);
|
||||
await testServer(entryPoint);
|
||||
});
|
||||
}
|
||||
|
||||
if (context.platform === 'linux-arm') {
|
||||
it('server-linux-armhf', async () => {
|
||||
await context.downloadAndUnpack('server-linux-armhf');
|
||||
const dir = await context.downloadAndUnpack('server-linux-armhf');
|
||||
const entryPoint = context.getServerEntryPoint(dir);
|
||||
await testServer(entryPoint);
|
||||
});
|
||||
}
|
||||
|
||||
if (context.platform === 'linux-x64') {
|
||||
it('server-linux-x64', async () => {
|
||||
await context.downloadAndUnpack('server-linux-x64');
|
||||
const dir = await context.downloadAndUnpack('server-linux-x64');
|
||||
const entryPoint = context.getServerEntryPoint(dir);
|
||||
await testServer(entryPoint);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -53,6 +69,8 @@ export function setup(context: TestContext) {
|
||||
it('server-win32-arm64', async () => {
|
||||
const dir = await context.downloadAndUnpack('server-win32-arm64');
|
||||
context.validateAllSignatures(dir);
|
||||
const entryPoint = context.getServerEntryPoint(dir);
|
||||
await testServer(entryPoint);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -60,6 +78,48 @@ export function setup(context: TestContext) {
|
||||
it('server-win32-x64', async () => {
|
||||
const dir = await context.downloadAndUnpack('server-win32-x64');
|
||||
context.validateAllSignatures(dir);
|
||||
const entryPoint = context.getServerEntryPoint(dir);
|
||||
await testServer(entryPoint);
|
||||
});
|
||||
}
|
||||
|
||||
async function testServer(entryPoint: string) {
|
||||
const args = ['--accept-server-license-terms', '--connection-token', '12345'];
|
||||
|
||||
context.log(`Starting server ${entryPoint} with args ${args.join(' ')}`);
|
||||
const server = spawn(entryPoint, args, { shell: true });
|
||||
|
||||
server.stderr.on('data', (data) => {
|
||||
context.error(`[Server Error] ${data.toString().trim()}`);
|
||||
});
|
||||
|
||||
server.stdout.on('data', (data) => {
|
||||
const text = data.toString().trim();
|
||||
text.split('\n').forEach((line: string) => {
|
||||
context.log(`[Server Output] ${line}`);
|
||||
});
|
||||
|
||||
const port = /Extension host agent listening on (\d+)/.exec(text)?.[1];
|
||||
if (port) {
|
||||
(async function () {
|
||||
try {
|
||||
const url = `http://localhost:${port}/version`;
|
||||
context.log(`Fetching ${url}`);
|
||||
const response = await fetch(url);
|
||||
assert.equal(response.status, 200);
|
||||
assert.equal(await response.text(), context.commit);
|
||||
} catch (error) {
|
||||
assert.fail(error instanceof Error ? error.message : String(error));
|
||||
} finally {
|
||||
context.killProcessTree(server.pid!);
|
||||
}
|
||||
})();
|
||||
}
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
server.on('error', reject);
|
||||
server.on('exit', resolve);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,63 +3,147 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import assert from 'assert';
|
||||
import { spawn } from 'child_process';
|
||||
import path from 'path';
|
||||
import { TestContext } from './context';
|
||||
import { UITest } from './uiTest';
|
||||
|
||||
export function setup(context: TestContext) {
|
||||
describe('Server Web', () => {
|
||||
if (context.platform === 'linux-arm64') {
|
||||
it('server-alpine-arm64-web', async () => {
|
||||
await context.downloadAndUnpack('server-alpine-arm64-web');
|
||||
it('server-web-alpine-arm64', async () => {
|
||||
const dir = await context.downloadAndUnpack('server-alpine-arm64-web');
|
||||
const entryPoint = context.getServerEntryPoint(dir);
|
||||
await testServer(entryPoint);
|
||||
});
|
||||
}
|
||||
|
||||
if (context.platform === 'linux-x64') {
|
||||
it('server-alpine-x64-web', async () => {
|
||||
await context.downloadAndUnpack('server-linux-alpine-web');
|
||||
it('server-web-alpine-x64', async () => {
|
||||
const dir = await context.downloadAndUnpack('server-linux-alpine-web');
|
||||
const entryPoint = context.getServerEntryPoint(dir);
|
||||
await testServer(entryPoint);
|
||||
});
|
||||
}
|
||||
|
||||
if (context.platform === 'darwin-arm64') {
|
||||
it('server-darwin-arm64-web', async () => {
|
||||
await context.downloadAndUnpack('server-darwin-arm64-web');
|
||||
it('server-web-darwin-arm64', async () => {
|
||||
const dir = await context.downloadAndUnpack('server-darwin-arm64-web');
|
||||
const entryPoint = context.getServerEntryPoint(dir);
|
||||
await testServer(entryPoint);
|
||||
});
|
||||
}
|
||||
|
||||
if (context.platform === 'darwin-x64') {
|
||||
it('server-darwin-x64-web', async () => {
|
||||
await context.downloadAndUnpack('server-darwin-web');
|
||||
it('server-web-darwin-x64', async () => {
|
||||
const dir = await context.downloadAndUnpack('server-darwin-web');
|
||||
const entryPoint = context.getServerEntryPoint(dir);
|
||||
await testServer(entryPoint);
|
||||
});
|
||||
}
|
||||
|
||||
if (context.platform === 'linux-arm64') {
|
||||
it('server-linux-arm64-web', async () => {
|
||||
await context.downloadAndUnpack('server-linux-arm64-web');
|
||||
it('server-web-linux-arm64', async () => {
|
||||
const dir = await context.downloadAndUnpack('server-linux-arm64-web');
|
||||
const entryPoint = context.getServerEntryPoint(dir);
|
||||
await testServer(entryPoint);
|
||||
});
|
||||
}
|
||||
|
||||
if (context.platform === 'linux-arm') {
|
||||
it('server-linux-armhf-web', async () => {
|
||||
await context.downloadAndUnpack('server-linux-armhf-web');
|
||||
it('server-web-linux-armhf', async () => {
|
||||
const dir = await context.downloadAndUnpack('server-linux-armhf-web');
|
||||
const entryPoint = context.getServerEntryPoint(dir);
|
||||
await testServer(entryPoint);
|
||||
});
|
||||
}
|
||||
|
||||
if (context.platform === 'linux-x64') {
|
||||
it('server-linux-x64-web', async () => {
|
||||
await context.downloadAndUnpack('server-linux-x64-web');
|
||||
it('server-web-linux-x64', async () => {
|
||||
const dir = await context.downloadAndUnpack('server-linux-x64-web');
|
||||
const entryPoint = context.getServerEntryPoint(dir);
|
||||
await testServer(entryPoint);
|
||||
});
|
||||
}
|
||||
|
||||
if (context.platform === 'win32-arm64') {
|
||||
it('server-win32-arm64-web', async () => {
|
||||
it('server-web-win32-arm64', async () => {
|
||||
const dir = await context.downloadAndUnpack('server-win32-arm64-web');
|
||||
context.validateAllSignatures(dir);
|
||||
const entryPoint = context.getServerEntryPoint(dir);
|
||||
await testServer(entryPoint);
|
||||
});
|
||||
}
|
||||
|
||||
if (context.platform === 'win32-x64') {
|
||||
it('server-win32-x64-web', async () => {
|
||||
it('server-web-win32-x64', async () => {
|
||||
const dir = await context.downloadAndUnpack('server-win32-x64-web');
|
||||
context.validateAllSignatures(dir);
|
||||
const entryPoint = context.getServerEntryPoint(dir);
|
||||
await testServer(entryPoint);
|
||||
});
|
||||
}
|
||||
|
||||
async function testServer(entryPoint: string) {
|
||||
const token = '12345';
|
||||
const test = new UITest(context);
|
||||
const args = [
|
||||
'--accept-server-license-terms',
|
||||
'--connection-token', token,
|
||||
'--server-data-dir', context.createTempDir(),
|
||||
'--extensions-dir', test.extensionsDir,
|
||||
'--user-data-dir', test.userDataDir
|
||||
];
|
||||
|
||||
context.log(`Starting server ${entryPoint} with args ${args.join(' ')}`);
|
||||
const server = spawn(entryPoint, args, { shell: true });
|
||||
|
||||
server.stderr.on('data', (data) => {
|
||||
context.error(`[Server Error] ${data.toString().trim()}`);
|
||||
});
|
||||
|
||||
server.stdout.on('data', (data) => {
|
||||
const text = data.toString().trim();
|
||||
text.split('\n').forEach((line: string) => {
|
||||
context.log(`[Server Output] ${line}`);
|
||||
});
|
||||
|
||||
const port = /Extension host agent listening on (\d+)/.exec(text)?.[1];
|
||||
if (port) {
|
||||
(async function () {
|
||||
try {
|
||||
const browser = await context.launchBrowser();
|
||||
const page = await browser.newPage();
|
||||
|
||||
const url = `http://localhost:${port}?tkn=${token}&folder=/${test.workspaceDir.replaceAll(path.sep, '/')}`;
|
||||
context.log(`Navigating to ${url}`);
|
||||
await page.goto(url, { waitUntil: 'networkidle' });
|
||||
|
||||
context.log('Waiting for the workbench to load');
|
||||
await page.waitForSelector('.monaco-workbench');
|
||||
|
||||
context.log('Verifying page title contains "Visual Studio Code"');
|
||||
assert.match(await page.title(), /Visual Studio Code/);
|
||||
|
||||
await test.run(page);
|
||||
|
||||
context.log('Closing browser');
|
||||
await browser.close();
|
||||
|
||||
test.validate();
|
||||
} catch (error) {
|
||||
assert.fail(error instanceof Error ? error.message : String(error));
|
||||
} finally {
|
||||
context.killProcessTree(server.pid!);
|
||||
}
|
||||
})();
|
||||
}
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
server.on('error', reject);
|
||||
server.on('exit', resolve);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import assert from 'assert';
|
||||
import fs from 'fs';
|
||||
import { Page } from 'playwright';
|
||||
import { TestContext } from './context';
|
||||
|
||||
/**
|
||||
* UI Test helper class to perform common UI actions and verifications.
|
||||
*/
|
||||
export class UITest {
|
||||
private _extensionsDir: string | undefined;
|
||||
private _workspaceDir: string | undefined;
|
||||
private _userDataDir: string | undefined;
|
||||
|
||||
constructor(private readonly context: TestContext) {
|
||||
}
|
||||
|
||||
/**
|
||||
* The directory where extensions are installed.
|
||||
*/
|
||||
public get extensionsDir(): string {
|
||||
return this._extensionsDir ??= this.context.createTempDir();
|
||||
}
|
||||
|
||||
/**
|
||||
* The workspace directory used for testing.
|
||||
*/
|
||||
public get workspaceDir(): string {
|
||||
return this._workspaceDir ??= this.context.createTempDir();
|
||||
}
|
||||
|
||||
/**
|
||||
* The user data directory used for testing.
|
||||
*/
|
||||
public get userDataDir(): string {
|
||||
return this._userDataDir ??= this.context.createTempDir();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the UI test actions.
|
||||
*/
|
||||
public async run(page: Page) {
|
||||
await this.dismissWorkspaceTrustDialog(page);
|
||||
await this.createTextFile(page);
|
||||
await this.installExtension(page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the results of the UI test actions.
|
||||
*/
|
||||
public async validate() {
|
||||
this.verifyTextFileCreated();
|
||||
this.verifyExtensionInstalled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss the workspace trust dialog.
|
||||
*/
|
||||
private async dismissWorkspaceTrustDialog(page: Page) {
|
||||
this.context.log('Dismissing workspace trust dialog');
|
||||
await page.getByText('Yes, I trust the authors').click();
|
||||
await page.waitForTimeout(500);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new text file in the editor with some content and save it.
|
||||
*/
|
||||
private async createTextFile(page: Page) {
|
||||
this.context.log('Focusing Explorer view');
|
||||
await page.keyboard.press('Control+Shift+E');
|
||||
|
||||
this.context.log('Clicking New File button');
|
||||
await page.getByLabel('New File...').click();
|
||||
|
||||
this.context.log('Typing file name');
|
||||
await page.locator('input').fill('helloWorld.txt');
|
||||
await page.keyboard.press('Enter');
|
||||
|
||||
this.context.log('Focusing the code editor');
|
||||
await page.getByText(/Start typing/).focus();
|
||||
|
||||
this.context.log('Typing some content into the file');
|
||||
await page.keyboard.type('Hello, World!');
|
||||
|
||||
this.context.log('Saving the file');
|
||||
await page.keyboard.press('Control+S');
|
||||
await page.waitForTimeout(1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the text file was created with the expected content.
|
||||
*/
|
||||
private verifyTextFileCreated() {
|
||||
this.context.log('Verifying file contents');
|
||||
const filePath = `${this.workspaceDir}/helloWorld.txt`;
|
||||
const fileContents = fs.readFileSync(filePath, 'utf-8');
|
||||
assert.strictEqual(fileContents, 'Hello, World!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Install GitHub Pull Requests extension from the Extensions view.
|
||||
*/
|
||||
private async installExtension(page: Page) {
|
||||
this.context.log('Opening Extensions view');
|
||||
await page.keyboard.press('Control+Shift+X');
|
||||
await page.waitForSelector('.extension-list-item');
|
||||
|
||||
this.context.log('Typing extension name to search for');
|
||||
await page.keyboard.type('GitHub Pull Requests');
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
this.context.log('Clicking Install on the first extension in the list');
|
||||
await page.locator('.extension-action:not(.disabled)', { hasText: /Install/ }).first().click();
|
||||
|
||||
this.context.log('Waiting for extension to be installed');
|
||||
await page.locator('.extension-action:not(.disabled)', { hasText: /Uninstall/ }).waitFor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the GitHub Pull Requests extension is installed.
|
||||
*/
|
||||
private verifyExtensionInstalled() {
|
||||
this.context.log('Verifying extension is installed');
|
||||
const extensions = fs.readdirSync(this.extensionsDir);
|
||||
const hasExtension = extensions.some(ext => ext.startsWith('github.vscode-pull-request-github'));
|
||||
assert.strictEqual(hasExtension, true);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user