Remove legacy smoke test driver (#147393)

* wip

* fix imports

* more lipstick

* fix?

* fix
This commit is contained in:
Benjamin Pasero 2022-04-13 19:19:58 +02:00 committed by GitHub
parent 675b8c3da0
commit c4cdb552a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 82 additions and 886 deletions

View File

@ -50,7 +50,6 @@ const vscodeEntryPoints = _.flatten([
const vscodeResources = [
'out-build/main.js',
'out-build/cli.js',
'out-build/driver.js',
'out-build/bootstrap.js',
'out-build/bootstrap-fork.js',
'out-build/bootstrap-amd.js',

View File

@ -76,7 +76,6 @@ exports.code = [
createModuleDescription('vs/code/node/cliProcessMain', ['vs/code/node/cli']),
createModuleDescription('vs/code/electron-sandbox/issue/issueReporterMain'),
createModuleDescription('vs/code/electron-browser/sharedProcess/sharedProcessMain'),
createModuleDescription('vs/platform/driver/node/driver'),
createModuleDescription('vs/code/electron-sandbox/processExplorer/processExplorerMain')
];

View File

@ -37,7 +37,6 @@ import { ElectronExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/el
import { IDiagnosticsService } from 'vs/platform/diagnostics/common/diagnostics';
import { DiagnosticsMainService, IDiagnosticsMainService } from 'vs/platform/diagnostics/electron-main/diagnosticsMainService';
import { DialogMainService, IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogMainService';
import { serve as serveDriver } from 'vs/platform/driver/electron-main/driver';
import { IEncryptionMainService } from 'vs/platform/encryption/common/encryptionService';
import { EncryptionMainService } from 'vs/platform/encryption/node/encryptionMainService';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
@ -523,14 +522,6 @@ export class CodeApplication extends Disposable {
// Services
const appInstantiationService = await this.initServices(machineId, sharedProcess, sharedProcessReady);
// Create driver
if (this.environmentMainService.driverHandle) {
const server = await serveDriver(mainProcessElectronServer, this.environmentMainService.driverHandle, appInstantiationService);
this.logService.info('Driver started at:', this.environmentMainService.driverHandle);
this._register(server);
}
// Setup Auth Handler
this._register(appInstantiationService.createInstance(ProxyAuthHandler));

View File

@ -3,8 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
// !! Do not remove the following START and END markers, they are parsed by the smoketest build
//*START
@ -19,14 +17,7 @@ export interface IElement {
}
export interface ILocaleInfo {
/**
* The UI language used.
*/
language: string;
/**
* The requested locale
*/
locale?: string;
}
@ -36,27 +27,6 @@ export interface ILocalizedStrings {
find: string;
}
export interface IDriver {
readonly _serviceBrand: undefined;
getWindowIds(): Promise<number[]>;
startTracing(windowId: number, name: string): Promise<void>;
stopTracing(windowId: number, name: string, persist: boolean): Promise<void>;
exitApplication(): Promise<void>;
dispatchKeybinding(windowId: number, keybinding: string): Promise<void>;
click(windowId: number, selector: string, xoffset?: number | undefined, yoffset?: number | undefined): Promise<void>;
setValue(windowId: number, selector: string, text: string): Promise<void>;
getTitle(windowId: number): Promise<string>;
isActiveElement(windowId: number, selector: string): Promise<boolean>;
getElements(windowId: number, selector: string, recursive?: boolean): Promise<IElement[]>;
getElementXY(windowId: number, selector: string, xoffset?: number, yoffset?: number): Promise<{ x: number; y: number }>;
typeInEditor(windowId: number, selector: string, text: string): Promise<void>;
getTerminalBuffer(windowId: number, selector: string): Promise<string[]>;
writeInTerminal(windowId: number, selector: string, text: string): Promise<void>;
getLocaleInfo(windowId: number): Promise<ILocaleInfo>;
getLocalizedStrings(windowId: number): Promise<ILocalizedStrings>;
}
export interface IWindowDriver {
click(selector: string, xoffset?: number | undefined, yoffset?: number | undefined): Promise<void>;
setValue(selector: string, text: string): Promise<void>;
@ -72,11 +42,3 @@ export interface IWindowDriver {
exitApplication(): Promise<void>;
}
//*END
export const ID = 'driverService';
export const IDriver = createDecorator<IDriver>(ID);
export interface IWindowDriverRegistry {
registerWindowDriver(windowId: number): Promise<void>;
reloadWindowDriver(windowId: number): Promise<void>;
}

View File

@ -1,105 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vs/base/common/event';
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
import { IElement, ILocaleInfo, ILocalizedStrings as ILocalizedStrings, IWindowDriver, IWindowDriverRegistry } from 'vs/platform/driver/common/driver';
export class WindowDriverChannel implements IServerChannel {
constructor(private driver: IWindowDriver) { }
listen<T>(_: unknown, event: string): Event<T> {
throw new Error(`No event found: ${event}`);
}
call(_: unknown, command: string, arg?: any): Promise<any> {
switch (command) {
case 'click': return this.driver.click(arg[0], arg[1], arg[2]);
case 'setValue': return this.driver.setValue(arg[0], arg[1]);
case 'getTitle': return this.driver.getTitle();
case 'isActiveElement': return this.driver.isActiveElement(arg);
case 'getElements': return this.driver.getElements(arg[0], arg[1]);
case 'getElementXY': return this.driver.getElementXY(arg[0], arg[1], arg[2]);
case 'typeInEditor': return this.driver.typeInEditor(arg[0], arg[1]);
case 'getTerminalBuffer': return this.driver.getTerminalBuffer(arg);
case 'writeInTerminal': return this.driver.writeInTerminal(arg[0], arg[1]);
case 'getLocaleInfo': return this.driver.getLocaleInfo();
case 'getLocalizedStrings': return this.driver.getLocalizedStrings();
}
throw new Error(`Call not found: ${command}`);
}
}
export class WindowDriverChannelClient implements IWindowDriver {
declare readonly _serviceBrand: undefined;
constructor(private channel: IChannel) { }
click(selector: string, xoffset?: number, yoffset?: number): Promise<void> {
return this.channel.call('click', [selector, xoffset, yoffset]);
}
setValue(selector: string, text: string): Promise<void> {
return this.channel.call('setValue', [selector, text]);
}
getTitle(): Promise<string> {
return this.channel.call('getTitle');
}
isActiveElement(selector: string): Promise<boolean> {
return this.channel.call('isActiveElement', selector);
}
getElements(selector: string, recursive: boolean): Promise<IElement[]> {
return this.channel.call('getElements', [selector, recursive]);
}
getElementXY(selector: string, xoffset?: number, yoffset?: number): Promise<{ x: number; y: number }> {
return this.channel.call('getElementXY', [selector, xoffset, yoffset]);
}
typeInEditor(selector: string, text: string): Promise<void> {
return this.channel.call('typeInEditor', [selector, text]);
}
getTerminalBuffer(selector: string): Promise<string[]> {
return this.channel.call('getTerminalBuffer', selector);
}
writeInTerminal(selector: string, text: string): Promise<void> {
return this.channel.call('writeInTerminal', [selector, text]);
}
getLocaleInfo(): Promise<ILocaleInfo> {
return this.channel.call('getLocaleInfo');
}
getLocalizedStrings(): Promise<ILocalizedStrings> {
return this.channel.call('getLocalizedStrings');
}
exitApplication(): Promise<void> {
return this.channel.call('exitApplication');
}
}
export class WindowDriverRegistryChannelClient implements IWindowDriverRegistry {
declare readonly _serviceBrand: undefined;
constructor(private channel: IChannel) { }
registerWindowDriver(windowId: number): Promise<void> {
return this.channel.call('registerWindowDriver', windowId);
}
reloadWindowDriver(windowId: number): Promise<void> {
return this.channel.call('reloadWindowDriver', windowId);
}
}

View File

@ -1,244 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { timeout } from 'vs/base/common/async';
import { Emitter, Event } from 'vs/base/common/event';
import { KeybindingParser } from 'vs/base/common/keybindingParser';
import { KeyCode } from 'vs/base/common/keyCodes';
import { SimpleKeybinding, ScanCodeBinding } from 'vs/base/common/keybindings';
import { combinedDisposable, IDisposable } from 'vs/base/common/lifecycle';
import { OS } from 'vs/base/common/platform';
import { IPCServer, StaticRouter } from 'vs/base/parts/ipc/common/ipc';
import { serve as serveNet } from 'vs/base/parts/ipc/node/ipc.net';
import { IDriver, IElement, ILocaleInfo, ILocalizedStrings, IWindowDriver, IWindowDriverRegistry } from 'vs/platform/driver/common/driver';
import { WindowDriverChannelClient } from 'vs/platform/driver/common/driverIpc';
import { DriverChannel, WindowDriverRegistryChannel } from 'vs/platform/driver/node/driver';
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding';
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
import { IFileService } from 'vs/platform/files/common/files';
import { URI } from 'vs/base/common/uri';
import { join } from 'vs/base/common/path';
import { VSBuffer } from 'vs/base/common/buffer';
import { ILogService } from 'vs/platform/log/common/log';
function isSilentKeyCode(keyCode: KeyCode) {
return keyCode < KeyCode.Digit0;
}
export class Driver implements IDriver, IWindowDriverRegistry {
declare readonly _serviceBrand: undefined;
private registeredWindowIds = new Set<number>();
private reloadingWindowIds = new Set<number>();
private readonly onDidReloadingChange = new Emitter<void>();
constructor(
private windowServer: IPCServer,
@IWindowsMainService private readonly windowsMainService: IWindowsMainService,
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
@IFileService private readonly fileService: IFileService,
@IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService,
@ILogService private readonly logService: ILogService
) { }
async registerWindowDriver(windowId: number): Promise<void> {
this.logService.info(`[driver] registerWindowDriver(${windowId})`);
this.registeredWindowIds.add(windowId);
this.reloadingWindowIds.delete(windowId);
this.onDidReloadingChange.fire();
}
async reloadWindowDriver(windowId: number): Promise<void> {
this.logService.info(`[driver] reloadWindowDriver(${windowId})`);
this.reloadingWindowIds.add(windowId);
}
async getWindowIds(): Promise<number[]> {
const windowIds = this.windowsMainService.getWindows()
.map(window => window.id)
.filter(windowId => this.registeredWindowIds.has(windowId) && !this.reloadingWindowIds.has(windowId));
return windowIds;
}
async startTracing(windowId: number, name: string): Promise<void> {
// ignore - tracing is not implemented yet
}
async stopTracing(windowId: number, name: string, persist: boolean): Promise<void> {
if (!persist) {
return;
}
const raw = await this.capturePage(windowId);
const buffer = Buffer.from(raw, 'base64');
await this.fileService.writeFile(URI.file(join(this.environmentMainService.logsPath, `${name}.png`)), VSBuffer.wrap(buffer));
}
private async capturePage(windowId: number): Promise<string> {
const window = this.windowsMainService.getWindowById(windowId) ?? this.windowsMainService.getLastActiveWindow(); // fallback to active window to ensure we capture window
if (!window?.win) {
throw new Error('Invalid window');
}
const webContents = window.win.webContents;
const image = await webContents.capturePage();
return image.toPNG().toString('base64');
}
async exitApplication(): Promise<void> {
this.logService.info(`[driver] exitApplication()`);
await this.lifecycleMainService.quit();
}
async dispatchKeybinding(windowId: number, keybinding: string): Promise<void> {
await this.whenUnfrozen(windowId);
const parts = KeybindingParser.parseUserBinding(keybinding);
for (let part of parts) {
await this._dispatchKeybinding(windowId, part);
}
}
private async _dispatchKeybinding(windowId: number, keybinding: SimpleKeybinding | ScanCodeBinding): Promise<void> {
if (keybinding instanceof ScanCodeBinding) {
throw new Error('ScanCodeBindings not supported');
}
const window = this.windowsMainService.getWindowById(windowId);
if (!window?.win) {
throw new Error('Invalid window');
}
const webContents = window.win.webContents;
const noModifiedKeybinding = new SimpleKeybinding(false, false, false, false, keybinding.keyCode);
const resolvedKeybinding = new USLayoutResolvedKeybinding(noModifiedKeybinding.toChord(), OS);
const keyCode = resolvedKeybinding.getElectronAccelerator();
const modifiers: string[] = [];
if (keybinding.ctrlKey) {
modifiers.push('ctrl');
}
if (keybinding.metaKey) {
modifiers.push('meta');
}
if (keybinding.shiftKey) {
modifiers.push('shift');
}
if (keybinding.altKey) {
modifiers.push('alt');
}
webContents.sendInputEvent({ type: 'keyDown', keyCode, modifiers } as any);
if (!isSilentKeyCode(keybinding.keyCode)) {
webContents.sendInputEvent({ type: 'char', keyCode, modifiers } as any);
}
webContents.sendInputEvent({ type: 'keyUp', keyCode, modifiers } as any);
await timeout(100);
}
async click(windowId: number, selector: string, xoffset?: number, yoffset?: number): Promise<void> {
const windowDriver = await this.getWindowDriver(windowId);
await windowDriver.click(selector, xoffset, yoffset);
}
async setValue(windowId: number, selector: string, text: string): Promise<void> {
const windowDriver = await this.getWindowDriver(windowId);
await windowDriver.setValue(selector, text);
}
async getTitle(windowId: number): Promise<string> {
const windowDriver = await this.getWindowDriver(windowId);
return await windowDriver.getTitle();
}
async isActiveElement(windowId: number, selector: string): Promise<boolean> {
const windowDriver = await this.getWindowDriver(windowId);
return await windowDriver.isActiveElement(selector);
}
async getElements(windowId: number, selector: string, recursive: boolean): Promise<IElement[]> {
const windowDriver = await this.getWindowDriver(windowId);
return await windowDriver.getElements(selector, recursive);
}
async getElementXY(windowId: number, selector: string, xoffset?: number, yoffset?: number): Promise<{ x: number; y: number }> {
const windowDriver = await this.getWindowDriver(windowId);
return await windowDriver.getElementXY(selector, xoffset, yoffset);
}
async typeInEditor(windowId: number, selector: string, text: string): Promise<void> {
const windowDriver = await this.getWindowDriver(windowId);
await windowDriver.typeInEditor(selector, text);
}
async getTerminalBuffer(windowId: number, selector: string): Promise<string[]> {
const windowDriver = await this.getWindowDriver(windowId);
return await windowDriver.getTerminalBuffer(selector);
}
async writeInTerminal(windowId: number, selector: string, text: string): Promise<void> {
const windowDriver = await this.getWindowDriver(windowId);
await windowDriver.writeInTerminal(selector, text);
}
async getLocaleInfo(windowId: number): Promise<ILocaleInfo> {
const windowDriver = await this.getWindowDriver(windowId);
return await windowDriver.getLocaleInfo();
}
async getLocalizedStrings(windowId: number): Promise<ILocalizedStrings> {
const windowDriver = await this.getWindowDriver(windowId);
return await windowDriver.getLocalizedStrings();
}
private async getWindowDriver(windowId: number): Promise<IWindowDriver> {
await this.whenUnfrozen(windowId);
const id = `window:${windowId}`;
const router = new StaticRouter(ctx => ctx === id);
const windowDriverChannel = this.windowServer.getChannel('windowDriver', router);
return new WindowDriverChannelClient(windowDriverChannel);
}
private async whenUnfrozen(windowId: number): Promise<void> {
while (this.reloadingWindowIds.has(windowId)) {
await Event.toPromise(this.onDidReloadingChange.event);
}
}
}
export async function serve(
windowServer: IPCServer,
handle: string,
instantiationService: IInstantiationService
): Promise<IDisposable> {
const driver = instantiationService.createInstance(Driver, windowServer);
const windowDriverRegistryChannel = new WindowDriverRegistryChannel(driver);
windowServer.registerChannel('windowDriverRegistry', windowDriverRegistryChannel);
const server = await serveNet(handle);
const channel = new DriverChannel(driver);
server.registerChannel('driver', channel);
return combinedDisposable(server, windowServer);
}

View File

@ -3,13 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { timeout } from 'vs/base/common/async';
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { BrowserWindowDriver } from 'vs/platform/driver/browser/driver';
import { WindowDriverChannel, WindowDriverRegistryChannelClient } from 'vs/platform/driver/common/driverIpc';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services';
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
interface INativeWindowDriverHelper {
exitApplication(): Promise<void>;
@ -29,50 +23,3 @@ class NativeWindowDriver extends BrowserWindowDriver {
export function registerWindowDriver(helper: INativeWindowDriverHelper): void {
Object.assign(window, { driver: new NativeWindowDriver(helper) });
}
class LegacyNativeWindowDriver extends BrowserWindowDriver {
constructor(
@INativeHostService private readonly nativeHostService: INativeHostService
) {
super();
}
override click(selector: string, xoffset?: number, yoffset?: number): Promise<void> {
const offset = typeof xoffset === 'number' && typeof yoffset === 'number' ? { x: xoffset, y: yoffset } : undefined;
return this.doClick(selector, 1, offset);
}
private async doClick(selector: string, clickCount: number, offset?: { x: number; y: number }): Promise<void> {
const { x, y } = await this._getElementXY(selector, offset);
await this.nativeHostService.sendInputEvent({ type: 'mouseDown', x, y, button: 'left', clickCount } as any);
await timeout(10);
await this.nativeHostService.sendInputEvent({ type: 'mouseUp', x, y, button: 'left', clickCount } as any);
await timeout(100);
}
}
/**
* Old school window driver that is implemented by us
* from the main process.
*
* @deprecated
*/
export async function registerLegacyWindowDriver(accessor: ServicesAccessor, windowId: number): Promise<IDisposable> {
const instantiationService = accessor.get(IInstantiationService);
const mainProcessService = accessor.get(IMainProcessService);
const windowDriver = instantiationService.createInstance(LegacyNativeWindowDriver);
const windowDriverChannel = new WindowDriverChannel(windowDriver);
mainProcessService.registerChannel('windowDriver', windowDriverChannel);
const windowDriverRegistryChannel = mainProcessService.getChannel('windowDriverRegistry');
const windowDriverRegistry = new WindowDriverRegistryChannelClient(windowDriverRegistryChannel);
await windowDriverRegistry.registerWindowDriver(windowId);
return toDisposable(() => windowDriverRegistry.reloadWindowDriver(windowId));
}

View File

@ -1,138 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vs/base/common/event';
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
import { Client } from 'vs/base/parts/ipc/common/ipc.net';
import { connect as connectNet } from 'vs/base/parts/ipc/node/ipc.net';
import { IDriver, IElement, ILocaleInfo, ILocalizedStrings, IWindowDriverRegistry } from 'vs/platform/driver/common/driver';
export class DriverChannel implements IServerChannel {
constructor(private driver: IDriver) { }
listen<T>(_: unknown, event: string): Event<T> {
throw new Error('No event found');
}
call(_: unknown, command: string, arg?: any): Promise<any> {
switch (command) {
case 'getWindowIds': return this.driver.getWindowIds();
case 'startTracing': return this.driver.startTracing(arg[0], arg[1]);
case 'stopTracing': return this.driver.stopTracing(arg[0], arg[1], arg[2]);
case 'exitApplication': return this.driver.exitApplication();
case 'dispatchKeybinding': return this.driver.dispatchKeybinding(arg[0], arg[1]);
case 'click': return this.driver.click(arg[0], arg[1], arg[2], arg[3]);
case 'setValue': return this.driver.setValue(arg[0], arg[1], arg[2]);
case 'getTitle': return this.driver.getTitle(arg[0]);
case 'isActiveElement': return this.driver.isActiveElement(arg[0], arg[1]);
case 'getElements': return this.driver.getElements(arg[0], arg[1], arg[2]);
case 'getElementXY': return this.driver.getElementXY(arg[0], arg[1], arg[2]);
case 'typeInEditor': return this.driver.typeInEditor(arg[0], arg[1], arg[2]);
case 'getTerminalBuffer': return this.driver.getTerminalBuffer(arg[0], arg[1]);
case 'writeInTerminal': return this.driver.writeInTerminal(arg[0], arg[1], arg[2]);
case 'getLocaleInfo': return this.driver.getLocaleInfo(arg);
case 'getLocalizedStrings': return this.driver.getLocalizedStrings(arg);
}
throw new Error(`Call not found: ${command}`);
}
}
export class DriverChannelClient implements IDriver {
declare readonly _serviceBrand: undefined;
constructor(private channel: IChannel) { }
getWindowIds(): Promise<number[]> {
return this.channel.call('getWindowIds');
}
startTracing(windowId: number, name: string): Promise<void> {
return this.channel.call('startTracing', [windowId, name]);
}
stopTracing(windowId: number, name: string, persist: boolean): Promise<void> {
return this.channel.call('stopTracing', [windowId, name, persist]);
}
exitApplication(): Promise<void> {
return this.channel.call('exitApplication');
}
dispatchKeybinding(windowId: number, keybinding: string): Promise<void> {
return this.channel.call('dispatchKeybinding', [windowId, keybinding]);
}
click(windowId: number, selector: string, xoffset: number | undefined, yoffset: number | undefined): Promise<void> {
return this.channel.call('click', [windowId, selector, xoffset, yoffset]);
}
setValue(windowId: number, selector: string, text: string): Promise<void> {
return this.channel.call('setValue', [windowId, selector, text]);
}
getTitle(windowId: number): Promise<string> {
return this.channel.call('getTitle', [windowId]);
}
isActiveElement(windowId: number, selector: string): Promise<boolean> {
return this.channel.call('isActiveElement', [windowId, selector]);
}
getElements(windowId: number, selector: string, recursive: boolean): Promise<IElement[]> {
return this.channel.call('getElements', [windowId, selector, recursive]);
}
getElementXY(windowId: number, selector: string, xoffset: number | undefined, yoffset: number | undefined): Promise<{ x: number; y: number }> {
return this.channel.call('getElementXY', [windowId, selector, xoffset, yoffset]);
}
typeInEditor(windowId: number, selector: string, text: string): Promise<void> {
return this.channel.call('typeInEditor', [windowId, selector, text]);
}
getTerminalBuffer(windowId: number, selector: string): Promise<string[]> {
return this.channel.call('getTerminalBuffer', [windowId, selector]);
}
writeInTerminal(windowId: number, selector: string, text: string): Promise<void> {
return this.channel.call('writeInTerminal', [windowId, selector, text]);
}
getLocaleInfo(windowId: number): Promise<ILocaleInfo> {
return this.channel.call('getLocaleInfo', windowId);
}
getLocalizedStrings(windowId: number): Promise<ILocalizedStrings> {
return this.channel.call('getLocalizedStrings', windowId);
}
}
export class WindowDriverRegistryChannel implements IServerChannel {
constructor(private registry: IWindowDriverRegistry) { }
listen<T>(_: unknown, event: string): Event<T> {
throw new Error(`Event not found: ${event}`);
}
call(_: unknown, command: string, arg?: any): Promise<any> {
switch (command) {
case 'registerWindowDriver': return this.registry.registerWindowDriver(arg);
case 'reloadWindowDriver': return this.registry.reloadWindowDriver(arg);
}
throw new Error(`Call not found: ${command}`);
}
}
export async function connect(handle: string): Promise<{ client: Client; driver: IDriver }> {
const client = await connectNet(handle, 'driverClient');
const channel = client.getChannel('driver');
const driver = new DriverChannelClient(channel);
return { client, driver };
}

View File

@ -79,10 +79,6 @@ export interface NativeParsedArgs {
'max-memory'?: string;
'file-write'?: boolean;
'file-chmod'?: boolean;
/**
* @deprecated use `enable-smoke-test-driver`
*/
'driver'?: string;
'enable-smoke-test-driver'?: boolean;
'remote'?: string;
'force'?: boolean;

View File

@ -132,9 +132,6 @@ export interface INativeEnvironmentService extends IEnvironmentService {
extensionsDownloadPath: string;
builtinExtensionsPath: string;
// --- smoke test support
driverHandle?: string;
// --- use keytar for credentials
disableKeytar?: boolean;

View File

@ -230,8 +230,6 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron
get crashReporterId(): string | undefined { return this.args['crash-reporter-id']; }
get crashReporterDirectory(): string | undefined { return this.args['crash-reporter-directory']; }
get driverHandle(): string | undefined { return this.args['driver']; }
@memoize
get telemetryLogResource(): URI { return URI.file(join(this.logsPath, 'telemetry.log')); }
get disableTelemetry(): boolean { return !!this.args['disable-telemetry']; }

View File

@ -98,7 +98,6 @@ export const OPTIONS: OptionDescriptions<Required<NativeParsedArgs>> = {
'inspect-brk-search': { type: 'string', deprecates: ['debugBrkSearch'] },
'export-default-configuration': { type: 'string' },
'install-source': { type: 'string' },
'driver': { type: 'string' },
'enable-smoke-test-driver': { type: 'boolean' },
'logExtensionHostCommunication': { type: 'boolean' },
'skip-release-notes': { type: 'boolean' },

View File

@ -545,7 +545,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
}
// If we run smoke tests, we never want to show a blocking dialog
if (this.environmentMainService.driverHandle) {
if (this.environmentMainService.args['enable-smoke-test-driver']) {
this.destroyWindow(false);
return;
}

View File

@ -63,7 +63,7 @@ import { whenEditorClosed } from 'vs/workbench/browser/editor';
import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services';
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { registerLegacyWindowDriver, registerWindowDriver } from 'vs/platform/driver/electron-sandbox/driver';
import { registerWindowDriver } from 'vs/platform/driver/electron-sandbox/driver';
export class NativeWindow extends Disposable {
@ -640,25 +640,18 @@ export class NativeWindow extends Disposable {
}
// Smoke Test Driver
this.setupDriver();
if (this.environmentService.enableSmokeTestDriver) {
this.setupDriver();
}
}
private setupDriver(): void {
// Modern Driver
if (this.environmentService.enableSmokeTestDriver) {
const that = this;
registerWindowDriver({
async exitApplication(): Promise<void> {
return that.nativeHostService.quit();
}
});
}
// Legacy Driver (TODO@bpasero remove me eventually)
else if (this.environmentService.args.driver) {
this.instantiationService.invokeFunction(async accessor => this._register(await registerLegacyWindowDriver(accessor, this.nativeHostService.windowId)));
}
const that = this;
registerWindowDriver({
async exitApplication(): Promise<void> {
return that.nativeHostService.quit();
}
});
}
private setupOpenHandlers(): void {

View File

@ -9,13 +9,11 @@
"main": "./out/index.js",
"private": true,
"scripts": {
"compile": "npm run copy-driver && npm run copy-driver-definition && node ../../node_modules/typescript/bin/tsc",
"watch": "npm-run-all -lp watch-driver watch-driver-definition watch-tsc",
"compile": "npm run copy-driver-definition && node ../../node_modules/typescript/bin/tsc",
"watch": "npm-run-all -lp watch-driver-definition watch-tsc",
"watch-tsc": "node ../../node_modules/typescript/bin/tsc --watch --preserveWatchOutput",
"copy-driver": "cpx src/driver.js out/",
"watch-driver": "cpx src/driver.js out/ -w",
"copy-driver-definition": "node tools/copy-driver-definition.js",
"watch-driver-definition": "watch \"node tools/copy-driver-definition.js\" ../../src/vs/platform/driver/node",
"watch-driver-definition": "watch \"node tools/copy-driver-definition.js\"",
"copy-package-version": "node tools/copy-package-version.js",
"prepublishOnly": "npm run copy-package-version"
},

View File

@ -16,8 +16,7 @@ export const enum Quality {
export interface ApplicationOptions extends LaunchOptions {
quality: Quality;
workspacePath: string;
waitTime: number;
readonly workspacePath: string;
}
export class Application {
@ -49,10 +48,6 @@ export class Application {
return !!this.options.web;
}
get legacy(): boolean {
return !!this.options.legacy;
}
private _workspacePathOrFolder: string;
get workspacePathOrFolder(): string {
return this._workspacePathOrFolder;
@ -118,9 +113,6 @@ export class Application {
private async checkWindowReady(code: Code): Promise<void> {
// This is legacy and will be removed when our old driver removes
await code.waitForWindowIds(ids => ids.length > 0);
// We need a rendered workbench
await this.checkWorkbenchReady(code);

View File

@ -6,14 +6,14 @@
import { join } from 'path';
import * as os from 'os';
import * as cp from 'child_process';
import { IDriver, IDisposable, IElement, Thenable, ILocalizedStrings, ILocaleInfo } from './driver';
import { launch as launchElectron } from './electron';
import { IElement, ILocalizedStrings, ILocaleInfo } from './driver';
import { launch as launchPlaywrightBrowser } from './playwrightBrowser';
import { launch as launchPlaywrightElectron } from './playwrightElectron';
import { Logger, measureAndLog } from './logger';
import { copyExtension } from './extensions';
import * as treekill from 'tree-kill';
import { teardown } from './processes';
import { PlaywrightDriver } from './playwrightDriver';
const rootPath = join(__dirname, '../../..');
@ -28,7 +28,6 @@ export interface LaunchOptions {
readonly extraArgs?: string[];
readonly remote?: boolean;
readonly web?: boolean;
readonly legacy?: boolean;
readonly tracing?: boolean;
readonly headless?: boolean;
readonly browser?: 'chromium' | 'webkit' | 'firefox';
@ -80,37 +79,27 @@ export async function launch(options: LaunchOptions): Promise<Code> {
// Browser smoke tests
if (options.web) {
const { serverProcess, client, driver } = await measureAndLog(launchPlaywrightBrowser(options), 'launch playwright (browser)', options.logger);
const { serverProcess, driver } = await measureAndLog(launchPlaywrightBrowser(options), 'launch playwright (browser)', options.logger);
registerInstance(serverProcess, options.logger, 'server');
return new Code(client, driver, options.logger, serverProcess);
return new Code(driver, options.logger, serverProcess);
}
// Electron smoke tests (playwright)
else if (!options.legacy) {
const { electronProcess, client, driver } = await measureAndLog(launchPlaywrightElectron(options), 'launch playwright (electron)', options.logger);
registerInstance(electronProcess, options.logger, 'electron');
return new Code(client, driver, options.logger, electronProcess);
}
// Electron smoke tests (legacy driver)
else {
const { electronProcess, client, driver } = await measureAndLog(launchElectron(options), 'launch electron', options.logger);
const { electronProcess, driver } = await measureAndLog(launchPlaywrightElectron(options), 'launch playwright (electron)', options.logger);
registerInstance(electronProcess, options.logger, 'electron');
return new Code(client, driver, options.logger, electronProcess);
return new Code(driver, options.logger, electronProcess);
}
}
export class Code {
private _activeWindowId: number | undefined = undefined;
readonly driver: IDriver;
readonly driver: PlaywrightDriver;
constructor(
private client: IDisposable,
driver: IDriver,
driver: PlaywrightDriver,
readonly logger: Logger,
private readonly mainProcess: cp.ChildProcess
) {
@ -134,22 +123,15 @@ export class Code {
}
async startTracing(name: string): Promise<void> {
const windowId = await this.getActiveWindowId();
return await this.driver.startTracing(windowId, name);
return await this.driver.startTracing(name);
}
async stopTracing(name: string, persist: boolean): Promise<void> {
const windowId = await this.getActiveWindowId();
return await this.driver.stopTracing(windowId, name, persist);
}
async waitForWindowIds(accept: (windowIds: number[]) => boolean): Promise<void> {
await this.poll(() => this.driver.getWindowIds(), accept, `get window ids`);
return await this.driver.stopTracing(name, persist);
}
async dispatchKeybinding(keybinding: string): Promise<void> {
const windowId = await this.getActiveWindowId();
await this.driver.dispatchKeybinding(windowId, keybinding);
await this.driver.dispatchKeybinding(keybinding);
}
async exit(): Promise<void> {
@ -195,17 +177,14 @@ export class Code {
}
}
})();
}).finally(() => {
this.dispose();
}), 'Code#exit()', this.logger);
}
async waitForTextContent(selector: string, textContent?: string, accept?: (result: string) => boolean, retryCount?: number): Promise<string> {
const windowId = await this.getActiveWindowId();
accept = accept || (result => textContent !== undefined ? textContent === result : !!result);
return await this.poll(
() => this.driver.getElements(windowId, selector).then(els => els.length > 0 ? Promise.resolve(els[0].textContent) : Promise.reject(new Error('Element not found for textContent'))),
() => this.driver.getElements(selector).then(els => els.length > 0 ? Promise.resolve(els[0].textContent) : Promise.reject(new Error('Element not found for textContent'))),
s => accept!(typeof s === 'string' ? s : ''),
`get text content '${selector}'`,
retryCount
@ -213,77 +192,51 @@ export class Code {
}
async waitAndClick(selector: string, xoffset?: number, yoffset?: number, retryCount: number = 200): Promise<void> {
const windowId = await this.getActiveWindowId();
await this.poll(() => this.driver.click(windowId, selector, xoffset, yoffset), () => true, `click '${selector}'`, retryCount);
await this.poll(() => this.driver.click(selector, xoffset, yoffset), () => true, `click '${selector}'`, retryCount);
}
async waitForSetValue(selector: string, value: string): Promise<void> {
const windowId = await this.getActiveWindowId();
await this.poll(() => this.driver.setValue(windowId, selector, value), () => true, `set value '${selector}'`);
await this.poll(() => this.driver.setValue(selector, value), () => true, `set value '${selector}'`);
}
async waitForElements(selector: string, recursive: boolean, accept: (result: IElement[]) => boolean = result => result.length > 0): Promise<IElement[]> {
const windowId = await this.getActiveWindowId();
return await this.poll(() => this.driver.getElements(windowId, selector, recursive), accept, `get elements '${selector}'`);
return await this.poll(() => this.driver.getElements(selector, recursive), accept, `get elements '${selector}'`);
}
async waitForElement(selector: string, accept: (result: IElement | undefined) => boolean = result => !!result, retryCount: number = 200): Promise<IElement> {
const windowId = await this.getActiveWindowId();
return await this.poll<IElement>(() => this.driver.getElements(windowId, selector).then(els => els[0]), accept, `get element '${selector}'`, retryCount);
return await this.poll<IElement>(() => this.driver.getElements(selector).then(els => els[0]), accept, `get element '${selector}'`, retryCount);
}
async waitForActiveElement(selector: string, retryCount: number = 200): Promise<void> {
const windowId = await this.getActiveWindowId();
await this.poll(() => this.driver.isActiveElement(windowId, selector), r => r, `is active element '${selector}'`, retryCount);
await this.poll(() => this.driver.isActiveElement(selector), r => r, `is active element '${selector}'`, retryCount);
}
async waitForTitle(accept: (title: string) => boolean): Promise<void> {
const windowId = await this.getActiveWindowId();
await this.poll(() => this.driver.getTitle(windowId), accept, `get title`);
await this.poll(() => this.driver.getTitle(), accept, `get title`);
}
async waitForTypeInEditor(selector: string, text: string): Promise<void> {
const windowId = await this.getActiveWindowId();
await this.poll(() => this.driver.typeInEditor(windowId, selector, text), () => true, `type in editor '${selector}'`);
await this.poll(() => this.driver.typeInEditor(selector, text), () => true, `type in editor '${selector}'`);
}
async waitForTerminalBuffer(selector: string, accept: (result: string[]) => boolean): Promise<void> {
const windowId = await this.getActiveWindowId();
await this.poll(() => this.driver.getTerminalBuffer(windowId, selector), accept, `get terminal buffer '${selector}'`);
await this.poll(() => this.driver.getTerminalBuffer(selector), accept, `get terminal buffer '${selector}'`);
}
async writeInTerminal(selector: string, value: string): Promise<void> {
const windowId = await this.getActiveWindowId();
await this.poll(() => this.driver.writeInTerminal(windowId, selector, value), () => true, `writeInTerminal '${selector}'`);
await this.poll(() => this.driver.writeInTerminal(selector, value), () => true, `writeInTerminal '${selector}'`);
}
async getLocaleInfo(): Promise<ILocaleInfo> {
const windowId = await this.getActiveWindowId();
return this.driver.getLocaleInfo(windowId);
return this.driver.getLocaleInfo();
}
async getLocalizedStrings(): Promise<ILocalizedStrings> {
const windowId = await this.getActiveWindowId();
return this.driver.getLocalizedStrings(windowId);
}
private async getActiveWindowId(): Promise<number> {
if (typeof this._activeWindowId !== 'number') {
this.logger.log('getActiveWindowId(): begin');
const windows = await this.driver.getWindowIds();
this._activeWindowId = windows[0];
this.logger.log(`getActiveWindowId(): end (windowId=${this._activeWindowId})`);
}
return this._activeWindowId;
}
dispose(): void {
this.client.dispose();
return this.driver.getLocalizedStrings();
}
private async poll<T>(
fn: () => Thenable<T>,
fn: () => Promise<T>,
acceptFn: (result: T) => boolean,
timeoutMessage: string,
retryCount = 200,

View File

@ -8,7 +8,7 @@ import { Commands } from './workbench';
import { Code, findElement } from './code';
import { Editors } from './editors';
import { Editor } from './editor';
import { IElement } from '../src/driver';
import { IElement } from './driver';
const VIEWLET = 'div[id="workbench.view.debug"]';
const DEBUG_VIEW = `${VIEWLET}`;

View File

@ -1,16 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
//@ts-check
'use strict';
const path = require('path');
exports.connect = function (outPath, handle) {
const bootstrapPath = path.join(outPath, 'bootstrap-amd.js');
const { load } = require(bootstrapPath);
return new Promise((resolve, reject) => load('vs/platform/driver/node/driver', ({ connect }) => connect(handle).then(resolve, reject), reject));
};

View File

@ -4,13 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { join } from 'path';
import { platform } from 'os';
import { tmpName } from 'tmp';
import { connect as connectElectronDriver, IDisposable, IDriver } from './driver';
import { ChildProcess, spawn, SpawnOptions } from 'child_process';
import * as mkdirp from 'mkdirp';
import { promisify } from 'util';
import { teardown } from './processes';
import { copyExtension } from './extensions';
import { URI } from 'vscode-uri';
import { measureAndLog } from './logger';
@ -96,61 +90,6 @@ export async function resolveElectronConfiguration(options: LaunchOptions): Prom
};
}
/**
* @deprecated should use the playwright based electron support instead
*/
export async function launch(options: LaunchOptions): Promise<{ electronProcess: ChildProcess; client: IDisposable; driver: IDriver }> {
const { codePath, logger, verbose } = options;
const { env, args, electronPath } = await resolveElectronConfiguration(options);
const driverIPCHandle = await measureAndLog(createDriverHandle(), 'createDriverHandle', logger);
args.push('--driver', driverIPCHandle);
const outPath = codePath ? getBuildOutPath(codePath) : getDevOutPath();
const spawnOptions: SpawnOptions = { env };
if (verbose) {
spawnOptions.stdio = ['ignore', 'inherit', 'inherit'];
}
const electronProcess = spawn(electronPath, args, spawnOptions);
logger.log(`Started electron for desktop smoke tests on pid ${electronProcess.pid}`);
let retries = 0;
while (true) {
try {
const { client, driver } = await measureAndLog(connectElectronDriver(outPath, driverIPCHandle), 'connectElectronDriver()', logger);
return {
electronProcess,
client,
driver
};
} catch (err) {
// give up
if (++retries > 30) {
logger.log(`Error connecting driver: ${err}. Giving up...`);
await measureAndLog(teardown(electronProcess, logger), 'Kill Electron after failing to connect', logger);
throw err;
}
// retry
else {
if ((err as NodeJS.ErrnoException).code !== 'ENOENT' /* ENOENT is expected for as long as the server has not started on the socket */) {
logger.log(`Error connecting driver: ${err}. Attempting to retry...`);
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
}
}
export function getDevElectronPath(): string {
const buildPath = join(root, '.build');
const product = require(join(root, 'product.json'));
@ -192,28 +131,3 @@ export function getBuildVersion(root: string): string {
return require(join(root, 'resources', 'app', 'package.json')).version;
}
}
function getDevOutPath(): string {
return join(root, 'out');
}
function getBuildOutPath(root: string): string {
switch (process.platform) {
case 'darwin':
return join(root, 'Contents', 'Resources', 'app', 'out');
default:
return join(root, 'resources', 'app', 'out');
}
}
async function createDriverHandle(): Promise<string> {
// Windows
if ('win32' === platform()) {
const name = [...Array(15)].map(() => Math.random().toString(36)[3]).join('');
return `\\\\.\\pipe\\${name}`;
}
// Posix
return promisify(tmpName)();
}

View File

@ -25,5 +25,4 @@ export * from './terminal';
export * from './viewlet';
export * from './localization';
export * from './workbench';
export * from './driver';
export { getDevElectronPath, getBuildElectronPath, getBuildVersion } from './electron';

View File

@ -8,7 +8,6 @@ import { ChildProcess, spawn } from 'child_process';
import { join } from 'path';
import { mkdir } from 'fs';
import { promisify } from 'util';
import { IDriver, IDisposable } from './driver';
import { URI } from 'vscode-uri';
import { Logger, measureAndLog } from './logger';
import type { LaunchOptions } from './code';
@ -18,7 +17,7 @@ const root = join(__dirname, '..', '..', '..');
let port = 9000;
export async function launch(options: LaunchOptions): Promise<{ serverProcess: ChildProcess; client: IDisposable; driver: IDriver }> {
export async function launch(options: LaunchOptions): Promise<{ serverProcess: ChildProcess; driver: PlaywrightDriver }> {
// Launch server
const { serverProcess, endpoint } = await launchServer(options);
@ -28,9 +27,6 @@ export async function launch(options: LaunchOptions): Promise<{ serverProcess: C
return {
serverProcess,
client: {
dispose: () => { /* there is no client to dispose for browser, teardown is triggered via exitApplication call */ }
},
driver: new PlaywrightDriver(browser, context, page, serverProcess, options)
};
}

View File

@ -5,14 +5,14 @@
import * as playwright from '@playwright/test';
import { join } from 'path';
import { IDriver, IWindowDriver } from './driver';
import { IWindowDriver } from './driver';
import { PageFunction } from 'playwright-core/types/structs';
import { measureAndLog } from './logger';
import { LaunchOptions } from './code';
import { teardown } from './processes';
import { ChildProcess } from 'child_process';
export class PlaywrightDriver implements IDriver {
export class PlaywrightDriver {
private static traceCounter = 1;
private static screenShotCounter = 1;
@ -31,8 +31,6 @@ export class PlaywrightDriver implements IDriver {
esc: 'Escape'
};
_serviceBrand: undefined;
constructor(
private readonly application: playwright.Browser | playwright.ElectronApplication,
private readonly context: playwright.BrowserContext,
@ -42,11 +40,7 @@ export class PlaywrightDriver implements IDriver {
) {
}
async getWindowIds() {
return [1];
}
async startTracing(windowId: number, name: string): Promise<void> {
async startTracing(name: string): Promise<void> {
if (!this.options.tracing) {
return; // tracing disabled
}
@ -58,7 +52,7 @@ export class PlaywrightDriver implements IDriver {
}
}
async stopTracing(windowId: number, name: string, persist: boolean): Promise<void> {
async stopTracing(name: string, persist: boolean): Promise<void> {
if (!this.options.tracing) {
return; // tracing disabled
}
@ -120,7 +114,7 @@ export class PlaywrightDriver implements IDriver {
// Desktop: exit via `driver.exitApplication`
else {
try {
await measureAndLog(this._evaluateWithDriver(([driver]) => driver.exitApplication()), 'driver.exitApplication()', this.options.logger);
await measureAndLog(this.evaluateWithDriver(([driver]) => driver.exitApplication()), 'driver.exitApplication()', this.options.logger);
} catch (error) {
this.options.logger.log(`Error exiting appliction (${error})`);
}
@ -132,7 +126,7 @@ export class PlaywrightDriver implements IDriver {
}
}
async dispatchKeybinding(windowId: number, keybinding: string) {
async dispatchKeybinding(keybinding: string) {
const chords = keybinding.split(' ');
for (let i = 0; i < chords.length; i++) {
const chord = chords[i];
@ -162,60 +156,60 @@ export class PlaywrightDriver implements IDriver {
await this.timeout(100);
}
async click(windowId: number, selector: string, xoffset?: number | undefined, yoffset?: number | undefined) {
const { x, y } = await this.getElementXY(windowId, selector, xoffset, yoffset);
async click(selector: string, xoffset?: number | undefined, yoffset?: number | undefined) {
const { x, y } = await this.getElementXY(selector, xoffset, yoffset);
await this.page.mouse.click(x + (xoffset ? xoffset : 0), y + (yoffset ? yoffset : 0));
}
async setValue(windowId: number, selector: string, text: string) {
return this.page.evaluate(([driver, selector, text]) => driver.setValue(selector, text), [await this._getDriverHandle(), selector, text] as const);
async setValue(selector: string, text: string) {
return this.page.evaluate(([driver, selector, text]) => driver.setValue(selector, text), [await this.getDriverHandle(), selector, text] as const);
}
async getTitle(windowId: number) {
return this._evaluateWithDriver(([driver]) => driver.getTitle());
async getTitle() {
return this.evaluateWithDriver(([driver]) => driver.getTitle());
}
async isActiveElement(windowId: number, selector: string) {
return this.page.evaluate(([driver, selector]) => driver.isActiveElement(selector), [await this._getDriverHandle(), selector] as const);
async isActiveElement(selector: string) {
return this.page.evaluate(([driver, selector]) => driver.isActiveElement(selector), [await this.getDriverHandle(), selector] as const);
}
async getElements(windowId: number, selector: string, recursive: boolean = false) {
return this.page.evaluate(([driver, selector, recursive]) => driver.getElements(selector, recursive), [await this._getDriverHandle(), selector, recursive] as const);
async getElements(selector: string, recursive: boolean = false) {
return this.page.evaluate(([driver, selector, recursive]) => driver.getElements(selector, recursive), [await this.getDriverHandle(), selector, recursive] as const);
}
async getElementXY(windowId: number, selector: string, xoffset?: number, yoffset?: number) {
return this.page.evaluate(([driver, selector, xoffset, yoffset]) => driver.getElementXY(selector, xoffset, yoffset), [await this._getDriverHandle(), selector, xoffset, yoffset] as const);
async getElementXY(selector: string, xoffset?: number, yoffset?: number) {
return this.page.evaluate(([driver, selector, xoffset, yoffset]) => driver.getElementXY(selector, xoffset, yoffset), [await this.getDriverHandle(), selector, xoffset, yoffset] as const);
}
async typeInEditor(windowId: number, selector: string, text: string) {
return this.page.evaluate(([driver, selector, text]) => driver.typeInEditor(selector, text), [await this._getDriverHandle(), selector, text] as const);
async typeInEditor(selector: string, text: string) {
return this.page.evaluate(([driver, selector, text]) => driver.typeInEditor(selector, text), [await this.getDriverHandle(), selector, text] as const);
}
async getTerminalBuffer(windowId: number, selector: string) {
return this.page.evaluate(([driver, selector]) => driver.getTerminalBuffer(selector), [await this._getDriverHandle(), selector] as const);
async getTerminalBuffer(selector: string) {
return this.page.evaluate(([driver, selector]) => driver.getTerminalBuffer(selector), [await this.getDriverHandle(), selector] as const);
}
async writeInTerminal(windowId: number, selector: string, text: string) {
return this.page.evaluate(([driver, selector, text]) => driver.writeInTerminal(selector, text), [await this._getDriverHandle(), selector, text] as const);
async writeInTerminal(selector: string, text: string) {
return this.page.evaluate(([driver, selector, text]) => driver.writeInTerminal(selector, text), [await this.getDriverHandle(), selector, text] as const);
}
async getLocaleInfo(windowId: number) {
return this._evaluateWithDriver(([driver]) => driver.getLocaleInfo());
async getLocaleInfo() {
return this.evaluateWithDriver(([driver]) => driver.getLocaleInfo());
}
async getLocalizedStrings(windowId: number) {
return this._evaluateWithDriver(([driver]) => driver.getLocalizedStrings());
async getLocalizedStrings() {
return this.evaluateWithDriver(([driver]) => driver.getLocalizedStrings());
}
private async _evaluateWithDriver<T>(pageFunction: PageFunction<playwright.JSHandle<IWindowDriver>[], T>) {
return this.page.evaluate(pageFunction, [await this._getDriverHandle()]);
private async evaluateWithDriver<T>(pageFunction: PageFunction<playwright.JSHandle<IWindowDriver>[], T>) {
return this.page.evaluate(pageFunction, [await this.getDriverHandle()]);
}
private timeout(ms: number): Promise<void> {
return new Promise<void>(resolve => setTimeout(resolve, ms));
}
private async _getDriverHandle(): Promise<playwright.JSHandle<IWindowDriver>> {
private async getDriverHandle(): Promise<playwright.JSHandle<IWindowDriver>> {
return this.page.evaluateHandle('window.driver');
}
}

View File

@ -4,14 +4,13 @@
*--------------------------------------------------------------------------------------------*/
import * as playwright from '@playwright/test';
import { IDriver, IDisposable } from './driver';
import type { LaunchOptions } from './code';
import { PlaywrightDriver } from './playwrightDriver';
import { IElectronConfiguration, resolveElectronConfiguration } from './electron';
import { measureAndLog } from './logger';
import { ChildProcess } from 'child_process';
export async function launch(options: LaunchOptions): Promise<{ electronProcess: ChildProcess; client: IDisposable; driver: IDriver }> {
export async function launch(options: LaunchOptions): Promise<{ electronProcess: ChildProcess; driver: PlaywrightDriver }> {
// Resolve electron config and update
const { electronPath, args, env } = await resolveElectronConfiguration(options);
@ -23,9 +22,6 @@ export async function launch(options: LaunchOptions): Promise<{ electronProcess:
return {
electronProcess,
client: {
dispose: () => { /* there is no client to dispose for electron, teardown is triggered via exitApplication call */ }
},
driver: new PlaywrightDriver(electron, context, page, undefined /* no server process */, options)
};
}

View File

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Viewlet } from './viewlet';
import { IElement } from '../src/driver';
import { IElement } from './driver';
import { findElement, findElements, Code } from './code';
const VIEWLET = 'div[id="workbench.view.scm"]';

View File

@ -21,34 +21,14 @@ contents = `/*------------------------------------------------------------------
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* Thenable is a common denominator between ES6 promises, Q, jquery.Deferred, WinJS.Promise,
* and others. This API makes no assumption about what promise library is being used which
* enables reusing existing code without migrating to a specific promise implementation. Still,
* we recommend the use of native promises which are available in this editor.
*/
interface Thenable<T> {
/**
* Attaches callbacks for the resolution and/or rejection of the Promise.
* @param onfulfilled The callback to execute when the Promise is resolved.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of which ever callback is executed.
*/
then<TResult>(onfulfilled?: (value: T) => TResult | Thenable<TResult>, onrejected?: (reason: any) => TResult | Thenable<TResult>): Thenable<TResult>;
then<TResult>(onfulfilled?: (value: T) => TResult | Thenable<TResult>, onrejected?: (reason: any) => void): Thenable<TResult>;
}
${contents}
export interface IDisposable {
dispose(): void;
}
export function connect(outPath: string, handle: string): Promise<{ client: IDisposable, driver: IDriver }>;
`;
const srcPath = path.join(path.dirname(__dirname), 'src');
const outPath = path.join(path.dirname(__dirname), 'out');
if (!fs.existsSync(outPath)) {
fs.mkdirSync(outPath);
}
fs.writeFileSync(path.join(srcPath, 'driver.d.ts'), contents);
fs.writeFileSync(path.join(outPath, 'driver.d.ts'), contents);

View File

@ -45,7 +45,6 @@ const opts = minimist(args, {
'remote',
'web',
'headless',
'legacy',
'tracing'
],
default: {
@ -56,7 +55,6 @@ const opts = minimist(args, {
remote?: boolean;
headless?: boolean;
web?: boolean;
legacy?: boolean;
tracing?: boolean;
build?: string;
'stable-build'?: string;
@ -71,9 +69,9 @@ const logsRootPath = (() => {
if (opts.web) {
logsName = 'smoke-tests-browser';
} else if (opts.remote) {
logsName = opts.legacy ? 'smoke-tests-remote-legacy' : 'smoke-tests-remote';
logsName = 'smoke-tests-remote';
} else {
logsName = opts.legacy ? 'smoke-tests-electron-legacy' : 'smoke-tests-electron';
logsName = 'smoke-tests-electron';
}
return path.join(logsParentPath, logsName);
@ -326,13 +324,11 @@ before(async function () {
workspacePath,
userDataDir,
extensionsPath,
waitTime: parseInt(opts['wait-time'] || '0') || 20,
logger,
logsPath: path.join(logsRootPath, 'suite_unknown'),
verbose: opts.verbose,
remote: opts.remote,
web: opts.web,
legacy: opts.legacy,
tracing: opts.tracing,
headless: opts.headless,
browser: opts.browser,
@ -366,7 +362,7 @@ after(async function () {
}
});
describe(`VSCode Smoke Tests (${opts.web ? 'Web' : opts.legacy ? 'Electron (legacy)' : 'Electron'})`, () => {
describe(`VSCode Smoke Tests (${opts.web ? 'Web' : 'Electron'})`, () => {
if (!opts.web) { setupDataLossTests(() => opts['stable-build'] /* Do not change, deferred for a reason! */, logger); }
setupPreferencesTests(logger);
setupSearchTests(logger);

View File

@ -12,11 +12,11 @@ const minimist = require('minimist');
const [, , ...args] = process.argv;
const opts = minimist(args, {
boolean: ['web', 'legacy'],
boolean: ['web'],
string: ['f', 'g']
});
const suite = opts['web'] ? 'Browser Smoke Tests' : opts['legacy'] ? 'Desktop Smoke Tests (Legacy)' : 'Desktop Smoke Tests';
const suite = opts['web'] ? 'Browser Smoke Tests' : 'Desktop Smoke Tests';
const options = {
color: true,