diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 9fa7fc9f28f84..67c8fe088589f 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -67,6 +67,7 @@ const vscodeEntryPoints = _.flatten([ const vscodeResources = [ 'out-build/main.js', 'out-build/cli.js', + 'out-build/driver.js', 'out-build/bootstrap.js', 'out-build/bootstrap-amd.js', 'out-build/paths.js', diff --git a/build/lib/compilation.js b/build/lib/compilation.js index 61c178b392e27..998ebb4f37933 100644 --- a/build/lib/compilation.js +++ b/build/lib/compilation.js @@ -22,7 +22,7 @@ var rootDir = path.join(__dirname, '../../src'); var options = require('../../src/tsconfig.json').compilerOptions; options.verbose = false; options.sourceMap = true; -if (process.env['VSCODE_NO_SOURCEMAP']) { +if (process.env['VSCODE_NO_SOURCEMAP']) { // To be used by developers in a hurry options.sourceMap = false; } options.rootDir = rootDir; diff --git a/build/lib/i18n.js b/build/lib/i18n.js index 6f5558001c447..61893ed8b1d64 100644 --- a/build/lib/i18n.js +++ b/build/lib/i18n.js @@ -1083,7 +1083,7 @@ function prepareI18nPackFiles(externalExtensions, resultingTranslationPaths, pse extPack = extensionsPacks[resource] = { version: i18nPackVersion, contents: {} }; } var externalId = externalExtensions[resource]; - if (!externalId) { + if (!externalId) { // internal extension: remove 'extensions/extensionId/' segnent var secondSlash = path.indexOf('/', firstSlash + 1); extPack.contents[path.substr(secondSlash + 1)] = file.messages; } diff --git a/build/lib/nls.js b/build/lib/nls.js index 7f2730ec89150..a63d36990143a 100644 --- a/build/lib/nls.js +++ b/build/lib/nls.js @@ -150,13 +150,16 @@ function isImportNode(node) { .filter(function (d) { return d.importClause.namedBindings.kind === ts.SyntaxKind.NamespaceImport; }) .map(function (d) { return d.importClause.namedBindings.name; }) .concat(importEqualsDeclarations.map(function (d) { return d.name; })) + // find read-only references to `nls` .map(function (n) { return service.getReferencesAtPosition(filename, n.pos + 1); }) .flatten() .filter(function (r) { return !r.isWriteAccess; }) + // find the deepest call expressions AST nodes that contain those references .map(function (r) { return collect(sourceFile, function (n) { return isCallExpressionWithinTextSpanCollectStep(r.textSpan, n); }); }) .map(function (a) { return lazy(a).last(); }) .filter(function (n) { return !!n; }) .map(function (n) { return n; }) + // only `localize` calls .filter(function (n) { return n.expression.kind === ts.SyntaxKind.PropertyAccessExpression && n.expression.name.getText() === 'localize'; }); // `localize` named imports var allLocalizeImportDeclarations = importDeclarations diff --git a/src/bootstrap-amd.js b/src/bootstrap-amd.js index 8b91b77b6719b..f0015d389d3cd 100644 --- a/src/bootstrap-amd.js +++ b/src/bootstrap-amd.js @@ -18,8 +18,8 @@ function uriFromPath(_path) { } function readFile(file) { - return new Promise(function(resolve, reject) { - fs.readFile(file, 'utf8', function(err, data) { + return new Promise(function (resolve, reject) { + fs.readFile(file, 'utf8', function (err, data) { if (err) { reject(err); return; @@ -35,7 +35,7 @@ var nlsConfig = rawNlsConfig ? JSON.parse(rawNlsConfig) : { availableLanguages: // We have a special location of the nls files. They come from a language pack if (nlsConfig._resolvedLanguagePackCoreLocation) { let bundles = Object.create(null); - nlsConfig.loadBundle = function(bundle, language, cb) { + nlsConfig.loadBundle = function (bundle, language, cb) { let result = bundles[bundle]; if (result) { cb(undefined, result); @@ -47,7 +47,7 @@ if (nlsConfig._resolvedLanguagePackCoreLocation) { bundles[bundle] = json; cb(undefined, json); }) - .catch(cb); + .catch(cb); }; } @@ -66,10 +66,13 @@ if (nlsConfig.pseudo) { }); } -exports.bootstrap = function (entrypoint) { +exports.bootstrap = function (entrypoint, onLoad, onError) { if (!entrypoint) { return; } - loader([entrypoint], function () { }, function (err) { console.error(err); }); + onLoad = onLoad || function () { }; + onError = onError || function (err) { console.error(err); }; + + loader([entrypoint], onLoad, onError); }; diff --git a/src/vs/base/browser/keyboardEvent.ts b/src/vs/base/browser/keyboardEvent.ts index 22b92dbb28f6e..646964e4e33ad 100644 --- a/src/vs/base/browser/keyboardEvent.ts +++ b/src/vs/base/browser/keyboardEvent.ts @@ -9,149 +9,160 @@ import { KeyCode, KeyCodeUtils, KeyMod, SimpleKeybinding } from 'vs/base/common/ import * as platform from 'vs/base/common/platform'; import * as browser from 'vs/base/browser/browser'; -let KEY_CODE_MAP: { [keyCode: number]: KeyCode } = {}; +let KEY_CODE_MAP: { [keyCode: number]: KeyCode } = new Array(230); +let INVERSE_KEY_CODE_MAP: KeyCode[] = new Array(KeyCode.MAX_VALUE); + (function () { - KEY_CODE_MAP[3] = KeyCode.PauseBreak; // VK_CANCEL 0x03 Control-break processing - KEY_CODE_MAP[8] = KeyCode.Backspace; - KEY_CODE_MAP[9] = KeyCode.Tab; - KEY_CODE_MAP[13] = KeyCode.Enter; - KEY_CODE_MAP[16] = KeyCode.Shift; - KEY_CODE_MAP[17] = KeyCode.Ctrl; - KEY_CODE_MAP[18] = KeyCode.Alt; - KEY_CODE_MAP[19] = KeyCode.PauseBreak; - KEY_CODE_MAP[20] = KeyCode.CapsLock; - KEY_CODE_MAP[27] = KeyCode.Escape; - KEY_CODE_MAP[32] = KeyCode.Space; - KEY_CODE_MAP[33] = KeyCode.PageUp; - KEY_CODE_MAP[34] = KeyCode.PageDown; - KEY_CODE_MAP[35] = KeyCode.End; - KEY_CODE_MAP[36] = KeyCode.Home; - KEY_CODE_MAP[37] = KeyCode.LeftArrow; - KEY_CODE_MAP[38] = KeyCode.UpArrow; - KEY_CODE_MAP[39] = KeyCode.RightArrow; - KEY_CODE_MAP[40] = KeyCode.DownArrow; - KEY_CODE_MAP[45] = KeyCode.Insert; - KEY_CODE_MAP[46] = KeyCode.Delete; - - KEY_CODE_MAP[48] = KeyCode.KEY_0; - KEY_CODE_MAP[49] = KeyCode.KEY_1; - KEY_CODE_MAP[50] = KeyCode.KEY_2; - KEY_CODE_MAP[51] = KeyCode.KEY_3; - KEY_CODE_MAP[52] = KeyCode.KEY_4; - KEY_CODE_MAP[53] = KeyCode.KEY_5; - KEY_CODE_MAP[54] = KeyCode.KEY_6; - KEY_CODE_MAP[55] = KeyCode.KEY_7; - KEY_CODE_MAP[56] = KeyCode.KEY_8; - KEY_CODE_MAP[57] = KeyCode.KEY_9; - - KEY_CODE_MAP[65] = KeyCode.KEY_A; - KEY_CODE_MAP[66] = KeyCode.KEY_B; - KEY_CODE_MAP[67] = KeyCode.KEY_C; - KEY_CODE_MAP[68] = KeyCode.KEY_D; - KEY_CODE_MAP[69] = KeyCode.KEY_E; - KEY_CODE_MAP[70] = KeyCode.KEY_F; - KEY_CODE_MAP[71] = KeyCode.KEY_G; - KEY_CODE_MAP[72] = KeyCode.KEY_H; - KEY_CODE_MAP[73] = KeyCode.KEY_I; - KEY_CODE_MAP[74] = KeyCode.KEY_J; - KEY_CODE_MAP[75] = KeyCode.KEY_K; - KEY_CODE_MAP[76] = KeyCode.KEY_L; - KEY_CODE_MAP[77] = KeyCode.KEY_M; - KEY_CODE_MAP[78] = KeyCode.KEY_N; - KEY_CODE_MAP[79] = KeyCode.KEY_O; - KEY_CODE_MAP[80] = KeyCode.KEY_P; - KEY_CODE_MAP[81] = KeyCode.KEY_Q; - KEY_CODE_MAP[82] = KeyCode.KEY_R; - KEY_CODE_MAP[83] = KeyCode.KEY_S; - KEY_CODE_MAP[84] = KeyCode.KEY_T; - KEY_CODE_MAP[85] = KeyCode.KEY_U; - KEY_CODE_MAP[86] = KeyCode.KEY_V; - KEY_CODE_MAP[87] = KeyCode.KEY_W; - KEY_CODE_MAP[88] = KeyCode.KEY_X; - KEY_CODE_MAP[89] = KeyCode.KEY_Y; - KEY_CODE_MAP[90] = KeyCode.KEY_Z; - - KEY_CODE_MAP[93] = KeyCode.ContextMenu; - - KEY_CODE_MAP[96] = KeyCode.NUMPAD_0; - KEY_CODE_MAP[97] = KeyCode.NUMPAD_1; - KEY_CODE_MAP[98] = KeyCode.NUMPAD_2; - KEY_CODE_MAP[99] = KeyCode.NUMPAD_3; - KEY_CODE_MAP[100] = KeyCode.NUMPAD_4; - KEY_CODE_MAP[101] = KeyCode.NUMPAD_5; - KEY_CODE_MAP[102] = KeyCode.NUMPAD_6; - KEY_CODE_MAP[103] = KeyCode.NUMPAD_7; - KEY_CODE_MAP[104] = KeyCode.NUMPAD_8; - KEY_CODE_MAP[105] = KeyCode.NUMPAD_9; - KEY_CODE_MAP[106] = KeyCode.NUMPAD_MULTIPLY; - KEY_CODE_MAP[107] = KeyCode.NUMPAD_ADD; - KEY_CODE_MAP[108] = KeyCode.NUMPAD_SEPARATOR; - KEY_CODE_MAP[109] = KeyCode.NUMPAD_SUBTRACT; - KEY_CODE_MAP[110] = KeyCode.NUMPAD_DECIMAL; - KEY_CODE_MAP[111] = KeyCode.NUMPAD_DIVIDE; - - KEY_CODE_MAP[112] = KeyCode.F1; - KEY_CODE_MAP[113] = KeyCode.F2; - KEY_CODE_MAP[114] = KeyCode.F3; - KEY_CODE_MAP[115] = KeyCode.F4; - KEY_CODE_MAP[116] = KeyCode.F5; - KEY_CODE_MAP[117] = KeyCode.F6; - KEY_CODE_MAP[118] = KeyCode.F7; - KEY_CODE_MAP[119] = KeyCode.F8; - KEY_CODE_MAP[120] = KeyCode.F9; - KEY_CODE_MAP[121] = KeyCode.F10; - KEY_CODE_MAP[122] = KeyCode.F11; - KEY_CODE_MAP[123] = KeyCode.F12; - KEY_CODE_MAP[124] = KeyCode.F13; - KEY_CODE_MAP[125] = KeyCode.F14; - KEY_CODE_MAP[126] = KeyCode.F15; - KEY_CODE_MAP[127] = KeyCode.F16; - KEY_CODE_MAP[128] = KeyCode.F17; - KEY_CODE_MAP[129] = KeyCode.F18; - KEY_CODE_MAP[130] = KeyCode.F19; - - KEY_CODE_MAP[144] = KeyCode.NumLock; - KEY_CODE_MAP[145] = KeyCode.ScrollLock; - - KEY_CODE_MAP[186] = KeyCode.US_SEMICOLON; - KEY_CODE_MAP[187] = KeyCode.US_EQUAL; - KEY_CODE_MAP[188] = KeyCode.US_COMMA; - KEY_CODE_MAP[189] = KeyCode.US_MINUS; - KEY_CODE_MAP[190] = KeyCode.US_DOT; - KEY_CODE_MAP[191] = KeyCode.US_SLASH; - KEY_CODE_MAP[192] = KeyCode.US_BACKTICK; - KEY_CODE_MAP[193] = KeyCode.ABNT_C1; - KEY_CODE_MAP[194] = KeyCode.ABNT_C2; - KEY_CODE_MAP[219] = KeyCode.US_OPEN_SQUARE_BRACKET; - KEY_CODE_MAP[220] = KeyCode.US_BACKSLASH; - KEY_CODE_MAP[221] = KeyCode.US_CLOSE_SQUARE_BRACKET; - KEY_CODE_MAP[222] = KeyCode.US_QUOTE; - KEY_CODE_MAP[223] = KeyCode.OEM_8; - - KEY_CODE_MAP[226] = KeyCode.OEM_102; + for (let i = 0; i < INVERSE_KEY_CODE_MAP.length; i++) { + INVERSE_KEY_CODE_MAP[i] = -1; + } + + function define(code: number, keyCode: KeyCode): void { + KEY_CODE_MAP[code] = keyCode; + INVERSE_KEY_CODE_MAP[keyCode] = code; + } + + define(3, KeyCode.PauseBreak); // VK_CANCEL 0x03 Control-break processing + define(8, KeyCode.Backspace); + define(9, KeyCode.Tab); + define(13, KeyCode.Enter); + define(16, KeyCode.Shift); + define(17, KeyCode.Ctrl); + define(18, KeyCode.Alt); + define(19, KeyCode.PauseBreak); + define(20, KeyCode.CapsLock); + define(27, KeyCode.Escape); + define(32, KeyCode.Space); + define(33, KeyCode.PageUp); + define(34, KeyCode.PageDown); + define(35, KeyCode.End); + define(36, KeyCode.Home); + define(37, KeyCode.LeftArrow); + define(38, KeyCode.UpArrow); + define(39, KeyCode.RightArrow); + define(40, KeyCode.DownArrow); + define(45, KeyCode.Insert); + define(46, KeyCode.Delete); + + define(48, KeyCode.KEY_0); + define(49, KeyCode.KEY_1); + define(50, KeyCode.KEY_2); + define(51, KeyCode.KEY_3); + define(52, KeyCode.KEY_4); + define(53, KeyCode.KEY_5); + define(54, KeyCode.KEY_6); + define(55, KeyCode.KEY_7); + define(56, KeyCode.KEY_8); + define(57, KeyCode.KEY_9); + + define(65, KeyCode.KEY_A); + define(66, KeyCode.KEY_B); + define(67, KeyCode.KEY_C); + define(68, KeyCode.KEY_D); + define(69, KeyCode.KEY_E); + define(70, KeyCode.KEY_F); + define(71, KeyCode.KEY_G); + define(72, KeyCode.KEY_H); + define(73, KeyCode.KEY_I); + define(74, KeyCode.KEY_J); + define(75, KeyCode.KEY_K); + define(76, KeyCode.KEY_L); + define(77, KeyCode.KEY_M); + define(78, KeyCode.KEY_N); + define(79, KeyCode.KEY_O); + define(80, KeyCode.KEY_P); + define(81, KeyCode.KEY_Q); + define(82, KeyCode.KEY_R); + define(83, KeyCode.KEY_S); + define(84, KeyCode.KEY_T); + define(85, KeyCode.KEY_U); + define(86, KeyCode.KEY_V); + define(87, KeyCode.KEY_W); + define(88, KeyCode.KEY_X); + define(89, KeyCode.KEY_Y); + define(90, KeyCode.KEY_Z); + + define(93, KeyCode.ContextMenu); + + define(96, KeyCode.NUMPAD_0); + define(97, KeyCode.NUMPAD_1); + define(98, KeyCode.NUMPAD_2); + define(99, KeyCode.NUMPAD_3); + define(100, KeyCode.NUMPAD_4); + define(101, KeyCode.NUMPAD_5); + define(102, KeyCode.NUMPAD_6); + define(103, KeyCode.NUMPAD_7); + define(104, KeyCode.NUMPAD_8); + define(105, KeyCode.NUMPAD_9); + define(106, KeyCode.NUMPAD_MULTIPLY); + define(107, KeyCode.NUMPAD_ADD); + define(108, KeyCode.NUMPAD_SEPARATOR); + define(109, KeyCode.NUMPAD_SUBTRACT); + define(110, KeyCode.NUMPAD_DECIMAL); + define(111, KeyCode.NUMPAD_DIVIDE); + + define(112, KeyCode.F1); + define(113, KeyCode.F2); + define(114, KeyCode.F3); + define(115, KeyCode.F4); + define(116, KeyCode.F5); + define(117, KeyCode.F6); + define(118, KeyCode.F7); + define(119, KeyCode.F8); + define(120, KeyCode.F9); + define(121, KeyCode.F10); + define(122, KeyCode.F11); + define(123, KeyCode.F12); + define(124, KeyCode.F13); + define(125, KeyCode.F14); + define(126, KeyCode.F15); + define(127, KeyCode.F16); + define(128, KeyCode.F17); + define(129, KeyCode.F18); + define(130, KeyCode.F19); + + define(144, KeyCode.NumLock); + define(145, KeyCode.ScrollLock); + + define(186, KeyCode.US_SEMICOLON); + define(187, KeyCode.US_EQUAL); + define(188, KeyCode.US_COMMA); + define(189, KeyCode.US_MINUS); + define(190, KeyCode.US_DOT); + define(191, KeyCode.US_SLASH); + define(192, KeyCode.US_BACKTICK); + define(193, KeyCode.ABNT_C1); + define(194, KeyCode.ABNT_C2); + define(219, KeyCode.US_OPEN_SQUARE_BRACKET); + define(220, KeyCode.US_BACKSLASH); + define(221, KeyCode.US_CLOSE_SQUARE_BRACKET); + define(222, KeyCode.US_QUOTE); + define(223, KeyCode.OEM_8); + + define(226, KeyCode.OEM_102); /** * https://lists.w3.org/Archives/Public/www-dom/2010JulSep/att-0182/keyCode-spec.html * If an Input Method Editor is processing key input and the event is keydown, return 229. */ - KEY_CODE_MAP[229] = KeyCode.KEY_IN_COMPOSITION; + define(229, KeyCode.KEY_IN_COMPOSITION); if (browser.isIE) { - KEY_CODE_MAP[91] = KeyCode.Meta; + define(91, KeyCode.Meta); } else if (browser.isFirefox) { - KEY_CODE_MAP[59] = KeyCode.US_SEMICOLON; - KEY_CODE_MAP[107] = KeyCode.US_EQUAL; - KEY_CODE_MAP[109] = KeyCode.US_MINUS; + define(59, KeyCode.US_SEMICOLON); + define(107, KeyCode.US_EQUAL); + define(109, KeyCode.US_MINUS); if (platform.isMacintosh) { - KEY_CODE_MAP[224] = KeyCode.Meta; + define(224, KeyCode.Meta); } } else if (browser.isWebKit) { - KEY_CODE_MAP[91] = KeyCode.Meta; + define(91, KeyCode.Meta); if (platform.isMacintosh) { // the two meta keys in the Mac have different key codes (91 and 93) - KEY_CODE_MAP[93] = KeyCode.Meta; + define(93, KeyCode.Meta); } else { - KEY_CODE_MAP[92] = KeyCode.Meta; + define(92, KeyCode.Meta); } } })(); @@ -165,6 +176,10 @@ function extractKeyCode(e: KeyboardEvent): KeyCode { return KEY_CODE_MAP[e.keyCode] || KeyCode.Unknown; } +export function getCodeForKeyCode(keyCode: KeyCode): number { + return INVERSE_KEY_CODE_MAP[keyCode]; +} + export interface IKeyboardEvent { readonly browserEvent: KeyboardEvent; readonly target: HTMLElement; diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index 2ac42d2fa4e52..060b728bcc693 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -279,7 +279,7 @@ export function debounceEvent(event: Event, merger: (last: O, event: I) let subscription: IDisposable; let output: O = undefined; - let handle: number = undefined; + let handle: any = undefined; let numDebouncedCalls = 0; const emitter = new Emitter({ diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index 97f8e5611c480..a3985361b63d1 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -152,7 +152,7 @@ export class ChannelServer implements IChannelServer, IDisposable { id, data: { message: data.message, name: data.name, - stack: data.stack ? data.stack.split('\n') : void 0 + stack: data.stack ? (data.stack.split ? data.stack.split('\n') : data.stack) : void 0 }, type: MessageType.ResponseError }); } else { diff --git a/src/vs/code/buildfile.js b/src/vs/code/buildfile.js index 2ff372605b728..1a39a0a0c2c61 100644 --- a/src/vs/code/buildfile.js +++ b/src/vs/code/buildfile.js @@ -5,24 +5,26 @@ 'use strict'; function createModuleDescription(name, exclude) { - var result= {}; + var result = {}; var excludes = ['vs/css', 'vs/nls']; - result.name= name; + result.name = name; if (Array.isArray(exclude) && exclude.length > 0) { excludes = excludes.concat(exclude); } - result.exclude= excludes; + result.exclude = excludes; return result; } -exports.collectModules= function() { +exports.collectModules = function () { return [ createModuleDescription('vs/code/electron-main/main', []), createModuleDescription('vs/code/node/cli', []), createModuleDescription('vs/code/node/cliProcessMain', ['vs/code/node/cli']), createModuleDescription('vs/code/electron-browser/issue/issueReporterMain', []), createModuleDescription('vs/code/electron-browser/sharedProcess/sharedProcessMain', []), + createModuleDescription('vs/code/electron-browser/issue/issueReporterMain', []), + createModuleDescription('vs/platform/driver/node/driver', []), createModuleDescription('vs/code/electron-browser/processExplorer/processExplorerMain', []) ]; }; diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 72ccf412cb83a..6d423f886bb9d 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -60,6 +60,7 @@ import { IssueService } from 'vs/platform/issue/electron-main/issueService'; import { LogLevelSetterChannel } from 'vs/platform/log/common/logIpc'; import { setUnexpectedErrorHandler } from 'vs/base/common/errors'; import { ElectronURLListener } from 'vs/platform/url/electron-main/electronUrlListener'; +import { serve as serveDriver } from 'vs/platform/driver/electron-main/driver'; import { join } from 'path'; import { exists, unlink, del } from 'vs/base/node/pfs'; @@ -289,15 +290,27 @@ export class CodeApplication { // Services const appInstantiationService = this.initServices(machineId); - // Setup Auth Handler - const authHandler = appInstantiationService.createInstance(ProxyAuthHandler); - this.toDispose.push(authHandler); + let promise: TPromise = TPromise.as(null); - // Open Windows - appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor)); + // Create driver + if (this.environmentService.driverHandle) { + serveDriver(this.electronIpcServer, this.environmentService.driverHandle, appInstantiationService).then(server => { + this.logService.info('Driver started at:', this.environmentService.driverHandle); + this.toDispose.push(server); + }); + } + + return promise.then(() => { + // Setup Auth Handler + const authHandler = appInstantiationService.createInstance(ProxyAuthHandler); + this.toDispose.push(authHandler); - // Post Open Windows Tasks - appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor)); + // Open Windows + appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor)); + + // Post Open Windows Tasks + appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor)); + }); }); }); } diff --git a/src/vs/platform/driver/common/driver.ts b/src/vs/platform/driver/common/driver.ts new file mode 100644 index 0000000000000..6abbc5d59ad43 --- /dev/null +++ b/src/vs/platform/driver/common/driver.ts @@ -0,0 +1,259 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { TPromise } from 'vs/base/common/winjs.base'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IChannel } from 'vs/base/parts/ipc/common/ipc'; + +export const ID = 'driverService'; +export const IDriver = createDecorator(ID); + +// !! Do not remove the following START and END markers, they are parsed by the smoketest build + +//*START +export interface IElement { + tagName: string; + className: string; + textContent: string; + attributes: { [name: string]: string; }; + children: IElement[]; +} + +export interface IDriver { + _serviceBrand: any; + + getWindowIds(): TPromise; + dispatchKeybinding(windowId: number, keybinding: string): TPromise; + click(windowId: number, selector: string, xoffset?: number | undefined, yoffset?: number | undefined): TPromise; + doubleClick(windowId: number, selector: string): TPromise; + move(windowId: number, selector: string): TPromise; + setValue(windowId: number, selector: string, text: string): TPromise; + getTitle(windowId: number): TPromise; + isActiveElement(windowId: number, selector: string): TPromise; + getElements(windowId: number, selector: string, recursive?: boolean): TPromise; + typeInEditor(windowId: number, selector: string, text: string): TPromise; + getTerminalBuffer(windowId: number, selector: string): TPromise; +} +//*END + +export interface IDriverChannel extends IChannel { + call(command: 'getWindowIds'): TPromise; + call(command: 'dispatchKeybinding', arg: [number, string]): TPromise; + call(command: 'click', arg: [number, string, number | undefined, number | undefined]): TPromise; + call(command: 'doubleClick', arg: [number, string]): TPromise; + call(command: 'move', arg: [number, string]): TPromise; + call(command: 'setValue', arg: [number, string, string]): TPromise; + call(command: 'getTitle', arg: [number]): TPromise; + call(command: 'isActiveElement', arg: [number, string]): TPromise; + call(command: 'getElements', arg: [number, string, boolean]): TPromise; + call(command: 'typeInEditor', arg: [number, string, string]): TPromise; + call(command: 'getTerminalBuffer', arg: [number, string]): TPromise; + call(command: string, arg: any): TPromise; +} + +export class DriverChannel implements IDriverChannel { + + constructor(private driver: IDriver) { } + + call(command: string, arg?: any): TPromise { + switch (command) { + case 'getWindowIds': return this.driver.getWindowIds(); + 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 'doubleClick': return this.driver.doubleClick(arg[0], arg[1]); + case 'move': return this.driver.move(arg[0], arg[1]); + 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 'typeInEditor': return this.driver.typeInEditor(arg[0], arg[1], arg[2]); + case 'getTerminalBuffer': return this.driver.getTerminalBuffer(arg[0], arg[1]); + } + + return undefined; + } +} + +export class DriverChannelClient implements IDriver { + + _serviceBrand: any; + + constructor(private channel: IDriverChannel) { } + + getWindowIds(): TPromise { + return this.channel.call('getWindowIds'); + } + + dispatchKeybinding(windowId: number, keybinding: string): TPromise { + return this.channel.call('dispatchKeybinding', [windowId, keybinding]); + } + + click(windowId: number, selector: string, xoffset: number | undefined, yoffset: number | undefined): TPromise { + return this.channel.call('click', [windowId, selector, xoffset, yoffset]); + } + + doubleClick(windowId: number, selector: string): TPromise { + return this.channel.call('doubleClick', [windowId, selector]); + } + + move(windowId: number, selector: string): TPromise { + return this.channel.call('move', [windowId, selector]); + } + + setValue(windowId: number, selector: string, text: string): TPromise { + return this.channel.call('setValue', [windowId, selector, text]); + } + + getTitle(windowId: number): TPromise { + return this.channel.call('getTitle', [windowId]); + } + + isActiveElement(windowId: number, selector: string): TPromise { + return this.channel.call('isActiveElement', [windowId, selector]); + } + + getElements(windowId: number, selector: string, recursive: boolean): TPromise { + return this.channel.call('getElements', [windowId, selector, recursive]); + } + + typeInEditor(windowId: number, selector: string, text: string): TPromise { + return this.channel.call('typeInEditor', [windowId, selector, text]); + } + + getTerminalBuffer(windowId: number, selector: string): TPromise { + return this.channel.call('getTerminalBuffer', [windowId, selector]); + } +} + +export interface IWindowDriverRegistry { + registerWindowDriver(windowId: number): TPromise; + reloadWindowDriver(windowId: number): TPromise; +} + +export interface IWindowDriverRegistryChannel extends IChannel { + call(command: 'registerWindowDriver', arg: number): TPromise; + call(command: 'reloadWindowDriver', arg: number): TPromise; + call(command: string, arg: any): TPromise; +} + +export class WindowDriverRegistryChannel implements IWindowDriverRegistryChannel { + + constructor(private registry: IWindowDriverRegistry) { } + + call(command: string, arg?: any): TPromise { + switch (command) { + case 'registerWindowDriver': return this.registry.registerWindowDriver(arg); + case 'reloadWindowDriver': return this.registry.reloadWindowDriver(arg); + } + + return undefined; + } +} + +export class WindowDriverRegistryChannelClient implements IWindowDriverRegistry { + + _serviceBrand: any; + + constructor(private channel: IWindowDriverRegistryChannel) { } + + registerWindowDriver(windowId: number): TPromise { + return this.channel.call('registerWindowDriver', windowId); + } + + reloadWindowDriver(windowId: number): TPromise { + return this.channel.call('reloadWindowDriver', windowId); + } +} + +export interface IWindowDriver { + click(selector: string, xoffset?: number | undefined, yoffset?: number | undefined): TPromise; + doubleClick(selector: string): TPromise; + move(selector: string): TPromise; + setValue(selector: string, text: string): TPromise; + getTitle(): TPromise; + isActiveElement(selector: string): TPromise; + getElements(selector: string, recursive: boolean): TPromise; + typeInEditor(selector: string, text: string): TPromise; + getTerminalBuffer(selector: string): TPromise; +} + +export interface IWindowDriverChannel extends IChannel { + call(command: 'click', arg: [string, number | undefined, number | undefined]): TPromise; + call(command: 'doubleClick', arg: string): TPromise; + call(command: 'move', arg: string): TPromise; + call(command: 'setValue', arg: [string, string]): TPromise; + call(command: 'getTitle'): TPromise; + call(command: 'isActiveElement', arg: string): TPromise; + call(command: 'getElements', arg: [string, boolean]): TPromise; + call(command: 'typeInEditor', arg: [string, string]): TPromise; + call(command: 'getTerminalBuffer', arg: string): TPromise; + call(command: string, arg: any): TPromise; +} + +export class WindowDriverChannel implements IWindowDriverChannel { + + constructor(private driver: IWindowDriver) { } + + call(command: string, arg?: any): TPromise { + switch (command) { + case 'click': return this.driver.click(arg[0], arg[1], arg[2]); + case 'doubleClick': return this.driver.doubleClick(arg); + case 'move': return this.driver.move(arg); + 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 'typeInEditor': return this.driver.typeInEditor(arg[0], arg[1]); + case 'getTerminalBuffer': return this.driver.getTerminalBuffer(arg); + } + + return undefined; + } +} + +export class WindowDriverChannelClient implements IWindowDriver { + + _serviceBrand: any; + + constructor(private channel: IWindowDriverChannel) { } + + click(selector: string, xoffset?: number, yoffset?: number): TPromise { + return this.channel.call('click', [selector, xoffset, yoffset]); + } + + doubleClick(selector: string): TPromise { + return this.channel.call('doubleClick', selector); + } + + move(selector: string): TPromise { + return this.channel.call('move', selector); + } + + setValue(selector: string, text: string): TPromise { + return this.channel.call('setValue', [selector, text]); + } + + getTitle(): TPromise { + return this.channel.call('getTitle'); + } + + isActiveElement(selector: string): TPromise { + return this.channel.call('isActiveElement', selector); + } + + getElements(selector: string, recursive: boolean): TPromise { + return this.channel.call('getElements', [selector, recursive]); + } + + typeInEditor(selector: string, text: string): TPromise { + return this.channel.call('typeInEditor', [selector, text]); + } + + getTerminalBuffer(selector: string): TPromise { + return this.channel.call('getTerminalBuffer', selector); + } +} \ No newline at end of file diff --git a/src/vs/platform/driver/electron-browser/driver.ts b/src/vs/platform/driver/electron-browser/driver.ts new file mode 100644 index 0000000000000..c762963dddc8c --- /dev/null +++ b/src/vs/platform/driver/electron-browser/driver.ts @@ -0,0 +1,185 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { TPromise } from 'vs/base/common/winjs.base'; +import { IDisposable, toDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; +import { IWindowDriver, IElement, WindowDriverChannel, WindowDriverRegistryChannelClient } from 'vs/platform/driver/common/driver'; +import { IPCClient } from 'vs/base/parts/ipc/common/ipc'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { getTopLeftOffset, getClientArea } from 'vs/base/browser/dom'; +import * as electron from 'electron'; + +function serializeElement(element: Element, recursive: boolean): IElement { + const attributes = Object.create(null); + + for (let j = 0; j < element.attributes.length; j++) { + const attr = element.attributes.item(j); + attributes[attr.name] = attr.value; + } + + const children = []; + + if (recursive) { + for (let i = 0; i < element.children.length; i++) { + children.push(serializeElement(element.children.item(i), true)); + } + } + + return { + tagName: element.tagName, + className: element.className, + textContent: element.textContent || '', + attributes, + children + }; +} + +class WindowDriver implements IWindowDriver { + + constructor() { } + + async click(selector: string, xoffset?: number, yoffset?: number): TPromise { + return this._click(selector, 1, xoffset, yoffset); + } + + doubleClick(selector: string): TPromise { + return this._click(selector, 2); + } + + private async _getElementXY(selector: string, xoffset?: number, yoffset?: number): TPromise<{ x: number; y: number; }> { + const element = document.querySelector(selector); + + if (!element) { + throw new Error('Element not found'); + } + + const { left, top } = getTopLeftOffset(element as HTMLElement); + const { width, height } = getClientArea(element as HTMLElement); + let x: number, y: number; + + if ((typeof xoffset === 'number') || (typeof yoffset === 'number')) { + x = left + xoffset; + y = top + yoffset; + } else { + x = left + (width / 2); + y = top + (height / 2); + } + + x = Math.round(x); + y = Math.round(y); + + return { x, y }; + } + + private async _click(selector: string, clickCount: number, xoffset?: number, yoffset?: number): TPromise { + const { x, y } = await this._getElementXY(selector, xoffset, yoffset); + const webContents = electron.remote.getCurrentWebContents(); + webContents.sendInputEvent({ type: 'mouseDown', x, y, button: 'left', clickCount } as any); + webContents.sendInputEvent({ type: 'mouseUp', x, y, button: 'left', clickCount } as any); + + await TPromise.timeout(100); + } + + async move(selector: string): TPromise { + const { x, y } = await this._getElementXY(selector); + const webContents = electron.remote.getCurrentWebContents(); + webContents.sendInputEvent({ type: 'mouseMove', x, y } as any); + + await TPromise.timeout(100); + } + + async setValue(selector: string, text: string): TPromise { + const element = document.querySelector(selector); + + if (!element) { + throw new Error('Element not found'); + } + + const inputElement = element as HTMLInputElement; + inputElement.value = text; + + const event = new Event('input', { bubbles: true, cancelable: true }); + inputElement.dispatchEvent(event); + } + + async getTitle(): TPromise { + return document.title; + } + + async isActiveElement(selector: string): TPromise { + const element = document.querySelector(selector); + return element === document.activeElement; + } + + async getElements(selector: string, recursive: boolean): TPromise { + const query = document.querySelectorAll(selector); + const result: IElement[] = []; + + for (let i = 0; i < query.length; i++) { + const element = query.item(i); + result.push(serializeElement(element, recursive)); + } + + return result; + } + + async typeInEditor(selector: string, text: string): TPromise { + const element = document.querySelector(selector); + + if (!element) { + throw new Error('Editor not found: ' + selector); + } + + const textarea = element as HTMLTextAreaElement; + const start = textarea.selectionStart; + const newStart = start + text.length; + const value = textarea.value; + const newValue = value.substr(0, start) + text + value.substr(start); + + textarea.value = newValue; + textarea.setSelectionRange(newStart, newStart); + + const event = new Event('input', { 'bubbles': true, 'cancelable': true }); + textarea.dispatchEvent(event); + } + + async getTerminalBuffer(selector: string): TPromise { + const element = document.querySelector(selector); + + if (!element) { + throw new Error('Terminal not found: ' + selector); + } + + const buffer = (element as any).xterm.buffer; + + const lines: string[] = []; + + for (let i = 0; i < buffer.lines.length; i++) { + lines.push(buffer.translateBufferLineToString(i, true)); + } + + return lines; + } +} + +export async function registerWindowDriver( + client: IPCClient, + windowId: number, + instantiationService: IInstantiationService +): TPromise { + const windowDriver = instantiationService.createInstance(WindowDriver); + const windowDriverChannel = new WindowDriverChannel(windowDriver); + client.registerChannel('windowDriver', windowDriverChannel); + + const windowDriverRegistryChannel = client.getChannel('windowDriverRegistry'); + const windowDriverRegistry = new WindowDriverRegistryChannelClient(windowDriverRegistryChannel); + + await windowDriverRegistry.registerWindowDriver(windowId); + + const disposable = toDisposable(() => windowDriverRegistry.reloadWindowDriver(windowId)); + return combinedDisposable([disposable, client]); +} \ No newline at end of file diff --git a/src/vs/platform/driver/electron-main/driver.ts b/src/vs/platform/driver/electron-main/driver.ts new file mode 100644 index 0000000000000..aa8ad7ed62500 --- /dev/null +++ b/src/vs/platform/driver/electron-main/driver.ts @@ -0,0 +1,193 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { TPromise } from 'vs/base/common/winjs.base'; +import { IDriver, DriverChannel, IElement, IWindowDriverChannel, WindowDriverChannelClient, IWindowDriverRegistry, WindowDriverRegistryChannel, IWindowDriver } from 'vs/platform/driver/common/driver'; +import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; +import { serve as serveNet } from 'vs/base/parts/ipc/node/ipc.net'; +import { combinedDisposable, IDisposable } from 'vs/base/common/lifecycle'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IPCServer, IClientRouter } from 'vs/base/parts/ipc/common/ipc'; +import { SimpleKeybinding, KeyCode } from 'vs/base/common/keyCodes'; +import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; +import { OS } from 'vs/base/common/platform'; +import { Emitter, toPromise } from 'vs/base/common/event'; + +// TODO@joao: bad layering! +import { KeybindingIO } from 'vs/workbench/services/keybinding/common/keybindingIO'; +import { ScanCodeBinding } from 'vs/workbench/services/keybinding/common/scanCode'; + +class WindowRouter implements IClientRouter { + + constructor(private windowId: number) { } + + route(command: string, arg: any): string { + return `window:${this.windowId}`; + } +} + +function isSilentKeyCode(keyCode: KeyCode) { + return keyCode < KeyCode.KEY_0; +} + +export class Driver implements IDriver, IWindowDriverRegistry { + + _serviceBrand: any; + + private registeredWindowIds = new Set(); + private reloadingWindowIds = new Set(); + private onDidReloadingChange = new Emitter(); + + constructor( + private windowServer: IPCServer, + @IWindowsMainService private windowsService: IWindowsMainService + ) { } + + async registerWindowDriver(windowId: number): TPromise { + this.registeredWindowIds.add(windowId); + this.reloadingWindowIds.delete(windowId); + this.onDidReloadingChange.fire(); + } + + async reloadWindowDriver(windowId: number): TPromise { + this.reloadingWindowIds.add(windowId); + } + + async getWindowIds(): TPromise { + return this.windowsService.getWindows() + .map(w => w.id) + .filter(id => this.registeredWindowIds.has(id) && !this.reloadingWindowIds.has(id)); + } + + async dispatchKeybinding(windowId: number, keybinding: string): TPromise { + await this.whenUnfrozen(windowId); + + const [first, second] = KeybindingIO._readUserBinding(keybinding); + + await this._dispatchKeybinding(windowId, first); + + if (second) { + await this._dispatchKeybinding(windowId, second); + } + } + + private async _dispatchKeybinding(windowId: number, keybinding: SimpleKeybinding | ScanCodeBinding): TPromise { + if (keybinding instanceof ScanCodeBinding) { + throw new Error('ScanCodeBindings not supported'); + } + + const window = this.windowsService.getWindowById(windowId); + const webContents = window.win.webContents; + const noModifiedKeybinding = new SimpleKeybinding(false, false, false, false, keybinding.keyCode); + const resolvedKeybinding = new USLayoutResolvedKeybinding(noModifiedKeybinding, OS); + const keyCode = resolvedKeybinding.getElectronAccelerator(); + + const modifiers = []; + + 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 TPromise.timeout(100); + } + + async click(windowId: number, selector: string, xoffset?: number, yoffset?: number): TPromise { + const windowDriver = await this.getWindowDriver(windowId); + return windowDriver.click(selector, xoffset, yoffset); + } + + async doubleClick(windowId: number, selector: string): TPromise { + const windowDriver = await this.getWindowDriver(windowId); + return windowDriver.doubleClick(selector); + } + + async move(windowId: number, selector: string): TPromise { + const windowDriver = await this.getWindowDriver(windowId); + return windowDriver.move(selector); + } + + async setValue(windowId: number, selector: string, text: string): TPromise { + const windowDriver = await this.getWindowDriver(windowId); + return windowDriver.setValue(selector, text); + } + + async getTitle(windowId: number): TPromise { + const windowDriver = await this.getWindowDriver(windowId); + return windowDriver.getTitle(); + } + + async isActiveElement(windowId: number, selector: string): TPromise { + const windowDriver = await this.getWindowDriver(windowId); + return windowDriver.isActiveElement(selector); + } + + async getElements(windowId: number, selector: string, recursive: boolean): TPromise { + const windowDriver = await this.getWindowDriver(windowId); + return windowDriver.getElements(selector, recursive); + } + + async typeInEditor(windowId: number, selector: string, text: string): TPromise { + const windowDriver = await this.getWindowDriver(windowId); + return windowDriver.typeInEditor(selector, text); + } + + async getTerminalBuffer(windowId: number, selector: string): TPromise { + const windowDriver = await this.getWindowDriver(windowId); + return windowDriver.getTerminalBuffer(selector); + } + + private async getWindowDriver(windowId: number): TPromise { + await this.whenUnfrozen(windowId); + + const router = new WindowRouter(windowId); + const windowDriverChannel = this.windowServer.getChannel('windowDriver', router); + return new WindowDriverChannelClient(windowDriverChannel); + } + + private async whenUnfrozen(windowId: number): TPromise { + while (this.reloadingWindowIds.has(windowId)) { + await toPromise(this.onDidReloadingChange.event); + } + } +} + +export async function serve( + windowServer: IPCServer, + handle: string, + instantiationService: IInstantiationService +): TPromise { + 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]); +} \ No newline at end of file diff --git a/src/vs/platform/driver/node/driver.ts b/src/vs/platform/driver/node/driver.ts new file mode 100644 index 0000000000000..fb61d1d1fa692 --- /dev/null +++ b/src/vs/platform/driver/node/driver.ts @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { TPromise } from 'vs/base/common/winjs.base'; +import { IDriver, DriverChannelClient } from 'vs/platform/driver/common/driver'; +import { connect as connectNet, Client } from 'vs/base/parts/ipc/node/ipc.net'; + +export async function connect(handle: string): TPromise<{ client: Client, driver: IDriver }> { + const client = await connectNet(handle, 'driverClient'); + const channel = client.getChannel('driver'); + const driver = new DriverChannelClient(channel); + return { client, driver }; +} diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 5917f324e1474..7b9d2d422596e 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -57,6 +57,7 @@ export interface ParsedArgs { 'file-write'?: boolean; 'file-chmod'?: boolean; 'upload-logs'?: string; + 'driver'?: string; } export const IEnvironmentService = createDecorator('environmentService'); @@ -130,4 +131,6 @@ export interface IEnvironmentService { installSourcePath: string; disableUpdates: boolean; disableCrashReporter: boolean; + + driverHandle: string; } diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index ed297cda68433..336ec1dcbcb42 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -29,7 +29,8 @@ const options: minimist.Opts = { 'enable-proposed-api', 'export-default-configuration', 'install-source', - 'upload-logs' + 'upload-logs', + 'driver' ], boolean: [ 'help', diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index cc1adabd5fde1..cd875edaccdb0 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -170,6 +170,8 @@ export class EnvironmentService implements IEnvironmentService { get disableUpdates(): boolean { return !!this._args['disable-updates']; } get disableCrashReporter(): boolean { return !!this._args['disable-crash-reporter']; } + get driverHandle(): string { return this._args['driver']; } + constructor(private _args: ParsedArgs, private _execPath: string) { if (!process.env['VSCODE_LOGS']) { const key = toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, ''); diff --git a/src/vs/platform/keybinding/test/common/mockKeybindingService.ts b/src/vs/platform/keybinding/test/common/mockKeybindingService.ts index 99c88c4fa0669..8a44c7bd8db9c 100644 --- a/src/vs/platform/keybinding/test/common/mockKeybindingService.ts +++ b/src/vs/platform/keybinding/test/common/mockKeybindingService.ts @@ -120,4 +120,8 @@ export class MockKeybindingService implements IKeybindingService { public softDispatch(keybinding: IKeyboardEvent, target: IContextKeyServiceTarget): IResolveResult { return null; } + + dispatchEvent(e: IKeyboardEvent, target: IContextKeyServiceTarget): boolean { + return false; + } } diff --git a/src/vs/workbench/electron-browser/main.ts b/src/vs/workbench/electron-browser/main.ts index b5d1817b21156..087bbe40d2448 100644 --- a/src/vs/workbench/electron-browser/main.ts +++ b/src/vs/workbench/electron-browser/main.ts @@ -97,7 +97,7 @@ function openWorkbench(configuration: IWindowConfiguration): TPromise { logService, timerService, storageService - }, mainServices, configuration); + }, mainServices, mainProcessClient, configuration); shell.open(); // Inform user about loading issues from the loader diff --git a/src/vs/workbench/electron-browser/shell.ts b/src/vs/workbench/electron-browser/shell.ts index c34ea66ab5a86..260c612aebb8a 100644 --- a/src/vs/workbench/electron-browser/shell.ts +++ b/src/vs/workbench/electron-browser/shell.ts @@ -60,7 +60,7 @@ import { WorkbenchModeServiceImpl } from 'vs/workbench/services/mode/common/work import { IModeService } from 'vs/editor/common/services/modeService'; import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { ICrashReporterService, NullCrashReporterService, CrashReporterService } from 'vs/workbench/services/crashReporter/electron-browser/crashReporterService'; -import { getDelayedChannel } from 'vs/base/parts/ipc/common/ipc'; +import { getDelayedChannel, IPCClient } from 'vs/base/parts/ipc/common/ipc'; import { connect as connectNet } from 'vs/base/parts/ipc/node/ipc.net'; import { IExtensionManagementChannel, ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; import { IExtensionManagementService, IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; @@ -139,7 +139,7 @@ export class WorkbenchShell { private configuration: IWindowConfiguration; private workbench: Workbench; - constructor(container: HTMLElement, coreServices: ICoreServices, mainProcessServices: ServiceCollection, configuration: IWindowConfiguration) { + constructor(container: HTMLElement, coreServices: ICoreServices, mainProcessServices: ServiceCollection, private mainProcessClient: IPCClient, configuration: IWindowConfiguration) { this.container = container; this.configuration = configuration; @@ -189,7 +189,7 @@ export class WorkbenchShell { private createWorkbench(instantiationService: IInstantiationService, serviceCollection: ServiceCollection, parent: HTMLElement, workbenchContainer: HTMLElement): Workbench { try { - const workbench = instantiationService.createInstance(Workbench, parent, workbenchContainer, this.configuration, serviceCollection, this.lifecycleService); + const workbench = instantiationService.createInstance(Workbench, parent, workbenchContainer, this.configuration, serviceCollection, this.lifecycleService, this.mainProcessClient); // Set lifecycle phase to `Restoring` this.lifecycleService.phase = LifecyclePhase.Restoring; diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts index cdb6879f8a608..57c90bb0b9141 100644 --- a/src/vs/workbench/electron-browser/workbench.ts +++ b/src/vs/workbench/electron-browser/workbench.ts @@ -106,6 +106,8 @@ import { NotificationsAlerts } from 'vs/workbench/browser/parts/notifications/no import { NotificationsStatus } from 'vs/workbench/browser/parts/notifications/notificationsStatus'; import { registerNotificationCommands } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; import { NotificationsToasts } from 'vs/workbench/browser/parts/notifications/notificationsToasts'; +import { IPCClient } from 'vs/base/parts/ipc/common/ipc'; +import { registerWindowDriver } from 'vs/platform/driver/electron-browser/driver'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { PreferencesService } from 'vs/workbench/services/preferences/browser/preferencesService'; @@ -233,9 +235,10 @@ export class Workbench implements IPartService { constructor( parent: HTMLElement, container: HTMLElement, - configuration: IWindowConfiguration, + private configuration: IWindowConfiguration, serviceCollection: ServiceCollection, private lifecycleService: LifecycleService, + private mainProcessClient: IPCClient, @IInstantiationService private instantiationService: IInstantiationService, @IWorkspaceContextService private contextService: IWorkspaceContextService, @IStorageService private storageService: IStorageService, @@ -320,6 +323,12 @@ export class Workbench implements IPartService { // Workbench Layout this.createWorkbenchLayout(); + // Driver + if (this.environmentService.driverHandle) { + registerWindowDriver(this.mainProcessClient, this.configuration.windowId, this.instantiationService) + .then(disposable => this.toUnbind.push(disposable)); + } + // Restore Parts return this.restoreParts(); } diff --git a/test/smoke/.gitignore b/test/smoke/.gitignore index 532798d3ea949..6601bb4c58817 100644 --- a/test/smoke/.gitignore +++ b/test/smoke/.gitignore @@ -4,4 +4,5 @@ Thumbs.db node_modules/ out/ keybindings.*.json -test_data/ \ No newline at end of file +test_data/ +src/vscode/driver.d.ts \ No newline at end of file diff --git a/test/smoke/package.json b/test/smoke/package.json index 97d8c0b3c3e11..e033db34da004 100644 --- a/test/smoke/package.json +++ b/test/smoke/package.json @@ -3,8 +3,13 @@ "version": "0.1.0", "main": "./src/main.js", "scripts": { - "postinstall": "tsc", - "watch": "tsc --watch", + "postinstall": "npm run compile", + "compile": "npm run copy-driver && npm run copy-driver-definition && tsc", + "watch": "concurrently \"npm run watch-driver\" \"npm run watch-driver-definition\" \"tsc --watch\"", + "copy-driver": "cpx src/vscode/driver.js out/vscode", + "watch-driver": "cpx src/vscode/driver.js out/vscode -w", + "copy-driver-definition": "node tools/copy-driver-definition.js", + "watch-driver-definition": "watch \"node tools/copy-driver-definition.js\" ../../src/vs/platform/driver/common", "mocha": "mocha" }, "devDependencies": { @@ -15,6 +20,8 @@ "@types/node": "8.0.33", "@types/rimraf": "2.0.2", "@types/webdriverio": "4.6.1", + "concurrently": "^3.5.1", + "cpx": "^1.5.0", "electron": "1.7.7", "htmlparser2": "^3.9.2", "mkdirp": "^0.5.1", @@ -22,9 +29,9 @@ "ncp": "^2.0.0", "portastic": "^1.0.1", "rimraf": "^2.6.1", - "spectron": "^3.7.2", "strip-json-comments": "^2.0.1", "tmp": "0.0.33", - "typescript": "2.5.2" + "typescript": "2.5.2", + "watch": "^1.0.2" } -} \ No newline at end of file +} diff --git a/test/smoke/src/application.ts b/test/smoke/src/application.ts new file mode 100644 index 0000000000000..11dd14ea1a4e6 --- /dev/null +++ b/test/smoke/src/application.ts @@ -0,0 +1,147 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Workbench } from './areas/workbench/workbench'; +import * as fs from 'fs'; +import * as cp from 'child_process'; +import { Code, spawn, SpawnOptions } from './vscode/code'; + +export enum Quality { + Dev, + Insiders, + Stable +} + +export interface ApplicationOptions extends SpawnOptions { + quality: Quality; + workspacePath: string; + workspaceFilePath: string; + waitTime: number; + verbose: boolean; +} + +export class Application { + + private _code: Code | undefined; + private _workbench: Workbench; + private keybindings: any[]; + private stopLogCollection: (() => Promise) | undefined; + + constructor(private options: ApplicationOptions) { } + + get quality(): Quality { + return this.options.quality; + } + + get code(): Code { + return this._code!; + } + + get workbench(): Workbench { + return this._workbench; + } + + get workspacePath(): string { + return this.options.workspacePath; + } + + get extensionsPath(): string { + return this.options.extensionsPath; + } + + get userDataPath(): string { + return this.options.userDataDir; + } + + get workspaceFilePath(): string { + return this.options.workspaceFilePath; + } + + async start(waitForWelcome: boolean = true): Promise { + await this._start(); + + if (waitForWelcome) { + await this.waitForWelcome(); + } + } + + async restart(options: { workspaceOrFolder?: string, extraArgs?: string[] }): Promise { + await this.stop(); + await new Promise(c => setTimeout(c, 1000)); + await this._start(options.workspaceOrFolder, options.extraArgs); + } + + private async _start(workspaceOrFolder = this.options.workspacePath, extraArgs: string[] = []): Promise { + await this.retrieveKeybindings(); + cp.execSync('git checkout .', { cwd: this.options.workspacePath }); + await this.startApplication(workspaceOrFolder, extraArgs); + await this.checkWindowReady(); + } + + async reload(): Promise { + this.workbench.runCommand('Reload Window') + .catch(err => null); // ignore the connection drop errors + + // needs to be enough to propagate the 'Reload Window' command + await new Promise(c => setTimeout(c, 1500)); + await this.checkWindowReady(); + } + + async stop(): Promise { + if (this.stopLogCollection) { + await this.stopLogCollection(); + this.stopLogCollection = undefined; + } + + if (this._code) { + this._code.dispose(); + this._code = undefined; + } + } + + private async startApplication(workspaceOrFolder: string, extraArgs: string[] = []): Promise { + this._code = await spawn({ + codePath: this.options.codePath, + workspacePath: workspaceOrFolder, + userDataDir: this.options.userDataDir, + extensionsPath: this.options.extensionsPath, + verbose: this.options.verbose, + extraArgs + }); + + this._workbench = new Workbench(this._code, this.keybindings, this.userDataPath); + } + + private async checkWindowReady(): Promise { + if (!this.code) { + console.error('No code instance found'); + return; + } + + await this.code.waitForWindowIds(ids => ids.length > 0); + await this.code.waitForElement('.monaco-workbench'); + } + + private async waitForWelcome(): Promise { + await this.code.waitForElement('.explorer-folders-view'); + await this.code.waitForElement(`.editor-container[id="workbench.editor.walkThroughPart"] .welcomePage`); + } + + private retrieveKeybindings(): Promise { + return new Promise((c, e) => { + fs.readFile(process.env.VSCODE_KEYBINDINGS_PATH as string, 'utf8', (err, data) => { + if (err) { + throw err; + } + try { + this.keybindings = JSON.parse(data); + c(); + } catch (e) { + throw new Error(`Error parsing keybindings JSON: ${e}`); + } + }); + }); + } +} diff --git a/test/smoke/src/areas/activitybar/activityBar.ts b/test/smoke/src/areas/activitybar/activityBar.ts index 894eef36f9f97..a2acb1ac44d00 100644 --- a/test/smoke/src/areas/activitybar/activityBar.ts +++ b/test/smoke/src/areas/activitybar/activityBar.ts @@ -3,8 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Element } from 'webdriverio'; -import { SpectronApplication } from '../../spectron/application'; +import { Code } from '../../vscode/code'; export enum ActivityBarPosition { LEFT = 0, @@ -13,11 +12,9 @@ export enum ActivityBarPosition { export class ActivityBar { - constructor(private spectron: SpectronApplication) { - // noop - } + constructor(private code: Code) { } - public async getActivityBar(position: ActivityBarPosition): Promise { + async waitForActivityBar(position: ActivityBarPosition): Promise { let positionClass: string; if (position === ActivityBarPosition.LEFT) { @@ -28,6 +25,6 @@ export class ActivityBar { throw new Error('No such position for activity bar defined.'); } - return this.spectron.client.waitForElement(`.part.activitybar.${positionClass}`); + await this.code.waitForElement(`.part.activitybar.${positionClass}`); } } \ No newline at end of file diff --git a/test/smoke/src/areas/css/css.test.ts b/test/smoke/src/areas/css/css.test.ts index b7ee545445a4d..176e634bf6d7a 100644 --- a/test/smoke/src/areas/css/css.test.ts +++ b/test/smoke/src/areas/css/css.test.ts @@ -3,55 +3,42 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; -import { SpectronApplication } from '../../spectron/application'; +import { Application } from '../../application'; import { ProblemSeverity, Problems } from '../problems/problems'; export function setup() { describe('CSS', () => { - before(function () { - this.app.suiteName = 'CSS'; - }); - it('verifies quick outline', async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; await app.workbench.quickopen.openFile('style.css'); - await app.workbench.editor.openOutline(); + await app.workbench.quickopen.openQuickOutline(); await app.workbench.quickopen.waitForQuickOpenElements(names => names.length === 2); }); it('verifies warnings for the empty rule', async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; await app.workbench.quickopen.openFile('style.css'); await app.workbench.editor.waitForTypeInEditor('style.css', '.foo{}'); - let warning = await app.client.waitForElement(Problems.getSelectorInEditor(ProblemSeverity.WARNING)); - await app.screenCapturer.capture('CSS Warning in editor'); - assert.ok(warning, `Warning squiggle is not shown in 'style.css'.`); + await app.code.waitForElement(Problems.getSelectorInEditor(ProblemSeverity.WARNING)); await app.workbench.problems.showProblemsView(); - warning = await app.client.waitForElement(Problems.getSelectorInProblemsView(ProblemSeverity.WARNING)); - await app.screenCapturer.capture('CSS Warning in problems view'); - assert.ok(warning, 'Warning does not appear in Problems view.'); + await app.code.waitForElement(Problems.getSelectorInProblemsView(ProblemSeverity.WARNING)); await app.workbench.problems.hideProblemsView(); }); it('verifies that warning becomes an error once setting changed', async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; await app.workbench.settingsEditor.addUserSetting('css.lint.emptyRules', '"error"'); await app.workbench.quickopen.openFile('style.css'); await app.workbench.editor.waitForTypeInEditor('style.css', '.foo{}'); - let error = await app.client.waitForElement(Problems.getSelectorInEditor(ProblemSeverity.ERROR)); - await app.screenCapturer.capture('CSS Error in editor'); - assert.ok(error, `Warning squiggle is not shown in 'style.css'.`); + await app.code.waitForElement(Problems.getSelectorInEditor(ProblemSeverity.ERROR)); - const problems = new Problems(app); + const problems = new Problems(app.code, app.workbench); await problems.showProblemsView(); - error = await app.client.waitForElement(Problems.getSelectorInProblemsView(ProblemSeverity.ERROR)); - await app.screenCapturer.capture('CSS Error in probles view'); - assert.ok(error, 'Warning does not appear in Problems view.'); + await app.code.waitForElement(Problems.getSelectorInProblemsView(ProblemSeverity.ERROR)); await problems.hideProblemsView(); }); }); diff --git a/test/smoke/src/areas/debug/debug.test.ts b/test/smoke/src/areas/debug/debug.test.ts index 7e924b95b626e..b193d856cfa48 100644 --- a/test/smoke/src/areas/debug/debug.test.ts +++ b/test/smoke/src/areas/debug/debug.test.ts @@ -8,17 +8,12 @@ import * as http from 'http'; import * as path from 'path'; import * as fs from 'fs'; import * as stripJsonComments from 'strip-json-comments'; -import { SpectronApplication } from '../../spectron/application'; +import { Application } from '../../application'; export function setup() { describe('Debug', () => { - before(async function () { - const app = this.app as SpectronApplication; - app.suiteName = 'Debug'; - }); - it('configure launch json', async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; await app.workbench.debug.openDebugViewlet(); await app.workbench.quickopen.openFile('app.js'); @@ -31,7 +26,6 @@ export function setup() { fs.writeFileSync(launchJsonPath, JSON.stringify(config, undefined, 4), 'utf8'); await app.workbench.editor.waitForEditorContents('launch.json', contents => /"protocol": "inspector"/.test(contents)); - await app.screenCapturer.capture('launch.json file'); assert.equal(config.configurations[0].request, 'launch'); assert.equal(config.configurations[0].type, 'node'); @@ -43,66 +37,61 @@ export function setup() { }); it('breakpoints', async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; await app.workbench.quickopen.openFile('index.js'); await app.workbench.debug.setBreakpointOnLine(6); - await app.screenCapturer.capture('breakpoints are set'); }); let port: number; it('start debugging', async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; + + // TODO@isidor + await new Promise(c => setTimeout(c, 100)); port = await app.workbench.debug.startDebugging(); - await app.screenCapturer.capture('debugging has started'); await new Promise((c, e) => { const request = http.get(`http://localhost:${port}`); request.on('error', e); app.workbench.debug.waitForStackFrame(sf => sf.name === 'index.js' && sf.lineNumber === 6, 'looking for index.js and line 6').then(c, e); }); - - await app.screenCapturer.capture('debugging is paused'); }); it('focus stack frames and variables', async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; - await app.client.waitFor(() => app.workbench.debug.getLocalVariableCount(), c => c === 4, 'there should be 4 local variables'); + await app.workbench.debug.waitForVariableCount(4); await app.workbench.debug.focusStackFrame('layer.js', 'looking for layer.js'); - await app.client.waitFor(() => app.workbench.debug.getLocalVariableCount(), c => c === 5, 'there should be 5 local variables'); + await app.workbench.debug.waitForVariableCount(5); await app.workbench.debug.focusStackFrame('route.js', 'looking for route.js'); - await app.client.waitFor(() => app.workbench.debug.getLocalVariableCount(), c => c === 3, 'there should be 3 local variables'); + await app.workbench.debug.waitForVariableCount(3); await app.workbench.debug.focusStackFrame('index.js', 'looking for index.js'); - await app.client.waitFor(() => app.workbench.debug.getLocalVariableCount(), c => c === 4, 'there should be 4 local variables'); + await app.workbench.debug.waitForVariableCount(4); }); it('stepOver, stepIn, stepOut', async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; await app.workbench.debug.stepIn(); - await app.screenCapturer.capture('debugging has stepped in'); const first = await app.workbench.debug.waitForStackFrame(sf => sf.name === 'response.js', 'looking for response.js'); await app.workbench.debug.stepOver(); - await app.screenCapturer.capture('debugging has stepped over'); await app.workbench.debug.waitForStackFrame(sf => sf.name === 'response.js' && sf.lineNumber === first.lineNumber + 1, `looking for response.js and line ${first.lineNumber + 1}`); await app.workbench.debug.stepOut(); - await app.screenCapturer.capture('debugging has stepped out'); await app.workbench.debug.waitForStackFrame(sf => sf.name === 'index.js' && sf.lineNumber === 7, `looking for index.js and line 7`); }); it('continue', async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; await app.workbench.debug.continue(); - await app.screenCapturer.capture('debugging has continued'); await new Promise((c, e) => { const request = http.get(`http://localhost:${port}`); @@ -110,20 +99,18 @@ export function setup() { app.workbench.debug.waitForStackFrame(sf => sf.name === 'index.js' && sf.lineNumber === 6, `looking for index.js and line 6`).then(c, e); }); - await app.screenCapturer.capture('debugging is paused'); }); it('debug console', async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; await app.workbench.debug.waitForReplCommand('2 + 2', r => r === '4'); }); it('stop debugging', async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; await app.workbench.debug.stopDebugging(); - await app.screenCapturer.capture('debugging has stopped'); }); }); } \ No newline at end of file diff --git a/test/smoke/src/areas/debug/debug.ts b/test/smoke/src/areas/debug/debug.ts index 0f9dfa15582e6..bf92dd930ff5a 100644 --- a/test/smoke/src/areas/debug/debug.ts +++ b/test/smoke/src/areas/debug/debug.ts @@ -3,13 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SpectronApplication } from '../../spectron/application'; import { Viewlet } from '../workbench/viewlet'; +import { Commands } from '../workbench/workbench'; +import { Code, findElement } from '../../vscode/code'; +import { Editors } from '../editor/editors'; +import { Editor } from '../editor/editor'; +import { IElement } from '../../vscode/driver'; const VIEWLET = 'div[id="workbench.view.debug"]'; const DEBUG_VIEW = `${VIEWLET} .debug-view-content`; const CONFIGURE = `div[id="workbench.parts.sidebar"] .actions-container .configure`; -const START = `.icon[title="Start Debugging"]`; const STOP = `.debug-actions-widget .debug-action.stop`; const STEP_OVER = `.debug-actions-widget .debug-action.step-over`; const STEP_IN = `.debug-actions-widget .debug-action.step-into`; @@ -22,150 +25,118 @@ const DEBUG_STATUS_BAR = `.statusbar.debugging`; const NOT_DEBUG_STATUS_BAR = `.statusbar:not(debugging)`; const TOOLBAR_HIDDEN = `.debug-actions-widget.monaco-builder-hidden`; const STACK_FRAME = `${VIEWLET} .monaco-tree-row .stack-frame`; +const SPECIFIC_STACK_FRAME = filename => `${STACK_FRAME} .file[title$="${filename}"]`; const VARIABLE = `${VIEWLET} .debug-variables .monaco-tree-row .expression`; -const CONSOLE_OUTPUT = `.repl .output.expression`; +const CONSOLE_OUTPUT = `.repl .output.expression .value`; const CONSOLE_INPUT_OUTPUT = `.repl .input-output-pair .output.expression .value`; const REPL_FOCUSED = '.repl-input-wrapper .monaco-editor textarea'; export interface IStackFrame { - id: string; name: string; lineNumber: number; } +function toStackFrame(element: IElement): IStackFrame { + const name = findElement(element, e => /\bfile-name\b/.test(e.className))!; + const line = findElement(element, e => /\bline-number\b/.test(e.className))!; + const lineNumber = line.textContent ? parseInt(line.textContent.split(':').shift() || '0') : 0; + + return { + name: name.textContent || '', + lineNumber + }; +} + export class Debug extends Viewlet { - constructor(spectron: SpectronApplication) { - super(spectron); + constructor(code: Code, private commands: Commands, private editors: Editors, private editor: Editor) { + super(code); } async openDebugViewlet(): Promise { - await this.spectron.runCommand('workbench.view.debug'); - await this.spectron.client.waitForElement(DEBUG_VIEW); + await this.commands.runCommand('workbench.view.debug'); + await this.code.waitForElement(DEBUG_VIEW); } async configure(): Promise { - await this.spectron.client.waitAndClick(CONFIGURE); - await this.spectron.workbench.waitForEditorFocus('launch.json'); + await this.code.waitAndClick(CONFIGURE); + await this.editors.waitForEditorFocus('launch.json'); } async setBreakpointOnLine(lineNumber: number): Promise { - await this.spectron.client.waitForElement(`${GLYPH_AREA}(${lineNumber})`); - await this.spectron.client.leftClick(`${GLYPH_AREA}(${lineNumber})`, 5, 5); - await this.spectron.client.waitForElement(BREAKPOINT_GLYPH); + await this.code.waitForElement(`${GLYPH_AREA}(${lineNumber})`); + await this.code.waitAndClick(`${GLYPH_AREA}(${lineNumber})`, 5, 5); + await this.code.waitForElement(BREAKPOINT_GLYPH); } async startDebugging(): Promise { - await this.spectron.client.waitAndClick(START); - await this.spectron.client.waitForElement(PAUSE); - await this.spectron.client.waitForElement(DEBUG_STATUS_BAR); + await this.commands.runCommand('workbench.action.debug.start'); + await this.code.waitForElement(PAUSE); + await this.code.waitForElement(DEBUG_STATUS_BAR); const portPrefix = 'Port: '; - await this.spectron.client.waitFor(async () => { - const output = await this.getConsoleOutput(); - return output.join(''); - }, text => !!text && text.indexOf(portPrefix) >= 0); - const output = await this.getConsoleOutput(); - const lastOutput = output.pop(); + + const output = await this.waitForOutput(output => output.some(line => line.indexOf(portPrefix) >= 0)); + const lastOutput = output.filter(line => line.indexOf(portPrefix) >= 0)[0]; return lastOutput ? parseInt(lastOutput.substr(portPrefix.length)) : 3000; } async stepOver(): Promise { - await this.spectron.client.waitAndClick(STEP_OVER); + await this.code.waitAndClick(STEP_OVER); } async stepIn(): Promise { - await this.spectron.client.waitAndClick(STEP_IN); + await this.code.waitAndClick(STEP_IN); } async stepOut(): Promise { - await this.spectron.client.waitAndClick(STEP_OUT); + await this.code.waitAndClick(STEP_OUT); } async continue(): Promise { - await this.spectron.client.waitAndClick(CONTINUE); + await this.code.waitAndClick(CONTINUE); await this.waitForStackFrameLength(0); } async stopDebugging(): Promise { - await this.spectron.client.waitAndClick(STOP); - await this.spectron.client.waitForElement(TOOLBAR_HIDDEN); - await this.spectron.client.waitForElement(NOT_DEBUG_STATUS_BAR); + await this.code.waitAndClick(STOP); + await this.code.waitForElement(TOOLBAR_HIDDEN); + await this.code.waitForElement(NOT_DEBUG_STATUS_BAR); } async waitForStackFrame(func: (stackFrame: IStackFrame) => boolean, message: string): Promise { - return await this.spectron.client.waitFor(async () => { - const stackFrames = await this.getStackFrames(); - return stackFrames.filter(func)[0]; - }, void 0, `Waiting for Stack Frame: ${message}`); + const elements = await this.code.waitForElements(STACK_FRAME, true, elements => elements.some(e => func(toStackFrame(e)))); + return elements.map(toStackFrame).filter(s => func(s))[0]; } async waitForStackFrameLength(length: number): Promise { - return await this.spectron.client.waitFor(() => this.getStackFrames(), stackFrames => stackFrames.length === length); + await this.code.waitForElements(STACK_FRAME, false, result => result.length === length); } async focusStackFrame(name: string, message: string): Promise { - const stackFrame = await this.waitForStackFrame(sf => sf.name === name, message); - await this.spectron.client.spectron.client.elementIdClick(stackFrame.id); - await this.spectron.workbench.waitForTab(name); + await this.code.waitAndClick(SPECIFIC_STACK_FRAME(name)); + await this.editors.waitForTab(name); } async waitForReplCommand(text: string, accept: (result: string) => boolean): Promise { - await this.spectron.workbench.quickopen.runCommand('Debug: Focus Debug Console'); - await this.spectron.client.waitForActiveElement(REPL_FOCUSED); - await this.spectron.client.setValue(REPL_FOCUSED, text); + await this.commands.runCommand('Debug: Focus Debug Console'); + await this.code.waitForActiveElement(REPL_FOCUSED); + await this.code.waitForSetValue(REPL_FOCUSED, text); // Wait for the keys to be picked up by the editor model such that repl evalutes what just got typed - await this.spectron.workbench.editor.waitForEditorContents('debug:input', s => s.indexOf(text) >= 0); - await this.spectron.client.keys(['Enter', 'NULL']); - await this.spectron.client.waitForElement(CONSOLE_INPUT_OUTPUT); - await this.spectron.client.waitFor(async () => { - const result = await this.getConsoleOutput(); - return result[result.length - 1] || ''; - }, accept); - } - - async getLocalVariableCount(): Promise { - return await this.spectron.webclient.selectorExecute(VARIABLE, div => (Array.isArray(div) ? div : [div]).length); + await this.editor.waitForEditorContents('debug:input', s => s.indexOf(text) >= 0); + await this.code.dispatchKeybinding('enter'); + await this.code.waitForElement(CONSOLE_INPUT_OUTPUT); + await this.waitForOutput(output => accept(output[output.length - 1] || '')); } - async getStackFramesLength(): Promise { - const stackFrames = await this.getStackFrames(); - return stackFrames.length; + async waitForVariableCount(count: number): Promise { + await this.code.waitForElements(VARIABLE, false, els => els.length === count); } - private async getStackFrames(): Promise { - const result = await this.spectron.webclient.selectorExecute(STACK_FRAME, - div => (Array.isArray(div) ? div : [div]).map(element => { - const name = element.querySelector('.file-name') as HTMLElement; - const line = element.querySelector('.line-number') as HTMLElement; - const lineNumber = line.textContent ? parseInt(line.textContent.split(':').shift() || '0') : 0; - - return { - name: name.textContent, - lineNumber, - element - }; - }) - ); - - if (!Array.isArray(result)) { - return []; - } - - return result - .map(({ name, lineNumber, element }) => ({ name, lineNumber, id: element.ELEMENT })); - } - - private async getConsoleOutput(): Promise { - const result = await this.spectron.webclient.selectorExecute(CONSOLE_OUTPUT, - div => (Array.isArray(div) ? div : [div]).map(element => { - const value = element.querySelector('.value') as HTMLElement; - return value && value.textContent; - }).filter(line => !!line) - ); - - return result; + private async waitForOutput(fn: (output: string[]) => boolean): Promise { + const elements = await this.code.waitForElements(CONSOLE_OUTPUT, false, elements => fn(elements.map(e => e.textContent))); + return elements.map(e => e.textContent); } } diff --git a/test/smoke/src/areas/editor/editor.test.ts b/test/smoke/src/areas/editor/editor.test.ts index 0911d10010041..b892c49d5daa6 100644 --- a/test/smoke/src/areas/editor/editor.test.ts +++ b/test/smoke/src/areas/editor/editor.test.ts @@ -3,24 +3,20 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SpectronApplication } from '../../spectron/application'; +import { Application } from '../../application'; export function setup() { describe('Editor', () => { - before(function () { - this.app.suiteName = 'Editor'; - }); - it('shows correct quick outline', async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; await app.workbench.quickopen.openFile('www'); - await app.workbench.editor.openOutline(); + await app.workbench.quickopen.openQuickOutline(); await app.workbench.quickopen.waitForQuickOpenElements(names => names.length >= 6); }); it(`finds 'All References' to 'app'`, async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; await app.workbench.quickopen.openFile('www'); const references = await app.workbench.editor.findReferences('app', 7); @@ -31,11 +27,10 @@ export function setup() { }); it(`renames local 'app' variable`, async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; await app.workbench.quickopen.openFile('www'); await app.workbench.editor.rename('www', 7, 'app', 'newApp'); await app.workbench.editor.waitForEditorContents('www', contents => contents.indexOf('newApp') > -1); - await app.screenCapturer.capture('Rename result'); }); // it('folds/unfolds the code correctly', async function () { @@ -55,16 +50,16 @@ export function setup() { // }); it(`verifies that 'Go To Definition' works`, async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; await app.workbench.quickopen.openFile('app.js'); await app.workbench.editor.gotoDefinition('express', 11); - await app.workbench.waitForActiveTab('index.d.ts'); + await app.workbench.editors.waitForActiveTab('index.d.ts'); }); it(`verifies that 'Peek Definition' works`, async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; await app.workbench.quickopen.openFile('app.js'); const peek = await app.workbench.editor.peekDefinition('express', 11); diff --git a/test/smoke/src/areas/editor/editor.ts b/test/smoke/src/areas/editor/editor.ts index 14c95e3ff6bd4..41a04d85412f1 100644 --- a/test/smoke/src/areas/editor/editor.ts +++ b/test/smoke/src/areas/editor/editor.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SpectronApplication } from '../../spectron/application'; -import { QuickOutline } from './quickoutline'; import { References } from './peek'; +import { Commands } from '../workbench/workbench'; +import { Code } from '../../vscode/code'; const RENAME_BOX = '.monaco-editor .monaco-editor.rename-box'; const RENAME_INPUT = `${RENAME_BOX} .rename-input`; +const EDITOR = filename => `.monaco-editor[data-uri$="${filename}"]`; export class Editor { @@ -17,42 +18,35 @@ export class Editor { private static readonly FOLDING_EXPANDED = '.monaco-editor .margin .margin-view-overlays>:nth-child(${INDEX}) .folding'; private static readonly FOLDING_COLLAPSED = `${Editor.FOLDING_EXPANDED}.collapsed`; - constructor(private spectron: SpectronApplication) { - } - - async openOutline(): Promise { - const outline = new QuickOutline(this.spectron); - await outline.open(); - return outline; - } + constructor(private code: Code, private commands: Commands) { } async findReferences(term: string, line: number): Promise { await this.clickOnTerm(term, line); - await this.spectron.workbench.quickopen.runCommand('Find All References'); - const references = new References(this.spectron); + await this.commands.runCommand('Find All References'); + const references = new References(this.code); await references.waitUntilOpen(); return references; } async rename(filename: string, line: number, from: string, to: string): Promise { await this.clickOnTerm(from, line); - await this.spectron.workbench.quickopen.runCommand('Rename Symbol'); + await this.commands.runCommand('Rename Symbol'); - await this.spectron.client.waitForActiveElement(RENAME_INPUT); - await this.spectron.client.setValue(RENAME_INPUT, to); + await this.code.waitForActiveElement(RENAME_INPUT); + await this.code.waitForSetValue(RENAME_INPUT, to); - await this.spectron.client.keys(['Enter', 'NULL']); + await this.code.dispatchKeybinding('enter'); } async gotoDefinition(term: string, line: number): Promise { await this.clickOnTerm(term, line); - await this.spectron.workbench.quickopen.runCommand('Go to Definition'); + await this.commands.runCommand('Go to Definition'); } async peekDefinition(term: string, line: number): Promise { await this.clickOnTerm(term, line); - await this.spectron.workbench.quickopen.runCommand('Peek Definition'); - const peek = new References(this.spectron); + await this.commands.runCommand('Peek Definition'); + const peek = new References(this.code); await peek.waitUntilOpen(); return peek; } @@ -60,7 +54,7 @@ export class Editor { async waitForHighlightingLine(line: number): Promise { const currentLineIndex = await this.getViewLineIndex(line); if (currentLineIndex) { - await this.spectron.client.waitForElement(`.monaco-editor .view-overlays>:nth-child(${currentLineIndex}) .current-line`); + await this.code.waitForElement(`.monaco-editor .view-overlays>:nth-child(${currentLineIndex}) .current-line`); return; } throw new Error('Cannot find line ' + line); @@ -68,24 +62,20 @@ export class Editor { async getSelector(term: string, line: number): Promise { const lineIndex = await this.getViewLineIndex(line); - const classNames = await this.spectron.client.waitFor(() => this.getClassSelectors(term, lineIndex), classNames => classNames && !!classNames.length, 'Getting class names for editor lines'); + const classNames = await this.getClassSelectors(term, lineIndex); return `${Editor.VIEW_LINES}>:nth-child(${lineIndex}) span span.${classNames[0]}`; } async foldAtLine(line: number): Promise { const lineIndex = await this.getViewLineIndex(line); - await this.spectron.client.waitAndClick(Editor.FOLDING_EXPANDED.replace('${INDEX}', '' + lineIndex)); - await this.spectron.client.waitForElement(Editor.FOLDING_COLLAPSED.replace('${INDEX}', '' + lineIndex)); + await this.code.waitAndClick(Editor.FOLDING_EXPANDED.replace('${INDEX}', '' + lineIndex)); + await this.code.waitForElement(Editor.FOLDING_COLLAPSED.replace('${INDEX}', '' + lineIndex)); } async unfoldAtLine(line: number): Promise { const lineIndex = await this.getViewLineIndex(line); - await this.spectron.client.waitAndClick(Editor.FOLDING_COLLAPSED.replace('${INDEX}', '' + lineIndex)); - await this.spectron.client.waitForElement(Editor.FOLDING_EXPANDED.replace('${INDEX}', '' + lineIndex)); - } - - async waitUntilHidden(line: number): Promise { - await this.spectron.client.waitFor(() => this.getViewLineIndexWithoutWait(line), lineNumber => lineNumber === undefined, 'Waiting until line number is hidden'); + await this.code.waitAndClick(Editor.FOLDING_COLLAPSED.replace('${INDEX}', '' + lineIndex)); + await this.code.waitForElement(Editor.FOLDING_EXPANDED.replace('${INDEX}', '' + lineIndex)); } async waitUntilShown(line: number): Promise { @@ -94,101 +84,44 @@ export class Editor { async clickOnTerm(term: string, line: number): Promise { const selector = await this.getSelector(term, line); - await this.spectron.client.waitAndClick(selector); + await this.code.waitAndClick(selector); } async waitForTypeInEditor(filename: string, text: string, selectorPrefix = ''): Promise { - const editor = [ - selectorPrefix || '', - `.monaco-editor[data-uri$="${filename}"]` - ].join(' '); + const editor = [selectorPrefix || '', EDITOR(filename)].join(' '); - await this.spectron.client.element(editor); + await this.code.waitForElement(editor); const textarea = `${editor} textarea`; - await this.spectron.client.waitForActiveElement(textarea); + await this.code.waitForActiveElement(textarea); - // https://github.com/Microsoft/vscode/issues/34203#issuecomment-334441786 - await this.spectron.client.spectron.client.selectorExecute(textarea, (elements, text) => { - const textarea = (Array.isArray(elements) ? elements : [elements])[0] as HTMLTextAreaElement; - const start = textarea.selectionStart; - const newStart = start + text.length; - const value = textarea.value; - const newValue = value.substr(0, start) + text + value.substr(start); - - textarea.value = newValue; - textarea.setSelectionRange(newStart, newStart); - - const event = new Event('input', { 'bubbles': true, 'cancelable': true }); - textarea.dispatchEvent(event); - }, text); + await this.code.waitForTypeInEditor(textarea, text); await this.waitForEditorContents(filename, c => c.indexOf(text) > -1, selectorPrefix); } async waitForEditorContents(filename: string, accept: (contents: string) => boolean, selectorPrefix = ''): Promise { - const selector = [ - selectorPrefix || '', - `.monaco-editor[data-uri$="${filename}"] .view-lines` - ].join(' '); - - return this.spectron.client.waitForTextContent(selector, undefined, c => accept(c.replace(/\u00a0/g, ' '))); - } - - async waitForActiveEditor(filename: string): Promise { - const selector = `.editor-container .monaco-editor[data-uri$="${filename}"] textarea`; - return this.spectron.client.waitForActiveElement(selector); + const selector = [selectorPrefix || '', `${EDITOR(filename)} .view-lines`].join(' '); + return this.code.waitForTextContent(selector, undefined, c => accept(c.replace(/\u00a0/g, ' '))); } - // async waitForActiveEditorFirstLineText(filename: string): Promise { - // const selector = `.editor-container .monaco-editor[data-uri$="${filename}"] textarea`; - // const result = await this.spectron.client.waitFor( - // () => this.spectron.client.spectron.client.execute(s => { - // if (!document.activeElement.matches(s)) { - // return undefined; - // } - - // let element: Element | null = document.activeElement; - // while (element && !/monaco-editor/.test(element.className) && element !== document.body) { - // element = element.parentElement; - // } - - // if (element && /monaco-editor/.test(element.className)) { - // const firstLine = element.querySelector('.view-lines span span:nth-child(1)'); - - // if (firstLine) { - // return (firstLine.textContent || '').replace(/\u00a0/g, ' '); // DAMN - // } - // } - - // return undefined; - // }, selector), - // r => typeof r.value === 'string', - // `wait for active editor first line: ${selector}` - // ); - - // return result.value; - // } - private async getClassSelectors(term: string, viewline: number): Promise { - const result: { text: string, className: string }[] = await this.spectron.webclient.selectorExecute(`${Editor.VIEW_LINES}>:nth-child(${viewline}) span span`, - elements => (Array.isArray(elements) ? elements : [elements]) - .map(element => ({ text: element.textContent, className: element.className }))); - return result.filter(r => r.text === term).map(({ className }) => className); + const elements = await this.code.waitForElements(`${Editor.VIEW_LINES}>:nth-child(${viewline}) span span`, false, els => els.some(el => el.textContent === term)); + const { className } = elements.filter(r => r.textContent === term)[0]; + return className.split(/\s/g); } private async getViewLineIndex(line: number): Promise { - return await this.spectron.client.waitFor(() => this.getViewLineIndexWithoutWait(line), void 0, 'Getting line index'); - } + const elements = await this.code.waitForElements(Editor.LINE_NUMBERS, false, els => { + return els.some(el => el.textContent === `${line}`); + }); - private async getViewLineIndexWithoutWait(line: number): Promise { - const lineNumbers = await this.spectron.webclient.selectorExecute(Editor.LINE_NUMBERS, - elements => (Array.isArray(elements) ? elements : [elements]).map(element => element.textContent)); - for (let index = 0; index < lineNumbers.length; index++) { - if (lineNumbers[index] === `${line}`) { + for (let index = 0; index < elements.length; index++) { + if (elements[index].textContent === `${line}`) { return index + 1; } } - return undefined; + + throw new Error('Line not found'); } } \ No newline at end of file diff --git a/test/smoke/src/areas/editor/editors.ts b/test/smoke/src/areas/editor/editors.ts new file mode 100644 index 0000000000000..4ac026780e46d --- /dev/null +++ b/test/smoke/src/areas/editor/editors.ts @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Commands } from '../workbench/workbench'; +import { Code } from '../../vscode/code'; + +export class Editors { + + constructor(private code: Code, private commands: Commands) { } + + async saveOpenedFile(): Promise { + await this.commands.runCommand('workbench.action.files.save'); + } + + async selectTab(tabName: string, untitled: boolean = false): Promise { + await this.code.waitAndClick(`.tabs-container div.tab[aria-label="${tabName}, tab"]`); + await this.waitForEditorFocus(tabName, untitled); + } + + async waitForActiveEditor(filename: string): Promise { + const selector = `.editor-container .monaco-editor[data-uri$="${filename}"] textarea`; + return this.code.waitForActiveElement(selector); + } + + async waitForEditorFocus(fileName: string, untitled: boolean = false): Promise { + await this.waitForActiveTab(fileName); + await this.waitForActiveEditor(fileName); + } + + async waitForActiveTab(fileName: string, isDirty: boolean = false): Promise { + await this.code.waitForElement(`.tabs-container div.tab.active${isDirty ? '.dirty' : ''}[aria-selected="true"][aria-label="${fileName}, tab"]`); + } + + async waitForTab(fileName: string, isDirty: boolean = false): Promise { + await this.code.waitForElement(`.tabs-container div.tab${isDirty ? '.dirty' : ''}[aria-label="${fileName}, tab"]`); + } + + async newUntitledFile(): Promise { + await this.commands.runCommand('workbench.action.files.newUntitledFile'); + await this.waitForEditorFocus('Untitled-1', true); + } +} \ No newline at end of file diff --git a/test/smoke/src/areas/editor/peek.ts b/test/smoke/src/areas/editor/peek.ts index 9c91282127092..55f7f5ece0666 100644 --- a/test/smoke/src/areas/editor/peek.ts +++ b/test/smoke/src/areas/editor/peek.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SpectronApplication } from '../../spectron/application'; +import { Code } from '../../vscode/code'; export class References { @@ -12,30 +12,29 @@ export class References { private static readonly REFERENCES_TITLE_COUNT = `${References.REFERENCES_WIDGET} .head .peekview-title .meta`; private static readonly REFERENCES = `${References.REFERENCES_WIDGET} .body .ref-tree.inline .monaco-tree-row .reference`; - constructor(private spectron: SpectronApplication) { - } + constructor(private code: Code) { } - public async waitUntilOpen(): Promise { - await this.spectron.client.waitForElement(References.REFERENCES_WIDGET); + async waitUntilOpen(): Promise { + await this.code.waitForElement(References.REFERENCES_WIDGET); } - public async waitForReferencesCountInTitle(count: number): Promise { - await this.spectron.client.waitForText(References.REFERENCES_TITLE_COUNT, void 0, titleCount => { + async waitForReferencesCountInTitle(count: number): Promise { + await this.code.waitForTextContent(References.REFERENCES_TITLE_COUNT, void 0, titleCount => { const matches = titleCount.match(/\d+/); return matches ? parseInt(matches[0]) === count : false; }); } - public async waitForReferencesCount(count: number): Promise { - await this.spectron.client.waitForElements(References.REFERENCES, result => result && result.length === count); + async waitForReferencesCount(count: number): Promise { + await this.code.waitForElements(References.REFERENCES, false, result => result && result.length === count); } - public async waitForFile(file: string): Promise { - await this.spectron.client.waitForText(References.REFERENCES_TITLE_FILE_NAME, file); + async waitForFile(file: string): Promise { + await this.code.waitForTextContent(References.REFERENCES_TITLE_FILE_NAME, file); } - public async close(): Promise { - await this.spectron.client.keys(['Escape', 'NULL']); - await this.spectron.client.waitForElement(References.REFERENCES_WIDGET, element => !element); + async close(): Promise { + await this.code.dispatchKeybinding('escape'); + await this.code.waitForElement(References.REFERENCES_WIDGET, element => !element); } } \ No newline at end of file diff --git a/test/smoke/src/areas/editor/quickoutline.ts b/test/smoke/src/areas/editor/quickoutline.ts deleted file mode 100644 index 852bd8b9145fb..0000000000000 --- a/test/smoke/src/areas/editor/quickoutline.ts +++ /dev/null @@ -1,28 +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 { SpectronApplication } from '../../spectron/application'; -import { QuickOpen } from '../quickopen/quickopen'; - -export class QuickOutline extends QuickOpen { - - constructor(spectron: SpectronApplication) { - super(spectron); - } - - public async open(): Promise { - await this.spectron.client.waitFor(async () => { - await this.spectron.runCommand('workbench.action.gotoSymbol'); - const entry = await this.spectron.client.element('div[aria-label="Quick Picker"] .monaco-tree-rows.show-twisties div.monaco-tree-row .quick-open-entry'); - if (entry) { - const text = await this.spectron.client.getText('div[aria-label="Quick Picker"] .monaco-tree-rows.show-twisties div.monaco-tree-row .quick-open-entry .monaco-icon-label .label-name .monaco-highlighted-label span'); - if (text !== 'No symbol information for the file') { - return entry; - } - } - await this.closeQuickOpen(); - }, undefined, 'Opening Outline'); - } -} diff --git a/test/smoke/src/areas/explorer/explorer.test.ts b/test/smoke/src/areas/explorer/explorer.test.ts index ab77642f6ddb9..946105649b7a9 100644 --- a/test/smoke/src/areas/explorer/explorer.test.ts +++ b/test/smoke/src/areas/explorer/explorer.test.ts @@ -3,16 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SpectronApplication } from '../../spectron/application'; +import { Application } from '../../application'; export function setup() { describe('Explorer', () => { - before(function () { - this.app.suiteName = 'Explorer'; - }); - it('quick open search produces correct result', async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; const expectedNames = [ '.eslintrc.json', 'tasks.json', @@ -25,11 +21,11 @@ export function setup() { await app.workbench.quickopen.openQuickOpen('.js'); await app.workbench.quickopen.waitForQuickOpenElements(names => expectedNames.every(n => names.some(m => n === m))); - await app.client.keys(['Escape', 'NULL']); + await app.code.dispatchKeybinding('escape'); }); it('quick open respects fuzzy matching', async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; const expectedNames = [ 'tasks.json', 'app.js', @@ -38,7 +34,7 @@ export function setup() { await app.workbench.quickopen.openQuickOpen('a.s'); await app.workbench.quickopen.waitForQuickOpenElements(names => expectedNames.every(n => names.some(m => n === m))); - await app.client.keys(['Escape', 'NULL']); + await app.code.dispatchKeybinding('escape'); }); }); } \ No newline at end of file diff --git a/test/smoke/src/areas/explorer/explorer.ts b/test/smoke/src/areas/explorer/explorer.ts index 6159e62c45f02..ef9231393ef12 100644 --- a/test/smoke/src/areas/explorer/explorer.ts +++ b/test/smoke/src/areas/explorer/explorer.ts @@ -3,33 +3,34 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SpectronApplication } from '../../spectron/application'; import { Viewlet } from '../workbench/viewlet'; - +import { Editors } from '../editor/editors'; +import { Commands } from '../workbench/workbench'; +import { Code } from '../../vscode/code'; export class Explorer extends Viewlet { private static readonly EXPLORER_VIEWLET = 'div[id="workbench.view.explorer"]'; private static readonly OPEN_EDITORS_VIEW = `${Explorer.EXPLORER_VIEWLET} .split-view-view:nth-child(1) .title`; - constructor(spectron: SpectronApplication) { - super(spectron); + constructor(code: Code, private commands: Commands, private editors: Editors) { + super(code); } - public openExplorerView(): Promise { - return this.spectron.runCommand('workbench.view.explorer'); + openExplorerView(): Promise { + return this.commands.runCommand('workbench.view.explorer'); } - public getOpenEditorsViewTitle(): Promise { - return this.spectron.client.waitForText(Explorer.OPEN_EDITORS_VIEW); + async waitForOpenEditorsViewTitle(fn: (title: string) => boolean): Promise { + await this.code.waitForTextContent(Explorer.OPEN_EDITORS_VIEW, undefined, fn); } - public async openFile(fileName: string): Promise { - await this.spectron.client.doubleClickAndWait(`div[class="monaco-icon-label file-icon ${fileName}-name-file-icon ${this.getExtensionSelector(fileName)} explorer-item"]`); - await this.spectron.workbench.waitForEditorFocus(fileName); + async openFile(fileName: string): Promise { + await this.code.waitAndDoubleClick(`div[class="monaco-icon-label file-icon ${fileName}-name-file-icon ${this.getExtensionSelector(fileName)} explorer-item"]`); + await this.editors.waitForEditorFocus(fileName); } - public getExtensionSelector(fileName: string): string { + getExtensionSelector(fileName: string): string { const extension = fileName.split('.')[1]; if (extension === 'js') { return 'js-ext-file-icon ext-file-icon javascript-lang-file-icon'; diff --git a/test/smoke/src/areas/extensions/extensions.test.ts b/test/smoke/src/areas/extensions/extensions.test.ts index 33776a254050f..ca3cbc1774f9f 100644 --- a/test/smoke/src/areas/extensions/extensions.test.ts +++ b/test/smoke/src/areas/extensions/extensions.test.ts @@ -3,17 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; -import { SpectronApplication, Quality } from '../../spectron/application'; +import { Application, Quality } from '../../application'; export function setup() { describe('Extensions', () => { - before(function () { - this.app.suiteName = 'Extensions'; - }); - it(`install and activate vscode-smoketest-check extension`, async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; if (app.quality === Quality.Dev) { this.skip(); @@ -23,16 +18,12 @@ export function setup() { const extensionName = 'vscode-smoketest-check'; await app.workbench.extensions.openExtensionsViewlet(); - const installed = await app.workbench.extensions.installExtension(extensionName); - assert.ok(installed); + await app.workbench.extensions.installExtension(extensionName); await app.reload(); await app.workbench.extensions.waitForExtensionsViewlet(); - await app.workbench.quickopen.runCommand('Smoke Test Check'); - - const statusbarText = await app.workbench.statusbar.getStatusbarTextByTitle('smoke test'); - await app.screenCapturer.capture('Statusbar'); - assert.equal(statusbarText, 'VS Code Smoke Test Check'); + await app.workbench.runCommand('Smoke Test Check'); + await app.workbench.statusbar.waitForStatusbarText('smoke test', 'VS Code Smoke Test Check'); }); }); } \ No newline at end of file diff --git a/test/smoke/src/areas/extensions/extensions.ts b/test/smoke/src/areas/extensions/extensions.ts index d0c3f97320d9f..64bfb07dd3d85 100644 --- a/test/smoke/src/areas/extensions/extensions.ts +++ b/test/smoke/src/areas/extensions/extensions.ts @@ -3,40 +3,36 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SpectronApplication } from '../../spectron/application'; import { Viewlet } from '../workbench/viewlet'; +import { Commands } from '../workbench/workbench'; +import { Code } from '../../vscode/code'; const SEARCH_BOX = 'div.extensions-viewlet[id="workbench.view.extensions"] input.search-box'; export class Extensions extends Viewlet { - constructor(spectron: SpectronApplication) { - super(spectron); + constructor(code: Code, private commands: Commands) { + super(code); } async openExtensionsViewlet(): Promise { - await this.spectron.runCommand('workbench.view.extensions'); + await this.commands.runCommand('workbench.view.extensions'); await this.waitForExtensionsViewlet(); } async waitForExtensionsViewlet(): Promise { - await this.spectron.client.waitForActiveElement(SEARCH_BOX); + await this.code.waitForActiveElement(SEARCH_BOX); } async searchForExtension(name: string): Promise { - await this.spectron.client.click(SEARCH_BOX); - await this.spectron.client.waitForActiveElement(SEARCH_BOX); - await this.spectron.client.setValue(SEARCH_BOX, name); + await this.code.waitAndClick(SEARCH_BOX); + await this.code.waitForActiveElement(SEARCH_BOX); + await this.code.waitForSetValue(SEARCH_BOX, name); } - async installExtension(name: string): Promise { + async installExtension(name: string): Promise { await this.searchForExtension(name); - - // we might want to wait for a while longer since the Marketplace can be slow - // a minute should do - await this.spectron.client.waitFor(() => this.spectron.client.click(`div.extensions-viewlet[id="workbench.view.extensions"] .monaco-list-row[aria-label="${name}"] .extension li[class='action-item'] .extension-action.install`), void 0, 'waiting for install button', 600); - - await this.spectron.client.waitForElement(`div.extensions-viewlet[id="workbench.view.extensions"] .monaco-list-row[aria-label="${name}"] .extension li[class='action-item'] .extension-action.reload`); - return true; + await this.code.waitAndClick(`div.extensions-viewlet[id="workbench.view.extensions"] .monaco-list-row[aria-label="${name}"] .extension li[class='action-item'] .extension-action.install`); + await this.code.waitForElement(`div.extensions-viewlet[id="workbench.view.extensions"] .monaco-list-row[aria-label="${name}"] .extension li[class='action-item'] .extension-action.reload`); } } \ No newline at end of file diff --git a/test/smoke/src/areas/git/git.test.ts b/test/smoke/src/areas/git/git.test.ts index cc383b9b8c5aa..e5c2669b7222c 100644 --- a/test/smoke/src/areas/git/git.test.ts +++ b/test/smoke/src/areas/git/git.test.ts @@ -4,47 +4,41 @@ *--------------------------------------------------------------------------------------------*/ import * as cp from 'child_process'; -import { SpectronApplication } from '../../spectron/application'; +import { Application } from '../../application'; const DIFF_EDITOR_LINE_INSERT = '.monaco-diff-editor .editor.modified .line-insert'; const SYNC_STATUSBAR = 'div[id="workbench.parts.statusbar"] .statusbar-entry a[title$="Synchronize Changes"]'; export function setup() { describe('Git', () => { - before(async function () { - const app = this.app as SpectronApplication; - app.suiteName = 'Git'; - }); - it('reflects working tree changes', async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; await app.workbench.scm.openSCMViewlet(); await app.workbench.quickopen.openFile('app.js'); await app.workbench.editor.waitForTypeInEditor('app.js', '.foo{}'); - await app.workbench.saveOpenedFile(); + await app.workbench.editors.saveOpenedFile(); await app.workbench.quickopen.openFile('index.jade'); await app.workbench.editor.waitForTypeInEditor('index.jade', 'hello world'); - await app.workbench.saveOpenedFile(); + await app.workbench.editors.saveOpenedFile(); await app.workbench.scm.refreshSCMViewlet(); await app.workbench.scm.waitForChange('app.js', 'Modified'); await app.workbench.scm.waitForChange('index.jade', 'Modified'); - await app.screenCapturer.capture('changes'); }); it('opens diff editor', async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; await app.workbench.scm.openSCMViewlet(); await app.workbench.scm.openChange('app.js'); - await app.client.waitForElement(DIFF_EDITOR_LINE_INSERT); + await app.code.waitForElement(DIFF_EDITOR_LINE_INSERT); }); it('stages correctly', async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; await app.workbench.scm.openSCMViewlet(); @@ -58,7 +52,7 @@ export function setup() { }); it(`stages, commits changes and verifies outgoing change`, async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; await app.workbench.scm.openSCMViewlet(); @@ -67,13 +61,13 @@ export function setup() { await app.workbench.scm.waitForChange('app.js', 'Index Modified'); await app.workbench.scm.commit('first commit'); - await app.client.waitForText(SYNC_STATUSBAR, ' 0↓ 1↑'); + await app.code.waitForTextContent(SYNC_STATUSBAR, ' 0↓ 1↑'); - await app.workbench.quickopen.runCommand('Git: Stage All Changes'); + await app.workbench.runCommand('Git: Stage All Changes'); await app.workbench.scm.waitForChange('index.jade', 'Index Modified'); await app.workbench.scm.commit('second commit'); - await app.client.waitForText(SYNC_STATUSBAR, ' 0↓ 2↑'); + await app.code.waitForTextContent(SYNC_STATUSBAR, ' 0↓ 2↑'); cp.execSync('git reset --hard origin/master', { cwd: app.workspacePath }); }); diff --git a/test/smoke/src/areas/git/scm.ts b/test/smoke/src/areas/git/scm.ts index 7f25c3a38813e..9c2a6f3e871c2 100644 --- a/test/smoke/src/areas/git/scm.ts +++ b/test/smoke/src/areas/git/scm.ts @@ -3,8 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SpectronApplication } from '../../spectron/application'; import { Viewlet } from '../workbench/viewlet'; +import { Commands } from '../workbench/workbench'; +import { IElement } from '../../vscode/driver'; +import { findElement, findElements, Code } from '../../vscode/code'; const VIEWLET = 'div[id="workbench.view.scm"]'; const SCM_INPUT = `${VIEWLET} .scm-editor textarea`; @@ -22,84 +24,61 @@ interface Change { actions: string[]; } +function toChange(element: IElement): Change { + const name = findElement(element, e => /\blabel-name\b/.test(e.className))!; + const type = element.attributes['data-tooltip'] || ''; + + const actionElementList = findElements(element, e => /\baction-label\b/.test(e.className)); + const actions = actionElementList.map(e => e.attributes['title']); + + return { + name: name.textContent || '', + type, + actions + }; +} + + export class SCM extends Viewlet { - constructor(spectron: SpectronApplication) { - super(spectron); + constructor(code: Code, private commands: Commands) { + super(code); } async openSCMViewlet(): Promise { - await this.spectron.runCommand('workbench.view.scm'); - await this.spectron.client.waitForElement(SCM_INPUT); + await this.commands.runCommand('workbench.view.scm'); + await this.code.waitForElement(SCM_INPUT); } - waitForChange(name: string, type?: string): Promise { - return this.spectron.client.waitFor(async () => { - const changes = await this.queryChanges(name, type); - return changes.length; - }, l => l > 0, 'Getting SCM changes') as Promise as Promise; + async waitForChange(name: string, type?: string): Promise { + const func = (change: Change) => change.name === name && (!type || change.type === type); + await this.code.waitForElements(SCM_RESOURCE, true, elements => elements.some(e => func(toChange(e)))); } async refreshSCMViewlet(): Promise { - await this.spectron.client.click(REFRESH_COMMAND); - } - - private async queryChanges(name: string, type?: string): Promise { - const result = await this.spectron.webclient.selectorExecute(SCM_RESOURCE, (div, name, type) => { - return (Array.isArray(div) ? div : [div]) - .map(element => { - const name = element.querySelector('.label-name') as HTMLElement; - const type = element.getAttribute('data-tooltip') || ''; - const actionElementList = element.querySelectorAll('.actions .action-label'); - const actions: string[] = []; - - for (let i = 0; i < actionElementList.length; i++) { - const element = actionElementList.item(i) as HTMLElement; - actions.push(element.title); - } - - return { - name: name.textContent, - type, - actions - }; - }) - .filter(change => { - if (change.name !== name) { - return false; - } - - if (type && (change.type !== type)) { - return false; - } - - return true; - }); - }, name, type); - - return result; + await this.code.waitAndClick(REFRESH_COMMAND); } async openChange(name: string): Promise { - await this.spectron.client.waitAndClick(SCM_RESOURCE_CLICK(name)); + await this.code.waitAndClick(SCM_RESOURCE_CLICK(name)); } async stage(name: string): Promise { - await this.spectron.client.waitAndClick(SCM_RESOURCE_ACTION_CLICK(name, 'Stage Changes')); + await this.code.waitAndClick(SCM_RESOURCE_ACTION_CLICK(name, 'Stage Changes')); } async stageAll(): Promise { - await this.spectron.client.waitAndClick(SCM_RESOURCE_GROUP_COMMAND_CLICK('Stage All Changes')); + await this.code.waitAndClick(SCM_RESOURCE_GROUP_COMMAND_CLICK('Stage All Changes')); } async unstage(name: string): Promise { - await this.spectron.client.waitAndClick(SCM_RESOURCE_ACTION_CLICK(name, 'Unstage Changes')); + await this.code.waitAndClick(SCM_RESOURCE_ACTION_CLICK(name, 'Unstage Changes')); } async commit(message: string): Promise { - await this.spectron.client.waitAndClick(SCM_INPUT); - await this.spectron.client.waitForActiveElement(SCM_INPUT); - await this.spectron.client.setValue(SCM_INPUT, message); - await this.spectron.client.waitAndClick(COMMIT_COMMAND); + await this.code.waitAndClick(SCM_INPUT); + await this.code.waitForActiveElement(SCM_INPUT); + await this.code.waitForSetValue(SCM_INPUT, message); + await this.code.waitAndClick(COMMIT_COMMAND); } } \ No newline at end of file diff --git a/test/smoke/src/areas/multiroot/multiroot.test.ts b/test/smoke/src/areas/multiroot/multiroot.test.ts index b81df706cf3a9..86449f54858cd 100644 --- a/test/smoke/src/areas/multiroot/multiroot.test.ts +++ b/test/smoke/src/areas/multiroot/multiroot.test.ts @@ -3,16 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; -import { SpectronApplication } from '../../spectron/application'; +import { Application } from '../../application'; export function setup() { describe('Multiroot', () => { before(async function () { - this.app.suiteName = 'Multiroot'; - - const app = this.app as SpectronApplication; + const app = this.app as Application; // restart with preventing additional windows from restoring // to ensure the window after restart is the multi-root workspace @@ -20,7 +17,7 @@ export function setup() { }); it('shows results from all folders', async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; await app.workbench.quickopen.openQuickOpen('*.*'); await app.workbench.quickopen.waitForQuickOpenElements(names => names.length === 6); @@ -28,10 +25,8 @@ export function setup() { }); it('shows workspace name in title', async function () { - const app = this.app as SpectronApplication; - const title = await app.client.getTitle(); - await app.screenCapturer.capture('window title'); - assert.ok(title.indexOf('smoketest (Workspace)') >= 0); + const app = this.app as Application; + await app.code.waitForTitle(title => /smoketest \(Workspace\)/i.test(title)); }); }); } \ No newline at end of file diff --git a/test/smoke/src/areas/preferences/keybindings.ts b/test/smoke/src/areas/preferences/keybindings.ts index 1de2eba3470d7..807a971891eb0 100644 --- a/test/smoke/src/areas/preferences/keybindings.ts +++ b/test/smoke/src/areas/preferences/keybindings.ts @@ -3,26 +3,28 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SpectronApplication } from '../../spectron/application'; +import { Commands } from '../workbench/workbench'; +import { Code } from '../../vscode/code'; const SEARCH_INPUT = '.settings-search-input input'; export class KeybindingsEditor { - constructor(private spectron: SpectronApplication) { } + constructor(private code: Code, private commands: Commands) { } - async updateKeybinding(command: string, keys: string[], ariaLabel: string): Promise { - await this.spectron.runCommand('workbench.action.openGlobalKeybindings'); - await this.spectron.client.waitForActiveElement(SEARCH_INPUT); - await this.spectron.client.setValue(SEARCH_INPUT, command); + async updateKeybinding(command: string, keybinding: string, ariaLabel: string): Promise { + await this.commands.runCommand('workbench.action.openGlobalKeybindings'); + await this.code.waitForActiveElement(SEARCH_INPUT); + await this.code.waitForSetValue(SEARCH_INPUT, command); - await this.spectron.client.waitAndClick('div[aria-label="Keybindings"] .monaco-list-row.keybinding-item'); - await this.spectron.client.waitForElement('div[aria-label="Keybindings"] .monaco-list-row.keybinding-item.focused.selected'); + await this.code.waitAndClick('div[aria-label="Keybindings"] .monaco-list-row.keybinding-item'); + await this.code.waitForElement('div[aria-label="Keybindings"] .monaco-list-row.keybinding-item.focused.selected'); - await this.spectron.client.waitAndClick('div[aria-label="Keybindings"] .monaco-list-row.keybinding-item .action-item .icon.add'); - await this.spectron.client.waitForElement('.defineKeybindingWidget .monaco-inputbox.synthetic-focus'); + await this.code.waitAndClick('div[aria-label="Keybindings"] .monaco-list-row.keybinding-item .action-item .icon.add'); + await this.code.waitForElement('.defineKeybindingWidget .monaco-inputbox.synthetic-focus'); - await this.spectron.client.keys([...keys, 'NULL', 'Enter', 'NULL']); - await this.spectron.client.waitForElement(`div[aria-label="Keybindings"] div[aria-label="Keybinding is ${ariaLabel}."]`); + await this.code.dispatchKeybinding(keybinding); + await this.code.dispatchKeybinding('enter'); + await this.code.waitForElement(`div[aria-label="Keybindings"] div[aria-label="Keybinding is ${ariaLabel}."]`); } } \ No newline at end of file diff --git a/test/smoke/src/areas/preferences/preferences.test.ts b/test/smoke/src/areas/preferences/preferences.test.ts index f273d6a9c12fb..1b2bbbec548a1 100644 --- a/test/smoke/src/areas/preferences/preferences.test.ts +++ b/test/smoke/src/areas/preferences/preferences.test.ts @@ -3,45 +3,34 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; - -import { SpectronApplication } from '../../spectron/application'; +import { Application } from '../../application'; import { ActivityBarPosition } from '../activitybar/activityBar'; export function setup() { describe('Preferences', () => { - before(function () { - this.app.suiteName = 'Preferences'; - }); - it('turns off editor line numbers and verifies the live change', async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; await app.workbench.explorer.openFile('app.js'); - let lineNumbers = await app.client.waitForElements('.line-numbers'); - await app.screenCapturer.capture('app.js has line numbers'); - assert.ok(!!lineNumbers.length, 'Line numbers are not present in the editor before disabling them.'); + await app.code.waitForElements('.line-numbers', false, elements => !!elements.length); await app.workbench.settingsEditor.addUserSetting('editor.lineNumbers', '"off"'); - await app.workbench.selectTab('app.js'); - lineNumbers = await app.client.waitForElements('.line-numbers', result => !result || result.length === 0); - - await app.screenCapturer.capture('line numbers hidden'); - assert.ok(!lineNumbers.length, 'Line numbers are still present in the editor after disabling them.'); + await app.workbench.editors.selectTab('app.js'); + await app.code.waitForElements('.line-numbers', false, result => !result || result.length === 0); }); it(`changes 'workbench.action.toggleSidebarPosition' command key binding and verifies it`, async function () { - const app = this.app as SpectronApplication; - assert.ok(await app.workbench.activitybar.getActivityBar(ActivityBarPosition.LEFT), 'Activity bar should be positioned on the left.'); + const app = this.app as Application; + await app.workbench.activitybar.waitForActivityBar(ActivityBarPosition.LEFT); - await app.workbench.keybindingsEditor.updateKeybinding('workbench.action.toggleSidebarPosition', ['Control', 'u'], 'Control+U'); + await app.workbench.keybindingsEditor.updateKeybinding('workbench.action.toggleSidebarPosition', 'ctrl+u', 'Control+U'); - await app.client.keys(['Control', 'u', 'NULL']); - assert.ok(await app.workbench.activitybar.getActivityBar(ActivityBarPosition.RIGHT), 'Activity bar was not moved to right after toggling its position.'); + await app.code.dispatchKeybinding('ctrl+u'); + await app.workbench.activitybar.waitForActivityBar(ActivityBarPosition.RIGHT); }); after(async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; await app.workbench.settingsEditor.clearUserSettings(); }); }); diff --git a/test/smoke/src/areas/preferences/settings.ts b/test/smoke/src/areas/preferences/settings.ts index 657d71357e9cf..3ac9805962977 100644 --- a/test/smoke/src/areas/preferences/settings.ts +++ b/test/smoke/src/areas/preferences/settings.ts @@ -5,7 +5,10 @@ import * as fs from 'fs'; import * as path from 'path'; -import { SpectronApplication } from '../../spectron/application'; +import { Commands } from '../workbench/workbench'; +import { Editor } from '../editor/editor'; +import { Editors } from '../editor/editors'; +import { Code } from '../../vscode/code'; export enum ActivityBarPosition { LEFT = 0, @@ -17,29 +20,26 @@ const EDITOR = '.editable-preferences-editor-container .monaco-editor textarea'; export class SettingsEditor { - constructor(private spectron: SpectronApplication) { } + constructor(private code: Code, private userDataPath: string, private commands: Commands, private editors: Editors, private editor: Editor) { } async addUserSetting(setting: string, value: string): Promise { - await this.spectron.runCommand('workbench.action.openGlobalSettings'); - await this.spectron.client.waitAndClick(SEARCH_INPUT); - await this.spectron.client.waitForActiveElement(SEARCH_INPUT); + await this.commands.runCommand('workbench.action.openGlobalSettings'); + await this.code.waitAndClick(SEARCH_INPUT); + await this.code.waitForActiveElement(SEARCH_INPUT); - await this.spectron.client.keys(['ArrowDown', 'NULL']); - await this.spectron.client.waitForActiveElement(EDITOR); + await this.code.dispatchKeybinding('down'); + await this.code.waitForActiveElement(EDITOR); - await this.spectron.client.keys(['ArrowRight', 'NULL']); - await this.spectron.screenCapturer.capture('user settings is open and focused'); - - await this.spectron.workbench.editor.waitForTypeInEditor('settings.json', `"${setting}": ${value}`, '.editable-preferences-editor-container'); - await this.spectron.workbench.saveOpenedFile(); - - await this.spectron.screenCapturer.capture('user settings has changed'); + await this.code.dispatchKeybinding('right'); + await this.editor.waitForTypeInEditor('settings.json', `"${setting}": ${value}`, '.editable-preferences-editor-container'); + await this.editors.saveOpenedFile(); } async clearUserSettings(): Promise { - const settingsPath = path.join(this.spectron.userDataPath, 'User', 'settings.json'); + const settingsPath = path.join(this.userDataPath, 'User', 'settings.json'); await new Promise((c, e) => fs.writeFile(settingsPath, '{}', 'utf8', err => err ? e(err) : c())); - await this.spectron.workbench.editor.waitForEditorContents('settings.json', c => c.length === 0, '.editable-preferences-editor-container'); + await this.commands.runCommand('workbench.action.openGlobalSettings'); + await this.editor.waitForEditorContents('settings.json', c => c === '{}', '.editable-preferences-editor-container'); } } \ No newline at end of file diff --git a/test/smoke/src/areas/problems/problems.ts b/test/smoke/src/areas/problems/problems.ts index 64156eced8e5b..0b82fa6914ecc 100644 --- a/test/smoke/src/areas/problems/problems.ts +++ b/test/smoke/src/areas/problems/problems.ts @@ -3,7 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SpectronApplication } from '../../spectron/application'; +import { Commands } from '../workbench/workbench'; +import { Code } from '../../vscode/code'; export enum ProblemSeverity { WARNING = 0, @@ -14,31 +15,20 @@ export class Problems { static PROBLEMS_VIEW_SELECTOR = '.panel.markers-panel'; - constructor(private spectron: SpectronApplication) { - // noop - } + constructor(private code: Code, private commands: Commands) { } public async showProblemsView(): Promise { - if (!await this.isVisible()) { - await this.spectron.runCommand('workbench.actions.view.problems'); - await this.waitForProblemsView(); - } + await this.commands.runCommand('workbench.actions.view.problems'); + await this.waitForProblemsView(); } public async hideProblemsView(): Promise { - if (await this.isVisible()) { - await this.spectron.runCommand('workbench.actions.view.problems'); - await this.spectron.client.waitForElement(Problems.PROBLEMS_VIEW_SELECTOR, el => !el); - } - } - - public async isVisible(): Promise { - const element = await this.spectron.client.element(Problems.PROBLEMS_VIEW_SELECTOR); - return !!element; + await this.commands.runCommand('workbench.actions.view.problems'); + await this.code.waitForElement(Problems.PROBLEMS_VIEW_SELECTOR, el => !el); } public async waitForProblemsView(): Promise { - await this.spectron.client.waitForElement(Problems.PROBLEMS_VIEW_SELECTOR); + await this.code.waitForElement(Problems.PROBLEMS_VIEW_SELECTOR); } public static getSelectorInProblemsView(problemType: ProblemSeverity): string { @@ -47,7 +37,7 @@ export class Problems { } public static getSelectorInEditor(problemType: ProblemSeverity): string { - let selector = problemType === ProblemSeverity.WARNING ? 'squiggly-c-warning' : 'squiggly-d-error'; + let selector = problemType === ProblemSeverity.WARNING ? 'squiggly-warning' : 'squiggly-error'; return `.view-overlays .cdr.${selector}`; } } diff --git a/test/smoke/src/areas/quickopen/quickopen.ts b/test/smoke/src/areas/quickopen/quickopen.ts index 9d76495c81f37..35cc55464ff0a 100644 --- a/test/smoke/src/areas/quickopen/quickopen.ts +++ b/test/smoke/src/areas/quickopen/quickopen.ts @@ -3,7 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SpectronApplication } from '../../spectron/application'; +import { Editors } from '../editor/editors'; +import { Commands } from '../workbench/workbench'; +import { Code } from '../../vscode/code'; export class QuickOpen { @@ -12,20 +14,21 @@ export class QuickOpen { static QUICK_OPEN_INPUT = `${QuickOpen.QUICK_OPEN} .quick-open-input input`; static QUICK_OPEN_FOCUSED_ELEMENT = `${QuickOpen.QUICK_OPEN} .quick-open-tree .monaco-tree-row.focused .monaco-highlighted-label`; static QUICK_OPEN_ENTRY_SELECTOR = 'div[aria-label="Quick Picker"] .monaco-tree-rows.show-twisties .monaco-tree-row .quick-open-entry'; + static QUICK_OPEN_ENTRY_LABEL_SELECTOR = 'div[aria-label="Quick Picker"] .monaco-tree-rows.show-twisties .monaco-tree-row .quick-open-entry .label-name'; - constructor(readonly spectron: SpectronApplication) { } + constructor(private code: Code, private commands: Commands, private editors: Editors) { } async openQuickOpen(value: string): Promise { - await this.spectron.runCommand('workbench.action.quickOpen'); + await this.commands.runCommand('workbench.action.quickOpen'); await this.waitForQuickOpenOpened(); if (value) { - await this.spectron.client.setValue(QuickOpen.QUICK_OPEN_INPUT, value); + await this.code.waitForSetValue(QuickOpen.QUICK_OPEN_INPUT, value); } } async closeQuickOpen(): Promise { - await this.spectron.runCommand('workbench.action.closeQuickOpen'); + await this.commands.runCommand('workbench.action.closeQuickOpen'); await this.waitForQuickOpenClosed(); } @@ -33,56 +36,62 @@ export class QuickOpen { await this.openQuickOpen(fileName); await this.waitForQuickOpenElements(names => names.some(n => n === fileName)); - await this.spectron.client.keys(['Enter', 'NULL']); - await this.spectron.workbench.waitForActiveTab(fileName); - await this.spectron.workbench.waitForEditorFocus(fileName); - } - - async runCommand(commandText: string): Promise { - await this.openQuickOpen(`> ${commandText}`); - - // wait for best choice to be focused - await this.spectron.client.waitForTextContent(QuickOpen.QUICK_OPEN_FOCUSED_ELEMENT, commandText); - - // wait and click on best choice - await this.spectron.client.waitAndClick(QuickOpen.QUICK_OPEN_FOCUSED_ELEMENT); + await this.code.dispatchKeybinding('enter'); + await this.editors.waitForActiveTab(fileName); + await this.editors.waitForEditorFocus(fileName); } async waitForQuickOpenOpened(): Promise { - await this.spectron.client.waitForActiveElement(QuickOpen.QUICK_OPEN_INPUT); + await this.code.waitForActiveElement(QuickOpen.QUICK_OPEN_INPUT); } private async waitForQuickOpenClosed(): Promise { - await this.spectron.client.waitForElement(QuickOpen.QUICK_OPEN_HIDDEN); + await this.code.waitForElement(QuickOpen.QUICK_OPEN_HIDDEN); } async submit(text: string): Promise { - await this.spectron.client.setValue(QuickOpen.QUICK_OPEN_INPUT, text); - await this.spectron.client.keys(['Enter', 'NULL']); + await this.code.waitForSetValue(QuickOpen.QUICK_OPEN_INPUT, text); + await this.code.dispatchKeybinding('enter'); await this.waitForQuickOpenClosed(); } async selectQuickOpenElement(index: number): Promise { await this.waitForQuickOpenOpened(); for (let from = 0; from < index; from++) { - await this.spectron.client.keys(['ArrowDown', 'NULL']); + await this.code.dispatchKeybinding('down'); } - await this.spectron.client.keys(['Enter', 'NULL']); + await this.code.dispatchKeybinding('enter'); await this.waitForQuickOpenClosed(); } async waitForQuickOpenElements(accept: (names: string[]) => boolean): Promise { - await this.spectron.client.waitFor(() => this.getQuickOpenElements(), accept); + await this.code.waitForElements(QuickOpen.QUICK_OPEN_ENTRY_LABEL_SELECTOR, false, els => accept(els.map(e => e.textContent))); + } + + async runCommand(command: string): Promise { + await this.openQuickOpen(`> ${command}`); + + // wait for best choice to be focused + await this.code.waitForTextContent(QuickOpen.QUICK_OPEN_FOCUSED_ELEMENT, command); + + // wait and click on best choice + await this.code.waitAndClick(QuickOpen.QUICK_OPEN_FOCUSED_ELEMENT); } - private async getQuickOpenElements(): Promise { - const result = await this.spectron.webclient.selectorExecute(QuickOpen.QUICK_OPEN_ENTRY_SELECTOR, - div => (Array.isArray(div) ? div : [div]).map(element => { - const name = element.querySelector('.label-name') as HTMLElement; - return name.textContent; - }) - ); + async openQuickOutline(): Promise { + let retries = 0; - return Array.isArray(result) ? result : []; + while (++retries < 10) { + await this.commands.runCommand('workbench.action.gotoSymbol'); + + const text = await this.code.waitForTextContent('div[aria-label="Quick Picker"] .monaco-tree-rows.show-twisties div.monaco-tree-row .quick-open-entry .monaco-icon-label .label-name .monaco-highlighted-label span'); + + if (text !== 'No symbol information for the file') { + return; + } + + await this.closeQuickOpen(); + await new Promise(c => setTimeout(c, 250)); + } } } diff --git a/test/smoke/src/areas/search/search.test.ts b/test/smoke/src/areas/search/search.test.ts index 4b0a5e837d91a..484e428e7b1f3 100644 --- a/test/smoke/src/areas/search/search.test.ts +++ b/test/smoke/src/areas/search/search.test.ts @@ -3,16 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SpectronApplication } from '../../spectron/application'; +import { Application } from '../../application'; export function setup() { describe('Search', () => { - before(function () { - this.app.suiteName = 'Search'; - }); - it('searches for body & checks for correct result number', async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; await app.workbench.search.openSearchViewlet(); await app.workbench.search.searchFor('body'); @@ -20,7 +16,7 @@ export function setup() { }); it('searches only for *.js files & checks for correct result number', async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; await app.workbench.search.searchFor('body'); await app.workbench.search.showQueryDetails(); await app.workbench.search.setFilesToIncludeText('*.js'); @@ -32,14 +28,14 @@ export function setup() { }); it('dismisses result & checks for correct result number', async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; await app.workbench.search.searchFor('body'); await app.workbench.search.removeFileMatch(1); await app.workbench.search.waitForResultText('10 results in 4 files'); }); it('replaces first search result with a replace term', async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; await app.workbench.search.searchFor('body'); await app.workbench.search.expandReplace(); diff --git a/test/smoke/src/areas/search/search.ts b/test/smoke/src/areas/search/search.ts index 5418cad3c21af..632707e1bf943 100644 --- a/test/smoke/src/areas/search/search.ts +++ b/test/smoke/src/areas/search/search.ts @@ -3,8 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SpectronApplication } from '../../spectron/application'; import { Viewlet } from '../workbench/viewlet'; +import { Commands } from '../workbench/workbench'; +import { Code } from '../../vscode/code'; const VIEWLET = 'div[id="workbench.view.search"] .search-view'; const INPUT = `${VIEWLET} .search-widget .search-container .monaco-inputbox input`; @@ -12,76 +13,67 @@ const INCLUDE_INPUT = `${VIEWLET} .query-details .monaco-inputbox input[aria-lab export class Search extends Viewlet { - constructor(spectron: SpectronApplication) { - super(spectron); + constructor(code: Code, private commands: Commands) { + super(code); } async openSearchViewlet(): Promise { - await this.spectron.runCommand('workbench.view.search'); - await this.spectron.client.waitForActiveElement(INPUT); + await this.commands.runCommand('workbench.view.search'); + await this.code.waitForActiveElement(INPUT); } async searchFor(text: string): Promise { - await this.spectron.client.click(INPUT); - await this.spectron.client.waitForActiveElement(INPUT); - await this.spectron.client.setValue(INPUT, text); + await this.code.waitAndClick(INPUT); + await this.code.waitForActiveElement(INPUT); + await this.code.waitForSetValue(INPUT, text); await this.submitSearch(); } async submitSearch(): Promise { - await this.spectron.client.click(INPUT); - await this.spectron.client.waitForActiveElement(INPUT); + await this.code.waitAndClick(INPUT); + await this.code.waitForActiveElement(INPUT); - await this.spectron.client.keys(['Enter', 'NULL']); - await this.spectron.client.element(`${VIEWLET} .messages[aria-hidden="false"]`); + await this.code.dispatchKeybinding('enter'); + await this.code.waitForElement(`${VIEWLET} .messages[aria-hidden="false"]`); } async setFilesToIncludeText(text: string): Promise { - await this.spectron.client.click(INCLUDE_INPUT); - await this.spectron.client.waitForActiveElement(INCLUDE_INPUT); - await this.spectron.client.setValue(INCLUDE_INPUT, text || ''); + await this.code.waitAndClick(INCLUDE_INPUT); + await this.code.waitForActiveElement(INCLUDE_INPUT); + await this.code.waitForSetValue(INCLUDE_INPUT, text || ''); } async showQueryDetails(): Promise { - if (!await this.areDetailsVisible()) { - await this.spectron.client.waitAndClick(`${VIEWLET} .query-details .more`); - } + await this.code.waitAndClick(`${VIEWLET} .query-details .more`); } async hideQueryDetails(): Promise { - if (await this.areDetailsVisible()) { - await this.spectron.client.waitAndClick(`${VIEWLET} .query-details.more .more`); - } - } - - async areDetailsVisible(): Promise { - const element = await this.spectron.client.element(`${VIEWLET} .query-details.more`); - return !!element; + await this.code.waitAndClick(`${VIEWLET} .query-details.more .more`); } async removeFileMatch(index: number): Promise { - await this.spectron.client.waitAndMoveToObject(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch`); - const file = await this.spectron.client.waitForText(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch a.label-name`); - await this.spectron.client.waitAndClick(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch .action-label.icon.action-remove`); - await this.spectron.client.waitForText(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch a.label-name`, void 0, result => result !== file); + await this.code.waitAndMove(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch`); + const file = await this.code.waitForTextContent(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch a.label-name`); + await this.code.waitAndClick(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch .action-label.icon.action-remove`); + await this.code.waitForTextContent(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch a.label-name`, void 0, result => result !== file); } async expandReplace(): Promise { - await this.spectron.client.waitAndClick(`${VIEWLET} .search-widget .monaco-button.toggle-replace-button.collapse`); + await this.code.waitAndClick(`${VIEWLET} .search-widget .monaco-button.toggle-replace-button.collapse`); } async setReplaceText(text: string): Promise { - await this.spectron.client.waitAndClick(`${VIEWLET} .search-widget .replace-container .monaco-inputbox input[title="Replace"]`); - await this.spectron.client.element(`${VIEWLET} .search-widget .replace-container .monaco-inputbox.synthetic-focus input[title="Replace"]`); - await this.spectron.client.setValue(`${VIEWLET} .search-widget .replace-container .monaco-inputbox.synthetic-focus input[title="Replace"]`, text); + await this.code.waitAndClick(`${VIEWLET} .search-widget .replace-container .monaco-inputbox input[title="Replace"]`); + await this.code.waitForElement(`${VIEWLET} .search-widget .replace-container .monaco-inputbox.synthetic-focus input[title="Replace"]`); + await this.code.waitForSetValue(`${VIEWLET} .search-widget .replace-container .monaco-inputbox.synthetic-focus input[title="Replace"]`, text); } async replaceFileMatch(index: number): Promise { - await this.spectron.client.waitAndMoveToObject(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch`); - await this.spectron.client.click(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch .action-label.icon.action-replace-all`); + await this.code.waitAndMove(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch`); + await this.code.waitAndClick(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch .action-label.icon.action-replace-all`); } async waitForResultText(text: string): Promise { - await this.spectron.client.waitForText(`${VIEWLET} .messages[aria-hidden="false"] .message>p`, text); + await this.code.waitForTextContent(`${VIEWLET} .messages[aria-hidden="false"] .message>p`, text); } } diff --git a/test/smoke/src/areas/statusbar/statusbar.test.ts b/test/smoke/src/areas/statusbar/statusbar.test.ts index 044af05c8d9fd..b5c119c05396c 100644 --- a/test/smoke/src/areas/statusbar/statusbar.test.ts +++ b/test/smoke/src/areas/statusbar/statusbar.test.ts @@ -3,19 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; - -import { SpectronApplication, Quality } from '../../spectron/application'; +import { Application, Quality } from '../../application'; import { StatusBarElement } from './statusbar'; export function setup() { describe('Statusbar', () => { - before(function () { - this.app.suiteName = 'Statusbar'; - }); - it('verifies presence of all default status bar elements', async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.BRANCH_STATUS); if (app.quality !== Quality.Dev) { @@ -33,7 +27,7 @@ export function setup() { }); it(`verifies that 'quick open' opens when clicking on status bar elements`, async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; await app.workbench.statusbar.clickOn(StatusBarElement.BRANCH_STATUS); await app.workbench.quickopen.waitForQuickOpenOpened(); @@ -55,25 +49,25 @@ export function setup() { }); it(`verifies that 'Problems View' appears when clicking on 'Problems' status element`, async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; await app.workbench.statusbar.clickOn(StatusBarElement.PROBLEMS_STATUS); await app.workbench.problems.waitForProblemsView(); }); it(`verifies that 'Tweet us feedback' pop-up appears when clicking on 'Feedback' icon`, async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; if (app.quality === Quality.Dev) { return this.skip(); } await app.workbench.statusbar.clickOn(StatusBarElement.FEEDBACK_ICON); - assert.ok(!!await app.client.waitForElement('.feedback-form')); + await app.code.waitForElement('.feedback-form'); }); it(`checks if 'Go to Line' works if called from the status bar`, async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; await app.workbench.quickopen.openFile('app.js'); await app.workbench.statusbar.clickOn(StatusBarElement.SELECTION_STATUS); @@ -85,7 +79,7 @@ export function setup() { }); it(`verifies if changing EOL is reflected in the status bar`, async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; await app.workbench.quickopen.openFile('app.js'); await app.workbench.statusbar.clickOn(StatusBarElement.EOL_STATUS); diff --git a/test/smoke/src/areas/statusbar/statusbar.ts b/test/smoke/src/areas/statusbar/statusbar.ts index b33121994d624..dd3392a5a9c63 100644 --- a/test/smoke/src/areas/statusbar/statusbar.ts +++ b/test/smoke/src/areas/statusbar/statusbar.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SpectronApplication } from '../../spectron/application'; +import { Code } from '../../vscode/code'; export enum StatusBarElement { BRANCH_STATUS = 0, @@ -23,23 +23,22 @@ export class StatusBar { private readonly leftSelector = '.statusbar-item.left'; private readonly rightSelector = '.statusbar-item.right'; - constructor(private spectron: SpectronApplication) { - } + constructor(private code: Code) { } - public async waitForStatusbarElement(element: StatusBarElement): Promise { - await this.spectron.client.waitForElement(this.getSelector(element)); + async waitForStatusbarElement(element: StatusBarElement): Promise { + await this.code.waitForElement(this.getSelector(element)); } - public async clickOn(element: StatusBarElement): Promise { - await this.spectron.client.waitAndClick(this.getSelector(element)); + async clickOn(element: StatusBarElement): Promise { + await this.code.waitAndClick(this.getSelector(element)); } - public async waitForEOL(eol: string): Promise { - return this.spectron.client.waitForText(this.getSelector(StatusBarElement.EOL_STATUS), eol); + async waitForEOL(eol: string): Promise { + return this.code.waitForTextContent(this.getSelector(StatusBarElement.EOL_STATUS), eol); } - public async getStatusbarTextByTitle(title: string): Promise { - return await this.spectron.client.waitForText(`${this.mainSelector} span[title="smoke test"]`); + async waitForStatusbarText(title: string, text: string): Promise { + await this.code.waitForTextContent(`${this.mainSelector} span[title="${title}"]`, text); } private getSelector(element: StatusBarElement): string { diff --git a/test/smoke/src/areas/terminal/terminal.test.ts b/test/smoke/src/areas/terminal/terminal.test.ts deleted file mode 100644 index abbb77e61d592..0000000000000 --- a/test/smoke/src/areas/terminal/terminal.test.ts +++ /dev/null @@ -1,29 +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 { SpectronApplication } from '../../spectron/application'; - -describe('Terminal', () => { - // let app: SpectronApplication; - // before(() => { app = new SpectronApplication(); return app.start('Terminal'); }); - // after(() => app.stop()); - - // it(`opens terminal, runs 'echo' and verifies the output`, async function () { - // const expected = new Date().getTime().toString(); - // await app.workbench.terminal.showTerminal(); - - // await app.workbench.terminal.runCommand(`echo ${expected}`); - - // await app.workbench.terminal.waitForTerminalText(terminalText => { - // // Last line will not contain the output - // for (let index = terminalText.length - 2; index >= 0; index--) { - // if (!!terminalText[index] && terminalText[index].trim() === expected) { - // return true; - // } - // } - // return false; - // }); - // }); -}); \ No newline at end of file diff --git a/test/smoke/src/areas/terminal/terminal.ts b/test/smoke/src/areas/terminal/terminal.ts deleted file mode 100644 index 951497edccfaa..0000000000000 --- a/test/smoke/src/areas/terminal/terminal.ts +++ /dev/null @@ -1,61 +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 { SpectronApplication } from '../../spectron/application'; - -const PANEL_SELECTOR = 'div[id="workbench.panel.terminal"]'; -const XTERM_SELECTOR = `${PANEL_SELECTOR} .terminal-wrapper`; - -export class Terminal { - - constructor(private spectron: SpectronApplication) { } - - async showTerminal(): Promise { - if (!await this.isVisible()) { - await this.spectron.workbench.quickopen.runCommand('View: Toggle Integrated Terminal'); - await this.spectron.client.waitForElement(XTERM_SELECTOR); - await this.waitForTerminalText(text => text.length > 0, 'Waiting for Terminal to be ready'); - } - } - - async isVisible(): Promise { - const element = await this.spectron.client.element(PANEL_SELECTOR); - return !!element; - } - - async runCommand(commandText: string): Promise { - // TODO@Tyriar fix this. we should not use type but setValue - // await this.spectron.client.type(commandText); - await this.spectron.client.keys(['Enter', 'NULL']); - } - - async waitForTerminalText(fn: (text: string[]) => boolean, timeOutDescription: string = 'Getting Terminal Text'): Promise { - return this.spectron.client.waitFor(async () => { - const terminalText = await this.getTerminalText(); - if (fn(terminalText)) { - return terminalText; - } - return undefined; - }, void 0, timeOutDescription); - } - - getCurrentLineNumber(): Promise { - return this.getTerminalText().then(text => text.length); - } - - private async getTerminalText(): Promise { - return await this.spectron.webclient.selectorExecute(XTERM_SELECTOR, - div => { - const xterm = ((Array.isArray(div) ? div[0] : div)).xterm; - const buffer = xterm.buffer; - const lines: string[] = []; - for (let i = 0; i < buffer.lines.length; i++) { - lines.push(buffer.translateBufferLineToString(i, true)); - } - return lines; - } - ); - } -} \ No newline at end of file diff --git a/test/smoke/src/areas/workbench/data-loss.test.ts b/test/smoke/src/areas/workbench/data-loss.test.ts index 8e6cb42d4ab4c..4dc1e37ab267c 100644 --- a/test/smoke/src/areas/workbench/data-loss.test.ts +++ b/test/smoke/src/areas/workbench/data-loss.test.ts @@ -3,39 +3,30 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SpectronApplication } from '../../spectron/application'; +import { Application } from '../../application'; export function setup() { describe('Dataloss', () => { - before(function () { - this.app.suiteName = 'Dataloss'; - }); - it(`verifies that 'hot exit' works for dirty files`, async function () { - const app = this.app as SpectronApplication; - await app.workbench.newUntitledFile(); + const app = this.app as Application; + await app.workbench.editors.newUntitledFile(); const untitled = 'Untitled-1'; const textToTypeInUntitled = 'Hello, Unitled Code'; await app.workbench.editor.waitForTypeInEditor(untitled, textToTypeInUntitled); - await app.screenCapturer.capture('Untitled file before reload'); const readmeMd = 'readme.md'; const textToType = 'Hello, Code'; await app.workbench.explorer.openFile(readmeMd); await app.workbench.editor.waitForTypeInEditor(readmeMd, textToType); - await app.screenCapturer.capture(`${readmeMd} before reload`); await app.reload(); - await app.screenCapturer.capture('After reload'); - await app.workbench.waitForActiveTab(readmeMd, true); - await app.screenCapturer.capture(`${readmeMd} after reload`); + await app.workbench.editors.waitForActiveTab(readmeMd, true); await app.workbench.editor.waitForEditorContents(readmeMd, c => c.indexOf(textToType) > -1); - await app.workbench.waitForTab(untitled, true); - await app.workbench.selectTab(untitled, true); - await app.screenCapturer.capture('Untitled file after reload'); + await app.workbench.editors.waitForTab(untitled, true); + await app.workbench.editors.selectTab(untitled, true); await app.workbench.editor.waitForEditorContents(untitled, c => c.indexOf(textToTypeInUntitled) > -1); }); }); diff --git a/test/smoke/src/areas/workbench/data-migration.test.ts b/test/smoke/src/areas/workbench/data-migration.test.ts index 7a2877ab4adfc..f4084ca624beb 100644 --- a/test/smoke/src/areas/workbench/data-migration.test.ts +++ b/test/smoke/src/areas/workbench/data-migration.test.ts @@ -3,130 +3,109 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; - -import { SpectronApplication, Quality } from '../../spectron/application'; +import { Application, Quality } from '../../application'; import * as rimraf from 'rimraf'; export interface ICreateAppFn { - (quality: Quality): SpectronApplication | null; + (quality: Quality): Application; } export function setup(userDataDir: string, createApp: ICreateAppFn) { describe('Data Migration', () => { + afterEach(async function () { await new Promise((c, e) => rimraf(userDataDir, { maxBusyTries: 10 }, err => err ? e(err) : c())); }); - it('checks if the Untitled file is restored migrating from stable to latest', async function () { - const stableApp = createApp(Quality.Stable); + // it('checks if the Untitled file is restored migrating from stable to latest', async function () { + // const stableApp = createApp(Quality.Stable); - if (!stableApp) { - this.skip(); - return; - } + // if (!stableApp) { + // this.skip(); + // return; + // } - await stableApp.start(); - stableApp.suiteName = 'Data Migration'; + // await stableApp.start(); - const textToType = 'Very dirty file'; + // const textToType = 'Very dirty file'; - await stableApp.workbench.newUntitledFile(); - await stableApp.workbench.editor.waitForTypeInEditor('Untitled-1', textToType); + // await stableApp.workbench.editors.newUntitledFile(); + // await stableApp.workbench.editor.waitForTypeInEditor('Untitled-1', textToType); - await stableApp.stop(); - await new Promise(c => setTimeout(c, 500)); // wait until all resources are released (e.g. locked local storage) + // await stableApp.stop(); + // await new Promise(c => setTimeout(c, 500)); // wait until all resources are released (e.g. locked local storage) - // Checking latest version for the restored state - const app = createApp(Quality.Insiders); + // // Checking latest version for the restored state + // const app = createApp(Quality.Insiders); - if (!app) { - return assert(false); - } + // await app.start(false); - await app.start(false); - app.suiteName = 'Data Migration'; + // await app.workbench.editors.waitForActiveTab('Untitled-1', true); + // await app.workbench.editor.waitForEditorContents('Untitled-1', c => c.indexOf(textToType) > -1); - assert.ok(await app.workbench.waitForActiveTab('Untitled-1', true), `Untitled-1 tab is not present after migration.`); + // await app.stop(); + // }); - await app.workbench.editor.waitForEditorContents('Untitled-1', c => c.indexOf(textToType) > -1); - await app.screenCapturer.capture('Untitled file text'); + // it('checks if the newly created dirty file is restored migrating from stable to latest', async function () { + // const stableApp = createApp(Quality.Stable); - await app.stop(); - }); + // if (!stableApp) { + // this.skip(); + // return; + // } - it('checks if the newly created dirty file is restored migrating from stable to latest', async function () { - const stableApp = createApp(Quality.Stable); + // await stableApp.start(); - if (!stableApp) { - this.skip(); - return; - } + // const fileName = 'app.js'; + // const textPart = 'This is going to be an unsaved file'; - await stableApp.start(); - stableApp.suiteName = 'Data Migration'; + // await stableApp.workbench.quickopen.openFile(fileName); - const fileName = 'app.js'; - const textPart = 'This is going to be an unsaved file'; + // await stableApp.workbench.editor.waitForTypeInEditor(fileName, textPart); - await stableApp.workbench.quickopen.openFile(fileName); + // await stableApp.stop(); + // await new Promise(c => setTimeout(c, 500)); // wait until all resources are released (e.g. locked local storage) - await stableApp.workbench.editor.waitForTypeInEditor(fileName, textPart); + // // Checking latest version for the restored state + // const app = createApp(Quality.Insiders); - await stableApp.stop(); - await new Promise(c => setTimeout(c, 500)); // wait until all resources are released (e.g. locked local storage) + // await app.start(false); - // Checking latest version for the restored state - const app = createApp(Quality.Insiders); + // await app.workbench.editors.waitForActiveTab(fileName); + // await app.workbench.editor.waitForEditorContents(fileName, c => c.indexOf(textPart) > -1); - if (!app) { - return assert(false); - } + // await app.stop(); + // }); - await app.start(false); - app.suiteName = 'Data Migration'; + // it('checks if opened tabs are restored migrating from stable to latest', async function () { + // const stableApp = createApp(Quality.Stable); - assert.ok(await app.workbench.waitForActiveTab(fileName), `dirty file tab is not present after migration.`); - await app.workbench.editor.waitForEditorContents(fileName, c => c.indexOf(textPart) > -1); - - await app.stop(); - }); + // if (!stableApp) { + // this.skip(); + // return; + // } - it('checks if opened tabs are restored migrating from stable to latest', async function () { - const stableApp = createApp(Quality.Stable); + // await stableApp.start(); - if (!stableApp) { - this.skip(); - return; - } + // const fileName1 = 'app.js', fileName2 = 'jsconfig.json', fileName3 = 'readme.md'; - await stableApp.start(); - stableApp.suiteName = 'Data Migration'; + // await stableApp.workbench.quickopen.openFile(fileName1); + // await stableApp.workbench.runCommand('View: Keep Editor'); + // await stableApp.workbench.quickopen.openFile(fileName2); + // await stableApp.workbench.runCommand('View: Keep Editor'); + // await stableApp.workbench.quickopen.openFile(fileName3); + // await stableApp.stop(); - const fileName1 = 'app.js', fileName2 = 'jsconfig.json', fileName3 = 'readme.md'; + // const app = createApp(Quality.Insiders); - await stableApp.workbench.quickopen.openFile(fileName1); - await stableApp.workbench.quickopen.runCommand('View: Keep Editor'); - await stableApp.workbench.quickopen.openFile(fileName2); - await stableApp.workbench.quickopen.runCommand('View: Keep Editor'); - await stableApp.workbench.quickopen.openFile(fileName3); - await stableApp.stop(); + // await app.start(false); - const app = createApp(Quality.Insiders); + // await app.workbench.editors.waitForTab(fileName1); + // await app.workbench.editors.waitForTab(fileName2); + // await app.workbench.editors.waitForTab(fileName3); - if (!app) { - return assert(false); - } - - await app.start(false); - app.suiteName = 'Data Migration'; - - assert.ok(await app.workbench.waitForTab(fileName1), `${fileName1} tab was not restored after migration.`); - assert.ok(await app.workbench.waitForTab(fileName2), `${fileName2} tab was not restored after migration.`); - assert.ok(await app.workbench.waitForTab(fileName3), `${fileName3} tab was not restored after migration.`); - - await app.stop(); - }); + // await app.stop(); + // }); }); } \ No newline at end of file diff --git a/test/smoke/src/areas/workbench/localization.test.ts b/test/smoke/src/areas/workbench/localization.test.ts index 0251a9bf96481..ea7a25bf8cfba 100644 --- a/test/smoke/src/areas/workbench/localization.test.ts +++ b/test/smoke/src/areas/workbench/localization.test.ts @@ -3,15 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; - -import { SpectronApplication, Quality } from '../../spectron/application'; +import { Application, Quality } from '../../application'; export function setup() { describe('Localization', () => { before(async function () { - const app = this.app as SpectronApplication; - this.app.suiteName = 'Localization'; + const app = this.app as Application; if (app.quality === Quality.Dev) { return; @@ -21,36 +18,26 @@ export function setup() { }); it(`starts with 'DE' locale and verifies title and viewlets text is in German`, async function () { - const app = this.app as SpectronApplication; + const app = this.app as Application; if (app.quality === Quality.Dev) { this.skip(); return; } - let text = await app.workbench.explorer.getOpenEditorsViewTitle(); - await app.screenCapturer.capture('Open editors title'); - assert(/geöffnete editoren/i.test(text)); + await app.workbench.explorer.waitForOpenEditorsViewTitle(title => /geöffnete editoren/i.test(title)); await app.workbench.search.openSearchViewlet(); - text = await app.workbench.search.getTitle(); - await app.screenCapturer.capture('Search title'); - assert(/suchen/i.test(text)); + await app.workbench.search.waitForTitle(title => /suchen/i.test(title)); await app.workbench.scm.openSCMViewlet(); - text = await app.workbench.scm.getTitle(); - await app.screenCapturer.capture('Scm title'); - assert(/quellcodeverwaltung/i.test(text)); + await app.workbench.scm.waitForTitle(title => /quellcodeverwaltung/i.test(title)); await app.workbench.debug.openDebugViewlet(); - text = await app.workbench.debug.getTitle(); - await app.screenCapturer.capture('Debug title'); - assert(/debuggen/i.test(text)); + await app.workbench.debug.waitForTitle(title => /debuggen/i.test(title)); await app.workbench.extensions.openExtensionsViewlet(); - text = await app.workbench.extensions.getTitle(); - await app.screenCapturer.capture('Extensions title'); - assert(/erweiterungen/i.test(text)); + await app.workbench.extensions.waitForTitle(title => /erweiterungen/i.test(title)); }); }); } \ No newline at end of file diff --git a/test/smoke/src/areas/workbench/viewlet.ts b/test/smoke/src/areas/workbench/viewlet.ts index 7eb114edaaf70..2293752b705c6 100644 --- a/test/smoke/src/areas/workbench/viewlet.ts +++ b/test/smoke/src/areas/workbench/viewlet.ts @@ -3,16 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SpectronApplication } from '../../spectron/application'; +'use strict'; + +import { Code } from '../../vscode/code'; export abstract class Viewlet { - constructor(protected spectron: SpectronApplication) { - // noop - } + constructor(protected code: Code) { } - public async getTitle(): Promise { - return this.spectron.client.waitForText('.monaco-workbench-container .part.sidebar > .title > .title-label > span'); + async waitForTitle(fn: (title: string) => boolean): Promise { + await this.code.waitForTextContent('.monaco-workbench-container .part.sidebar > .title > .title-label > span', undefined, fn); } - } \ No newline at end of file diff --git a/test/smoke/src/areas/workbench/workbench.ts b/test/smoke/src/areas/workbench/workbench.ts index c2711f449d347..d81733bb58bde 100644 --- a/test/smoke/src/areas/workbench/workbench.ts +++ b/test/smoke/src/areas/workbench/workbench.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SpectronApplication } from '../../spectron/application'; import { Explorer } from '../explorer/explorer'; import { ActivityBar } from '../activitybar/activityBar'; import { QuickOpen } from '../quickopen/quickopen'; @@ -16,13 +15,19 @@ import { StatusBar } from '../statusbar/statusbar'; import { Problems } from '../problems/problems'; import { SettingsEditor } from '../preferences/settings'; import { KeybindingsEditor } from '../preferences/keybindings'; -import { Terminal } from '../terminal/terminal'; +import { Editors } from '../editor/editors'; +import { Code } from '../../vscode/code'; -export class Workbench { +export interface Commands { + runCommand(command: string): Promise; +} + +export class Workbench implements Commands { + readonly quickopen: QuickOpen; + readonly editors: Editors; readonly explorer: Explorer; readonly activitybar: ActivityBar; - readonly quickopen: QuickOpen; readonly search: Search; readonly extensions: Extensions; readonly editor: Editor; @@ -32,49 +37,35 @@ export class Workbench { readonly problems: Problems; readonly settingsEditor: SettingsEditor; readonly keybindingsEditor: KeybindingsEditor; - readonly terminal: Terminal; - - constructor(private spectron: SpectronApplication) { - this.explorer = new Explorer(spectron); - this.activitybar = new ActivityBar(spectron); - this.quickopen = new QuickOpen(spectron); - this.search = new Search(spectron); - this.extensions = new Extensions(spectron); - this.editor = new Editor(spectron); - this.scm = new SCM(spectron); - this.debug = new Debug(spectron); - this.statusbar = new StatusBar(spectron); - this.problems = new Problems(spectron); - this.settingsEditor = new SettingsEditor(spectron); - this.keybindingsEditor = new KeybindingsEditor(spectron); - this.terminal = new Terminal(spectron); - } - - public async saveOpenedFile(): Promise { - await this.spectron.client.waitForElement('.tabs-container div.tab.active.dirty'); - await this.spectron.workbench.quickopen.runCommand('File: Save'); - } - - public async selectTab(tabName: string, untitled: boolean = false): Promise { - await this.spectron.client.waitAndClick(`.tabs-container div.tab[aria-label="${tabName}, tab"]`); - await this.waitForEditorFocus(tabName, untitled); - } - public async waitForEditorFocus(fileName: string, untitled: boolean = false): Promise { - await this.waitForActiveTab(fileName); - await this.editor.waitForActiveEditor(fileName); + constructor(private code: Code, private keybindings: any[], userDataPath: string) { + this.editors = new Editors(code, this); + this.quickopen = new QuickOpen(code, this, this.editors); + this.explorer = new Explorer(code, this.quickopen, this.editors); + this.activitybar = new ActivityBar(code); + this.search = new Search(code, this); + this.extensions = new Extensions(code, this); + this.editor = new Editor(code, this); + this.scm = new SCM(code, this); + this.debug = new Debug(code, this, this.editors, this.editor); + this.statusbar = new StatusBar(code); + this.problems = new Problems(code, this); + this.settingsEditor = new SettingsEditor(code, userDataPath, this, this.editors, this.editor); + this.keybindingsEditor = new KeybindingsEditor(code, this); } - public async waitForActiveTab(fileName: string, isDirty: boolean = false): Promise { - return this.spectron.client.waitForElement(`.tabs-container div.tab.active${isDirty ? '.dirty' : ''}[aria-selected="true"][aria-label="${fileName}, tab"]`); - } + /** + * Retrieves the command from keybindings file and executes it with WebdriverIO client API + * @param command command (e.g. 'workbench.action.files.newUntitledFile') + */ + async runCommand(command: string): Promise { + const binding = this.keybindings.find(x => x['command'] === command); + if (!binding) { + await this.quickopen.runCommand(command); + return; + } - public async waitForTab(fileName: string, isDirty: boolean = false): Promise { - return this.spectron.client.waitForElement(`.tabs-container div.tab${isDirty ? '.dirty' : ''}[aria-label="${fileName}, tab"]`).then(() => true); - } - - public async newUntitledFile(): Promise { - await this.spectron.runCommand('workbench.action.files.newUntitledFile'); - await this.waitForEditorFocus('Untitled-1', true); + return this.code.dispatchKeybinding(binding.key); } } + diff --git a/test/smoke/src/helpers/screenshot.ts b/test/smoke/src/helpers/screenshot.ts deleted file mode 100644 index 18a068ec388b3..0000000000000 --- a/test/smoke/src/helpers/screenshot.ts +++ /dev/null @@ -1,37 +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 * as path from 'path'; -import * as fs from 'fs'; -import * as mkdirp from 'mkdirp'; -import { Application } from 'spectron'; -import { sanitize } from './utilities'; - -export class ScreenCapturer { - - private static counter = 0; - - constructor( - private application: Application, - public suiteName: string, - private screenshotsDirPath: string | undefined, - ) { } - - async capture(name: string): Promise { - if (!this.screenshotsDirPath) { - return; - } - - const screenshotPath = path.join( - this.screenshotsDirPath, - sanitize(this.suiteName), - `${ScreenCapturer.counter++}-${sanitize(name)}.png` - ); - - const image = await this.application.browserWindow.capturePage(); - await new Promise((c, e) => mkdirp(path.dirname(screenshotPath), err => err ? e(err) : c())); - await new Promise((c, e) => fs.writeFile(screenshotPath, image, err => err ? e(err) : c())); - } -} diff --git a/test/smoke/src/helpers/utilities.ts b/test/smoke/src/helpers/utilities.ts deleted file mode 100644 index 8b86d31c95765..0000000000000 --- a/test/smoke/src/helpers/utilities.ts +++ /dev/null @@ -1,53 +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 * as fs from 'fs'; -import { dirname } from 'path'; - -export function nfcall(fn: Function, ...args): Promise { - return new Promise((c, e) => fn(...args, (err, r) => err ? e(err) : c(r))); -} - -export async function mkdirp(path: string, mode?: number): Promise { - const mkdir = async () => { - try { - await nfcall(fs.mkdir, path, mode); - } catch (err) { - if (err.code === 'EEXIST') { - const stat = await nfcall(fs.stat, path); - - if (stat.isDirectory) { - return; - } - - throw new Error(`'${path}' exists and is not a directory.`); - } - - throw err; - } - }; - - // is root? - if (path === dirname(path)) { - return true; - } - - try { - await mkdir(); - } catch (err) { - if (err.code !== 'ENOENT') { - throw err; - } - - await mkdirp(dirname(path), mode); - await mkdir(); - } - - return true; -} - -export function sanitize(name: string): string { - return name.replace(/[&*:\/]/g, ''); -} \ No newline at end of file diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index 8adeab24a789b..63149f3e52286 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -11,9 +11,9 @@ import * as minimist from 'minimist'; import * as tmp from 'tmp'; import * as rimraf from 'rimraf'; import * as mkdirp from 'mkdirp'; -import { SpectronApplication, Quality } from './spectron/application'; -import { setup as setupDataMigrationTests } from './areas/workbench/data-migration.test'; +import { Application, Quality } from './application'; +import { setup as setupDataMigrationTests } from './areas/workbench/data-migration.test'; import { setup as setupDataLossTests } from './areas/workbench/data-loss.test'; import { setup as setupDataExplorerTests } from './areas/explorer/explorer.test'; import { setup as setupDataPreferencesTests } from './areas/preferences/preferences.test'; @@ -26,7 +26,6 @@ import { setup as setupDataStatusbarTests } from './areas/statusbar/statusbar.te import { setup as setupDataExtensionTests } from './areas/extensions/extensions.test'; import { setup as setupDataMultirootTests } from './areas/multiroot/multiroot.test'; import { setup as setupDataLocalizationTests } from './areas/workbench/localization.test'; -// import './areas/terminal/terminal.test'; const tmpDir = tmp.dirSync({ prefix: 't' }) as { name: string; removeCallback: Function; }; const testDataPath = tmpDir.name; @@ -37,13 +36,18 @@ const opts = minimist(args, { string: [ 'build', 'stable-build', - 'log', - 'wait-time' - ] + 'wait-time', + 'test-repo', + 'keybindings' + ], + boolean: [ + 'verbose' + ], + default: { + verbose: false + } }); -const artifactsPath = opts.log || ''; - const workspaceFilePath = path.join(testDataPath, 'smoketest.code-workspace'); const testRepoUrl = 'https://github.com/Microsoft/vscode-smoketest-express'; const workspacePath = path.join(testDataPath, 'vscode-smoketest-express'); @@ -96,16 +100,16 @@ function getBuildElectronPath(root: string): string { } let testCodePath = opts.build; -let stableCodePath = opts['stable-build']; +// let stableCodePath = opts['stable-build']; let electronPath: string; -let stablePath: string; +// let stablePath: string; if (testCodePath) { electronPath = getBuildElectronPath(testCodePath); - if (stableCodePath) { - stablePath = getBuildElectronPath(stableCodePath); - } + // if (stableCodePath) { + // stablePath = getBuildElectronPath(stableCodePath); + // } } else { testCodePath = getDevElectronPath(); electronPath = testCodePath; @@ -147,96 +151,96 @@ function toUri(path: string): string { return `${path}`; } -async function setup(): Promise { - console.log('*** Test data:', testDataPath); - console.log('*** Preparing smoketest setup...'); - - const keybindingsUrl = `https://raw.githubusercontent.com/Microsoft/vscode-docs/master/build/keybindings/doc.keybindings.${getKeybindingPlatform()}.json`; - console.log('*** Fetching keybindings...'); - - await new Promise((c, e) => { - https.get(keybindingsUrl, res => { - const output = fs.createWriteStream(keybindingsPath); - res.on('error', e); - output.on('error', e); - output.on('close', c); - res.pipe(output); - }).on('error', e); - }); +async function getKeybindings(): Promise { + if (opts.keybindings) { + console.log('*** Using keybindings: ', opts.keybindings); + const rawKeybindings = fs.readFileSync(opts.keybindings); + fs.writeFileSync(keybindingsPath, rawKeybindings); + } else { + const keybindingsUrl = `https://raw.githubusercontent.com/Microsoft/vscode-docs/master/build/keybindings/doc.keybindings.${getKeybindingPlatform()}.json`; + console.log('*** Fetching keybindings...'); + + await new Promise((c, e) => { + https.get(keybindingsUrl, res => { + const output = fs.createWriteStream(keybindingsPath); + res.on('error', e); + output.on('error', e); + output.on('close', c); + res.pipe(output); + }).on('error', e); + }); + } +} - if (!fs.existsSync(workspaceFilePath)) { - console.log('*** Creating workspace file...'); - const workspace = { - folders: [ - { - path: toUri(path.join(workspacePath, 'public')) - }, - { - path: toUri(path.join(workspacePath, 'routes')) - }, - { - path: toUri(path.join(workspacePath, 'views')) - } - ] - }; - - fs.writeFileSync(workspaceFilePath, JSON.stringify(workspace, null, '\t')); +async function createWorkspaceFile(): Promise { + if (fs.existsSync(workspaceFilePath)) { + return; } - if (!fs.existsSync(workspacePath)) { - console.log('*** Cloning test project repository...'); - cp.spawnSync('git', ['clone', testRepoUrl, workspacePath]); + console.log('*** Creating workspace file...'); + const workspace = { + folders: [ + { + path: toUri(path.join(workspacePath, 'public')) + }, + { + path: toUri(path.join(workspacePath, 'routes')) + }, + { + path: toUri(path.join(workspacePath, 'views')) + } + ] + }; + + fs.writeFileSync(workspaceFilePath, JSON.stringify(workspace, null, '\t')); +} + +async function setupRepository(): Promise { + if (opts['test-repo']) { + console.log('*** Copying test project repository:', opts['test-repo']); + rimraf.sync(workspacePath); + // not platform friendly + cp.execSync(`cp -R "${opts['test-repo']}" "${workspacePath}"`); } else { - console.log('*** Cleaning test project repository...'); - cp.spawnSync('git', ['fetch'], { cwd: workspacePath }); - cp.spawnSync('git', ['reset', '--hard', 'FETCH_HEAD'], { cwd: workspacePath }); - cp.spawnSync('git', ['clean', '-xdf'], { cwd: workspacePath }); + if (!fs.existsSync(workspacePath)) { + console.log('*** Cloning test project repository...'); + cp.spawnSync('git', ['clone', testRepoUrl, workspacePath]); + } else { + console.log('*** Cleaning test project repository...'); + cp.spawnSync('git', ['fetch'], { cwd: workspacePath }); + cp.spawnSync('git', ['reset', '--hard', 'FETCH_HEAD'], { cwd: workspacePath }); + cp.spawnSync('git', ['clean', '-xdf'], { cwd: workspacePath }); + } + + console.log('*** Running npm install...'); + cp.execSync('npm install', { cwd: workspacePath, stdio: 'inherit' }); } +} + +async function setup(): Promise { + console.log('*** Test data:', testDataPath); + console.log('*** Preparing smoketest setup...'); - console.log('*** Running npm install...'); - cp.execSync('npm install', { cwd: workspacePath, stdio: 'inherit' }); + await getKeybindings(); + await createWorkspaceFile(); + await setupRepository(); console.log('*** Smoketest setup done!\n'); } -/** - * WebDriverIO 4.8.0 outputs all kinds of "deprecation" warnings - * for common commands like `keys` and `moveToObject`. - * According to https://github.com/Codeception/CodeceptJS/issues/531, - * these deprecation warnings are for Firefox, and have no alternative replacements. - * Since we can't downgrade WDIO as suggested (it's Spectron's dep, not ours), - * we must suppress the warning with a classic monkey-patch. - * - * @see webdriverio/lib/helpers/depcrecationWarning.js - * @see https://github.com/webdriverio/webdriverio/issues/2076 - */ -// Filter out the following messages: -const wdioDeprecationWarning = /^WARNING: the "\w+" command will be deprecated soon../; // [sic] -// Monkey patch: -const warn = console.warn; -console.warn = function suppressWebdriverWarnings(message) { - if (wdioDeprecationWarning.test(message)) { return; } - warn.apply(console, arguments); -}; - -function createApp(quality: Quality): SpectronApplication | null { - const path = quality === Quality.Stable ? stablePath : electronPath; - - if (!path) { - return null; - } - - return new SpectronApplication({ +function createApp(quality: Quality): Application { + return new Application({ quality, - electronPath: path, + codePath: opts.build, workspacePath, userDataDir, extensionsPath, - artifactsPath, workspaceFilePath, - waitTime: parseInt(opts['wait-time'] || '0') || 20 + waitTime: parseInt(opts['wait-time'] || '0') || 20, + verbose: opts.verbose }); } + before(async function () { // allow two minutes for setup this.timeout(2 * 60 * 1000); diff --git a/test/smoke/src/spectron/application.ts b/test/smoke/src/spectron/application.ts deleted file mode 100644 index 91d742cd1593f..0000000000000 --- a/test/smoke/src/spectron/application.ts +++ /dev/null @@ -1,351 +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 { Application, SpectronClient as WebClient } from 'spectron'; -import { test as testPort } from 'portastic'; -import { SpectronClient } from './client'; -import { ScreenCapturer } from '../helpers/screenshot'; -import { Workbench } from '../areas/workbench/workbench'; -import * as fs from 'fs'; -import * as cp from 'child_process'; -import * as path from 'path'; -import * as mkdirp from 'mkdirp'; -import { sanitize } from '../helpers/utilities'; - -// Just hope random helps us here, cross your fingers! -export async function findFreePort(): Promise { - for (let i = 0; i < 10; i++) { - const port = 10000 + Math.round(Math.random() * 10000); - - if (await testPort(port)) { - return port; - } - } - - throw new Error('Could not find free port!'); -} - -export enum Quality { - Dev, - Insiders, - Stable -} - -export interface SpectronApplicationOptions { - quality: Quality; - electronPath: string; - workspacePath: string; - userDataDir: string; - extensionsPath: string; - artifactsPath: string; - workspaceFilePath: string; - waitTime: number; -} - -/** - * Wraps Spectron's Application instance with its used methods. - */ -export class SpectronApplication { - - private static count = 0; - - private _client: SpectronClient; - private _workbench: Workbench; - private _screenCapturer: ScreenCapturer; - private spectron: Application | undefined; - private keybindings: any[]; private stopLogCollection: (() => Promise) | undefined; - - constructor( - private options: SpectronApplicationOptions - ) { } - - get quality(): Quality { - return this.options.quality; - } - - get client(): SpectronClient { - return this._client; - } - - get webclient(): WebClient { - if (!this.spectron) { - throw new Error('Application not started'); - } - - return this.spectron.client; - } - - get screenCapturer(): ScreenCapturer { - return this._screenCapturer; - } - - get workbench(): Workbench { - return this._workbench; - } - - get workspacePath(): string { - return this.options.workspacePath; - } - - get extensionsPath(): string { - return this.options.extensionsPath; - } - - get userDataPath(): string { - return this.options.userDataDir; - } - - get workspaceFilePath(): string { - return this.options.workspaceFilePath; - } - - private _suiteName: string = 'Init'; - - set suiteName(suiteName: string) { - this._suiteName = suiteName; - this._screenCapturer.suiteName = suiteName; - } - - async start(waitForWelcome: boolean = true): Promise { - await this._start(); - - if (waitForWelcome) { - await this.waitForWelcome(); - } - } - - async restart(options: { workspaceOrFolder?: string, extraArgs?: string[] }): Promise { - await this.stop(); - await new Promise(c => setTimeout(c, 1000)); - await this._start(options.workspaceOrFolder, options.extraArgs); - } - - private async _start(workspaceOrFolder = this.options.workspacePath, extraArgs: string[] = []): Promise { - await this.retrieveKeybindings(); - cp.execSync('git checkout .', { cwd: this.options.workspacePath }); - await this.startApplication(workspaceOrFolder, extraArgs); - await this.checkWindowReady(); - } - - async reload(): Promise { - await this.workbench.quickopen.runCommand('Reload Window'); - // TODO @sandy: Find a proper condition to wait for reload - await new Promise(c => setTimeout(c, 1500)); - await this.checkWindowReady(); - } - - async stop(): Promise { - if (this.stopLogCollection) { - await this.stopLogCollection(); - this.stopLogCollection = undefined; - } - - if (this.spectron && this.spectron.isRunning()) { - await this.spectron.stop(); - this.spectron = undefined; - } - } - - private async startApplication(workspaceOrFolder: string, extraArgs: string[] = []): Promise { - - let args: string[] = []; - let chromeDriverArgs: string[] = []; - - if (process.env.VSCODE_REPOSITORY) { - args.push(process.env.VSCODE_REPOSITORY as string); - } - - args.push(workspaceOrFolder); - - // Prevent 'Getting Started' web page from opening on clean user-data-dir - args.push('--skip-getting-started'); - - // Prevent 'Getting Started' web page from opening on clean user-data-dir - args.push('--skip-release-notes'); - - // Prevent Quick Open from closing when focus is stolen, this allows concurrent smoketest suite running - args.push('--sticky-quickopen'); - - // Disable telemetry - args.push('--disable-telemetry'); - - // Disable updates - args.push('--disable-updates'); - - // Disable crash reporter - // This seems to be the fix for the strange hangups in which Code stays unresponsive - // and tests finish badly with timeouts, leaving Code running in the background forever - args.push('--disable-crash-reporter'); - - // Ensure that running over custom extensions directory, rather than picking up the one that was used by a tester previously - args.push(`--extensions-dir=${this.options.extensionsPath}`); - - args.push(...extraArgs); - - chromeDriverArgs.push(`--user-data-dir=${this.options.userDataDir}`); - - // Spectron always uses the same port number for the chrome driver - // and it handles gracefully when two instances use the same port number - // This works, but when one of the instances quits, it takes down - // chrome driver with it, leaving the other instance in DISPAIR!!! :( - const port = await findFreePort(); - - // We must get a different port for debugging the smoketest express app - // otherwise concurrent test runs will clash on those ports - const env = { PORT: String(await findFreePort()), ...process.env }; - - const opts: any = { - path: this.options.electronPath, - port, - args, - env, - chromeDriverArgs, - startTimeout: 10000, - requireName: 'nodeRequire' - }; - - const runName = String(SpectronApplication.count++); - let testsuiteRootPath: string | undefined = undefined; - let screenshotsDirPath: string | undefined = undefined; - - if (this.options.artifactsPath) { - testsuiteRootPath = path.join(this.options.artifactsPath, sanitize(runName)); - mkdirp.sync(testsuiteRootPath); - - // Collect screenshots - screenshotsDirPath = path.join(testsuiteRootPath, 'screenshots'); - mkdirp.sync(screenshotsDirPath); - - // Collect chromedriver logs - const chromedriverLogPath = path.join(testsuiteRootPath, 'chromedriver.log'); - opts.chromeDriverLogPath = chromedriverLogPath; - - // Collect webdriver logs - const webdriverLogsPath = path.join(testsuiteRootPath, 'webdriver'); - mkdirp.sync(webdriverLogsPath); - opts.webdriverLogPath = webdriverLogsPath; - } - - this.spectron = new Application(opts); - await this.spectron.start(); - - if (testsuiteRootPath) { - // Collect logs - const mainProcessLogPath = path.join(testsuiteRootPath, 'main.log'); - const rendererProcessLogPath = path.join(testsuiteRootPath, 'renderer.log'); - - const flush = async () => { - if (!this.spectron) { - return; - } - - const mainLogs = await this.spectron.client.getMainProcessLogs(); - await new Promise((c, e) => fs.appendFile(mainProcessLogPath, mainLogs.join('\n'), { encoding: 'utf8' }, err => err ? e(err) : c())); - - const rendererLogs = (await this.spectron.client.getRenderProcessLogs()).map(m => `${m.timestamp} - ${m.level} - ${m.message}`); - await new Promise((c, e) => fs.appendFile(rendererProcessLogPath, rendererLogs.join('\n'), { encoding: 'utf8' }, err => err ? e(err) : c())); - }; - - let running = true; - const loopFlush = async () => { - while (true) { - await flush(); - - if (!running) { - return; - } - - await new Promise(c => setTimeout(c, 1000)); - } - }; - - const loopPromise = loopFlush(); - this.stopLogCollection = () => { - running = false; - return loopPromise; - }; - } - - this._screenCapturer = new ScreenCapturer(this.spectron, this._suiteName, screenshotsDirPath); - this._client = new SpectronClient(this.spectron, this, this.options.waitTime); - this._workbench = new Workbench(this); - } - - private async checkWindowReady(): Promise { - await this.webclient.waitUntilWindowLoaded(); - - // Pick the first workbench window here - const count = await this.webclient.getWindowCount(); - - for (let i = 0; i < count; i++) { - await this.webclient.windowByIndex(i); - - if (/bootstrap\/index\.html/.test(await this.webclient.getUrl())) { - break; - } - } - - await this.client.waitForElement('.monaco-workbench'); - } - - private async waitForWelcome(): Promise { - await this.client.waitForElement('.explorer-folders-view'); - await this.client.waitForElement(`.editor-container[id="workbench.editor.walkThroughPart"] .welcomePage`); - } - - private retrieveKeybindings(): Promise { - return new Promise((c, e) => { - fs.readFile(process.env.VSCODE_KEYBINDINGS_PATH as string, 'utf8', (err, data) => { - if (err) { - throw err; - } - try { - this.keybindings = JSON.parse(data); - c(); - } catch (e) { - throw new Error(`Error parsing keybindings JSON: ${e}`); - } - }); - }); - } - - /** - * Retrieves the command from keybindings file and executes it with WebdriverIO client API - * @param command command (e.g. 'workbench.action.files.newUntitledFile') - */ - runCommand(command: string): Promise { - const binding = this.keybindings.find(x => x['command'] === command); - if (!binding) { - return this.workbench.quickopen.runCommand(command); - } - - const keys: string = binding.key; - let keysToPress: string[] = []; - - const chords = keys.split(' '); - chords.forEach((chord) => { - const keys = chord.split('+'); - keys.forEach((key) => keysToPress.push(this.transliterate(key))); - keysToPress.push('NULL'); - }); - - return this.client.keys(keysToPress); - } - - /** - * Transliterates key names from keybindings file to WebdriverIO keyboard actions defined in: - * https://w3c.github.io/webdriver/webdriver-spec.html#keyboard-actions - */ - private transliterate(key: string): string { - switch (key) { - case 'ctrl': - return 'Control'; - case 'cmd': - return 'Meta'; - default: - return key.length === 1 ? key : key.charAt(0).toUpperCase() + key.slice(1); - } - } -} diff --git a/test/smoke/src/spectron/client.ts b/test/smoke/src/spectron/client.ts deleted file mode 100644 index 428d6a183103c..0000000000000 --- a/test/smoke/src/spectron/client.ts +++ /dev/null @@ -1,204 +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 { Application } from 'spectron'; -import { RawResult, Element } from 'webdriverio'; -import { SpectronApplication } from './application'; - -/** - * Abstracts the Spectron's WebdriverIO managed client property on the created Application instances. - */ -export class SpectronClient { - - // waitFor calls should not take more than 200 * 100 = 20 seconds to complete, excluding - // the time it takes for the actual retry call to complete - private retryCount: number; - private readonly retryDuration = 100; // in milliseconds - - constructor( - readonly spectron: Application, - private application: SpectronApplication, - waitTime: number - ) { - this.retryCount = (waitTime * 1000) / this.retryDuration; - } - - keys(keys: string[]): Promise { - this.spectron.client.keys(keys); - return Promise.resolve(); - } - - async getText(selector: string, capture: boolean = true): Promise { - return this.spectron.client.getText(selector); - } - - async waitForText(selector: string, text?: string, accept?: (result: string) => boolean): Promise { - accept = accept ? accept : result => text !== void 0 ? text === result : !!result; - return this.waitFor(() => this.spectron.client.getText(selector), accept, `getText with selector ${selector}`); - } - - async waitForTextContent(selector: string, textContent?: string, accept?: (result: string) => boolean): Promise { - accept = accept ? accept : (result => textContent !== void 0 ? textContent === result : !!result); - const fn = async () => await this.spectron.client.selectorExecute(selector, div => Array.isArray(div) ? div[0].textContent : div.textContent); - return this.waitFor(fn, s => accept!(typeof s === 'string' ? s : ''), `getTextContent with selector ${selector}`); - } - - async waitForValue(selector: string, value?: string, accept?: (result: string) => boolean): Promise { - accept = accept ? accept : result => value !== void 0 ? value === result : !!result; - return this.waitFor(() => this.spectron.client.getValue(selector), accept, `getValue with selector ${selector}`); - } - - async waitAndClick(selector: string): Promise { - return this.waitFor(() => this.spectron.client.click(selector), void 0, `click with selector ${selector}`); - } - - async click(selector: string): Promise { - return this.spectron.client.click(selector); - } - - async doubleClickAndWait(selector: string, capture: boolean = true): Promise { - return this.waitFor(() => this.spectron.client.doubleClick(selector), void 0, `doubleClick with selector ${selector}`); - } - - async leftClick(selector: string, xoffset: number, yoffset: number, capture: boolean = true): Promise { - return this.spectron.client.leftClick(selector, xoffset, yoffset); - } - - async rightClick(selector: string, capture: boolean = true): Promise { - return this.spectron.client.rightClick(selector); - } - - async moveToObject(selector: string, capture: boolean = true): Promise { - return this.spectron.client.moveToObject(selector); - } - - async waitAndMoveToObject(selector: string): Promise { - return this.waitFor(() => this.spectron.client.moveToObject(selector), void 0, `move to object with selector ${selector}`); - } - - async setValue(selector: string, text: string, capture: boolean = true): Promise { - return this.spectron.client.setValue(selector, text); - } - - async waitForElements(selector: string, accept: (result: Element[]) => boolean = result => result.length > 0): Promise { - return this.waitFor>(() => this.spectron.client.elements(selector), result => accept(result.value), `elements with selector ${selector}`) - .then(result => result.value); - } - - async waitForElement(selector: string, accept: (result: Element | undefined) => boolean = result => !!result): Promise { - return this.waitFor>(() => this.spectron.client.element(selector), result => accept(result ? result.value : void 0), `element with selector ${selector}`) - .then(result => result.value); - } - - async waitForVisibility(selector: string, accept: (result: boolean) => boolean = result => result): Promise { - return this.waitFor(() => this.spectron.client.isVisible(selector), accept, `isVisible with selector ${selector}`); - } - - async element(selector: string): Promise { - return this.spectron.client.element(selector) - .then(result => result.value); - } - - async waitForActiveElement(selector: string): Promise { - return this.waitFor( - () => this.spectron.client.execute(s => document.activeElement.matches(s), selector), - r => r.value, - `wait for active element: ${selector}` - ); - } - - async waitForAttribute(selector: string, attribute: string, accept: (result: string) => boolean = result => !!result): Promise { - return this.waitFor(() => this.spectron.client.getAttribute(selector), accept, `attribute with selector ${selector}`); - } - - async dragAndDrop(sourceElem: string, destinationElem: string, capture: boolean = true): Promise { - return this.spectron.client.dragAndDrop(sourceElem, destinationElem); - } - - async selectByValue(selector: string, value: string, capture: boolean = true): Promise { - return this.spectron.client.selectByValue(selector, value); - } - - async getValue(selector: string, capture: boolean = true): Promise { - return this.spectron.client.getValue(selector); - } - - async getAttribute(selector: string, attribute: string, capture: boolean = true): Promise { - return Promise.resolve(this.spectron.client.getAttribute(selector, attribute)); - } - - buttonDown(): any { - return this.spectron.client.buttonDown(); - } - - buttonUp(): any { - return this.spectron.client.buttonUp(); - } - - async isVisible(selector: string, capture: boolean = true): Promise { - return this.spectron.client.isVisible(selector); - } - - async getTitle(): Promise { - return this.spectron.client.getTitle(); - } - - private running = false; - async waitFor(func: () => T | Promise, accept?: (result: T) => boolean | Promise, timeoutMessage?: string, retryCount?: number): Promise; - async waitFor(func: () => T | Promise, accept: (result: T) => boolean | Promise = result => !!result, timeoutMessage?: string, retryCount?: number): Promise { - if (this.running) { - throw new Error('Not allowed to run nested waitFor calls!'); - } - - this.running = true; - - try { - let trial = 1; - retryCount = typeof retryCount === 'number' ? retryCount : this.retryCount; - - while (true) { - if (trial > retryCount) { - await this.application.screenCapturer.capture('timeout'); - throw new Error(`${timeoutMessage}: Timed out after ${(retryCount * this.retryDuration) / 1000} seconds.`); - } - - let result; - try { - result = await func(); - } catch (e) { - // console.log(e); - } - - if (accept(result)) { - return result; - } - - await new Promise(resolve => setTimeout(resolve, this.retryDuration)); - trial++; - } - } finally { - this.running = false; - } - } - - // type(text: string): Promise { - // return new Promise((res) => { - // let textSplit = text.split(' '); - - // const type = async (i: number) => { - // if (!textSplit[i] || textSplit[i].length <= 0) { - // return res(); - // } - - // const toType = textSplit[i + 1] ? `${textSplit[i]} ` : textSplit[i]; - // await this.keys(toType); - // await this.keys(['NULL']); - // await type(i + 1); - // }; - - // return type(0); - // }); - // } -} \ No newline at end of file diff --git a/test/smoke/src/vscode/code.ts b/test/smoke/src/vscode/code.ts new file mode 100644 index 0000000000000..281c05eba801b --- /dev/null +++ b/test/smoke/src/vscode/code.ts @@ -0,0 +1,298 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as path from 'path'; +import * as cp from 'child_process'; +import { tmpName } from 'tmp'; +import { IDriver, connect as connectDriver, IDisposable, IElement } from './driver'; + +const repoPath = path.join(__dirname, '../../../..'); + +function getDevElectronPath(): string { + const buildPath = path.join(repoPath, '.build'); + const product = require(path.join(repoPath, 'product.json')); + + switch (process.platform) { + case 'darwin': + return path.join(buildPath, 'electron', `${product.nameLong}.app`, 'Contents', 'MacOS', 'Electron'); + case 'linux': + return path.join(buildPath, 'electron', `${product.applicationName}`); + case 'win32': + return path.join(buildPath, 'electron', `${product.nameShort}.exe`); + default: + throw new Error('Unsupported platform.'); + } +} + +function getBuildElectronPath(root: string): string { + switch (process.platform) { + case 'darwin': + return path.join(root, 'Contents', 'MacOS', 'Electron'); + case 'linux': { + const product = require(path.join(root, 'resources', 'app', 'product.json')); + return path.join(root, product.applicationName); + } + case 'win32': { + const product = require(path.join(root, 'resources', 'app', 'product.json')); + return path.join(root, `${product.nameShort}.exe`); + } + default: + throw new Error('Unsupported platform.'); + } +} + +function getDevOutPath(): string { + return path.join(repoPath, 'out'); +} + +function getBuildOutPath(root: string): string { + switch (process.platform) { + case 'darwin': + return path.join(root, 'Contents', 'Resources', 'app', 'out'); + default: + return path.join(root, 'resources', 'app', 'out'); + } +} + +async function connect(child: cp.ChildProcess, outPath: string, handlePath: string, verbose: boolean): Promise { + let errCount = 0; + + while (true) { + try { + const { client, driver } = await connectDriver(outPath, handlePath); + return new Code(child, client, driver, verbose); + } catch (err) { + if (++errCount > 50) { + child.kill(); + throw err; + } + + // retry + await new Promise(c => setTimeout(c, 100)); + } + } +} + +// Kill all running instances, when dead +const instances = new Set(); +process.once('exit', () => instances.forEach(code => code.kill())); + +export interface SpawnOptions { + codePath?: string; + workspacePath: string; + userDataDir: string; + extensionsPath: string; + verbose: boolean; + extraArgs?: string[]; +} + +export async function spawn(options: SpawnOptions): Promise { + const codePath = options.codePath; + const electronPath = codePath ? getBuildElectronPath(codePath) : getDevElectronPath(); + const outPath = codePath ? getBuildOutPath(codePath) : getDevOutPath(); + const handlePath = await new Promise((c, e) => tmpName((err, handlePath) => err ? e(err) : c(handlePath))); + + const args = [ + options.workspacePath, + '--skip-getting-started', + '--skip-release-notes', + '--sticky-quickopen', + '--disable-telemetry', + '--disable-updates', + '--disable-crash-reporter', + `--extensions-dir=${options.extensionsPath}`, + `--user-data-dir=${options.userDataDir}`, + '--driver', handlePath + ]; + + if (!codePath) { + args.unshift(repoPath); + } + + if (options.extraArgs) { + args.push(...options.extraArgs); + } + + const spawnOptions: cp.SpawnOptions = {}; + + if (options.verbose) { + spawnOptions.stdio = 'inherit'; + } + + const child = cp.spawn(electronPath, args, spawnOptions); + + instances.add(child); + child.once('exit', () => instances.delete(child)); + + return connect(child, outPath, handlePath, options.verbose); +} + +export class Code { + + private _activeWindowId: number | undefined = undefined; + private driver: IDriver; + + constructor( + private process: cp.ChildProcess, + private client: IDisposable, + driver: IDriver, + verbose: boolean + ) { + if (verbose) { + this.driver = new Proxy(driver, { + get(target, prop, receiver) { + if (typeof target[prop] !== 'function') { + return target[prop]; + } + + return function (...args) { + console.log('** ', prop, ...args.filter(a => typeof a === 'string')); + return target[prop].apply(this, args); + }; + } + }); + } else { + this.driver = driver; + } + } + + async waitForWindowIds(fn: (windowIds: number[]) => boolean): Promise { + await this.poll(() => this.driver.getWindowIds(), fn, `get window ids`); + } + + async dispatchKeybinding(keybinding: string): Promise { + const windowId = await this.getActiveWindowId(); + await this.driver.dispatchKeybinding(windowId, keybinding); + } + + async waitForTextContent(selector: string, textContent?: string, accept?: (result: string) => boolean): Promise { + const windowId = await this.getActiveWindowId(); + accept = accept || (result => textContent !== void 0 ? textContent === result : !!result); + return await this.poll(() => this.driver.getElements(windowId, selector).then(els => els[0].textContent), s => accept!(typeof s === 'string' ? s : ''), `get text content '${selector}'`); + } + + async waitAndClick(selector: string, xoffset?: number, yoffset?: number): Promise { + const windowId = await this.getActiveWindowId(); + await this.poll(() => this.driver.click(windowId, selector, xoffset, yoffset), () => true, `click '${selector}'`); + } + + async waitAndDoubleClick(selector: string): Promise { + const windowId = await this.getActiveWindowId(); + await this.poll(() => this.driver.doubleClick(windowId, selector), () => true, `double click '${selector}'`); + } + + async waitAndMove(selector: string): Promise { + const windowId = await this.getActiveWindowId(); + await this.poll(() => this.driver.move(windowId, selector), () => true, `move '${selector}'`); + } + + async waitForSetValue(selector: string, value: string): Promise { + const windowId = await this.getActiveWindowId(); + await this.poll(() => this.driver.setValue(windowId, selector, value), () => true, `set value '${selector}'`); + } + + async waitForElements(selector: string, recursive: boolean, accept: (result: IElement[]) => boolean = result => result.length > 0): Promise { + const windowId = await this.getActiveWindowId(); + return await this.poll(() => this.driver.getElements(windowId, selector, recursive), accept, `get elements '${selector}'`); + } + + async waitForElement(selector: string, accept: (result: IElement | undefined) => boolean = result => !!result): Promise { + const windowId = await this.getActiveWindowId(); + return await this.poll(() => this.driver.getElements(windowId, selector).then(els => els[0]), accept, `get element '${selector}'`); + } + + async waitForActiveElement(selector: string): Promise { + const windowId = await this.getActiveWindowId(); + await this.poll(() => this.driver.isActiveElement(windowId, selector), r => r, `is active element '${selector}'`); + } + + async waitForTitle(fn: (title: string) => boolean): Promise { + const windowId = await this.getActiveWindowId(); + await this.poll(() => this.driver.getTitle(windowId), fn, `get title`); + } + + async waitForTypeInEditor(selector: string, text: string): Promise { + const windowId = await this.getActiveWindowId(); + await this.poll(() => this.driver.typeInEditor(windowId, selector, text), () => true, `type in editor '${selector}'`); + } + + private static PollRetryCount: number = 200; + private static PollInterval = 100; // millis + + private async poll(fn: () => Promise, accept: (result: T) => boolean, timeoutMessage: string): Promise { + let trial = 1; + + while (true) { + if (trial > Code.PollRetryCount) { + throw new Error(`Timeout: ${timeoutMessage} after ${(Code.PollRetryCount * Code.PollInterval) / 1000} seconds.`); + } + + let result; + try { + result = await fn(); + + if (accept(result)) { + return result; + } + } catch (e) { + // console.warn(e); + + if (/Method not implemented/.test(e.message)) { + throw e; + } + } + + await new Promise(resolve => setTimeout(resolve, Code.PollInterval)); + trial++; + } + } + + private async getActiveWindowId(): Promise { + if (typeof this._activeWindowId !== 'number') { + const windows = await this.driver.getWindowIds(); + this._activeWindowId = windows[0]; + } + + return this._activeWindowId; + } + + dispose(): void { + this.client.dispose(); + this.process.kill(); + } +} + +export function findElement(element: IElement, fn: (element: IElement) => boolean): IElement | null { + const queue = [element]; + + while (queue.length > 0) { + const element = queue.shift()!; + + if (fn(element)) { + return element; + } + + queue.push(...element.children); + } + + return null; +} + +export function findElements(element: IElement, fn: (element: IElement) => boolean): IElement[] { + const result: IElement[] = []; + const queue = [element]; + + while (queue.length > 0) { + const element = queue.shift()!; + + if (fn(element)) { + result.push(element); + } + + queue.push(...element.children); + } + + return result; +} \ No newline at end of file diff --git a/test/smoke/src/vscode/driver.js b/test/smoke/src/vscode/driver.js new file mode 100644 index 0000000000000..24af32d436f88 --- /dev/null +++ b/test/smoke/src/vscode/driver.js @@ -0,0 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +const path = require('path'); + +exports.connect = function (outPath, handle) { + const bootstrapPath = path.join(outPath, 'bootstrap-amd.js'); + const { bootstrap } = require(bootstrapPath); + return new Promise((c, e) => bootstrap('vs/platform/driver/node/driver', ({ connect }) => connect(handle).then(c, e), e)); +}; \ No newline at end of file diff --git a/test/smoke/test/mocha.opts b/test/smoke/test/mocha.opts index 102d5b65adee8..9f76965562507 100644 --- a/test/smoke/test/mocha.opts +++ b/test/smoke/test/mocha.opts @@ -1,3 +1,3 @@ ---timeout 60000 +--timeout 20000 --slow 20000 out/main.js \ No newline at end of file diff --git a/test/smoke/tools/copy-driver-definition.js b/test/smoke/tools/copy-driver-definition.js new file mode 100644 index 0000000000000..fedf0c2843217 --- /dev/null +++ b/test/smoke/tools/copy-driver-definition.js @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +const fs = require('fs'); +const path = require('path'); + +const root = path.dirname(path.dirname(path.dirname(__dirname))); +const driverPath = path.join(root, 'src/vs/platform/driver/common/driver.ts'); + +let contents = fs.readFileSync(driverPath, 'utf8'); +contents = /\/\/\*START([\s\S]*)\/\/\*END/mi.exec(contents)[1].trim(); +contents = contents.replace(/\bTPromise\b/g, 'Promise'); + +contents = `/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +${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/vscode'); +const outDriverPath = path.join(srcPath, 'driver.d.ts'); + +fs.writeFileSync(outDriverPath, contents); \ No newline at end of file diff --git a/test/smoke/yarn.lock b/test/smoke/yarn.lock index 0508d361c3b33..2f15aec32788e 100644 --- a/test/smoke/yarn.lock +++ b/test/smoke/yarn.lock @@ -58,6 +58,17 @@ dependencies: "@types/node" "*" +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + +ajv@^4.9.1: + version "4.11.8" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" + dependencies: + co "^4.6.0" + json-stable-stringify "^1.0.1" + ajv@^5.1.0: version "5.3.0" resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.3.0.tgz#4414ff74a50879c208ee5fdc826e32c303549eda" @@ -67,56 +78,66 @@ ajv@^5.1.0: fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.3.0" -amdefine@>=0.0.4: - version "1.0.1" - resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" - -ansi-escapes@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.0.0.tgz#ec3e8b4e9f8064fc02c3ac9b65f1c275bda8ef92" +ansi-regex@^0.2.0, ansi-regex@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-0.2.1.tgz#0d8e946967a3d8143f93e24e298525fc1b2235f9" ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" +ansi-styles@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.1.0.tgz#eaecbf66cd706882760b2f4691582b8f55d7a7de" -ansi-styles@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88" +anymatch@^1.3.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a" dependencies: - color-convert "^1.9.0" + micromatch "^2.1.5" + normalize-path "^2.0.0" -archiver-utils@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-1.3.0.tgz#e50b4c09c70bf3d680e32ff1b7994e9f9d895174" +aproba@^1.0.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + +are-we-there-yet@~1.1.2: + version "1.1.4" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz#bb5dca382bb94f05e15194373d16fd3ba1ca110d" dependencies: - glob "^7.0.0" - graceful-fs "^4.1.0" - lazystream "^1.0.0" - lodash "^4.8.0" - normalize-path "^2.0.0" - readable-stream "^2.0.0" + delegates "^1.0.0" + readable-stream "^2.0.6" -archiver@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/archiver/-/archiver-2.1.0.tgz#d2df2e8d5773a82c1dcce925ccc41450ea999afd" +arr-diff@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" dependencies: - archiver-utils "^1.3.0" - async "^2.0.0" - buffer-crc32 "^0.2.1" - glob "^7.0.0" - lodash "^4.8.0" - readable-stream "^2.0.0" - tar-stream "^1.5.0" - zip-stream "^1.2.0" + arr-flatten "^1.0.1" + +arr-flatten@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + +array-filter@~0.0.0: + version "0.0.1" + resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-0.0.1.tgz#7da8cf2e26628ed732803581fd21f67cacd2eeec" array-find-index@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" +array-map@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662" + +array-reduce@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/array-reduce/-/array-reduce-0.0.0.tgz#173899d3ffd1c7d9383e4479525dbe278cab5f2b" + +array-unique@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" + asn1@~0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" @@ -125,29 +146,35 @@ assert-plus@1.0.0, assert-plus@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" -async@^2.0.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4" - dependencies: - lodash "^4.14.0" +assert-plus@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" + +async-each@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" -atob@~1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/atob/-/atob-1.1.3.tgz#95f13629b12c3a51a5d215abdce2aa9f32f80773" +aws-sign2@~0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" +aws4@^1.2.1: + version "1.7.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.7.0.tgz#d4d0e9b9dbfca77bf08eeb0a8a471550fe39e289" + aws4@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" -babel-runtime@^6.26.0: +babel-runtime@^6.9.2: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" dependencies: @@ -164,16 +191,26 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -bl@^1.0.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.1.tgz#cac328f7bee45730d404b692203fcb590e172d5e" +binary-extensions@^1.0.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205" + +block-stream@*: + version "0.0.9" + resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" dependencies: - readable-stream "^2.0.5" + inherits "~2.0.0" bluebird@^2.9.34: version "2.11.0" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1" +boom@2.x.x: + version "2.10.1" + resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" + dependencies: + hoek "2.x.x" + boom@4.x.x: version "4.3.1" resolved "https://registry.yarnpkg.com/boom/-/boom-4.3.1.tgz#4f8a3005cb4a7e3889f749030fd25b96e01d2e31" @@ -193,14 +230,18 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +braces@^1.8.2: + version "1.8.5" + resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" + dependencies: + expand-range "^1.8.1" + preserve "^0.2.0" + repeat-element "^1.1.2" + browser-stdout@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f" -buffer-crc32@^0.2.1: - version "0.2.13" - resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" - builtin-modules@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" @@ -220,23 +261,30 @@ caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" -chalk@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba" +chalk@0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.5.1.tgz#663b3a648b68b55d04690d49167aa837858f2174" dependencies: - ansi-styles "^3.1.0" - escape-string-regexp "^1.0.5" - supports-color "^4.0.0" + ansi-styles "^1.1.0" + escape-string-regexp "^1.0.0" + has-ansi "^0.1.0" + strip-ansi "^0.3.0" + supports-color "^0.2.0" -cli-cursor@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" +chokidar@^1.6.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" dependencies: - restore-cursor "^2.0.0" - -cli-width@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" + anymatch "^1.3.0" + async-each "^1.0.0" + glob-parent "^2.0.0" + inherits "^2.0.1" + is-binary-path "^1.0.0" + is-glob "^2.0.0" + path-is-absolute "^1.0.0" + readdirp "^2.0.0" + optionalDependencies: + fsevents "^1.0.0" co@^4.6.0: version "4.6.0" @@ -246,22 +294,16 @@ code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" -color-convert@^1.9.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed" - dependencies: - color-name "^1.1.1" - -color-name@^1.1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - combined-stream@^1.0.5, combined-stream@~1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" dependencies: delayed-stream "~1.0.0" +commander@2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.6.0.tgz#9df7e52fb2a0cb0fb89058ee80c3104225f37e1d" + commander@2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" @@ -272,15 +314,6 @@ commander@^2.8.1: version "2.11.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" -compress-commons@^1.2.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-1.2.2.tgz#524a9f10903f3a813389b0225d27c48bb751890f" - dependencies: - buffer-crc32 "^0.2.1" - crc32-stream "^2.0.0" - normalize-path "^2.0.0" - readable-stream "^2.0.0" - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -293,6 +326,23 @@ concat-stream@1.6.0: readable-stream "^2.2.2" typedarray "^0.0.6" +concurrently@^3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-3.5.1.tgz#ee8b60018bbe86b02df13e5249453c6ececd2521" + dependencies: + chalk "0.5.1" + commander "2.6.0" + date-fns "^1.23.0" + lodash "^4.5.1" + rx "2.3.24" + spawn-command "^0.0.2-1" + supports-color "^3.2.3" + tree-kill "^1.1.0" + +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + core-js@^2.4.0: version "2.5.1" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b" @@ -301,16 +351,27 @@ core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" -crc32-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-2.0.0.tgz#e3cdd3b4df3168dd74e3de3fbbcb7b297fe908f4" +cpx@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/cpx/-/cpx-1.5.0.tgz#185be018511d87270dedccc293171e37655ab88f" dependencies: - crc "^3.4.4" - readable-stream "^2.0.0" + babel-runtime "^6.9.2" + chokidar "^1.6.0" + duplexer "^0.1.1" + glob "^7.0.5" + glob2base "^0.0.12" + minimatch "^3.0.2" + mkdirp "^0.5.1" + resolve "^1.1.7" + safe-buffer "^5.0.1" + shell-quote "^1.6.1" + subarg "^1.0.0" -crc@^3.4.4: - version "3.5.0" - resolved "https://registry.yarnpkg.com/crc/-/crc-3.5.0.tgz#98b8ba7d489665ba3979f59b21381374101a1964" +cryptiles@2.x.x: + version "2.0.5" + resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" + dependencies: + boom "2.x.x" cryptiles@3.x.x: version "3.1.2" @@ -318,25 +379,6 @@ cryptiles@3.x.x: dependencies: boom "5.x.x" -css-parse@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/css-parse/-/css-parse-2.0.0.tgz#a468ee667c16d81ccf05c58c38d2a97c780dbfd4" - dependencies: - css "^2.0.0" - -css-value@~0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/css-value/-/css-value-0.0.1.tgz#5efd6c2eea5ea1fd6b6ac57ec0427b18452424ea" - -css@^2.0.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/css/-/css-2.2.1.tgz#73a4c81de85db664d4ee674f7d47085e3b2d55dc" - dependencies: - inherits "^2.0.1" - source-map "^0.1.38" - source-map-resolve "^0.3.0" - urix "^0.1.0" - currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" @@ -349,6 +391,10 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +date-fns@^1.23.0: + version "1.29.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.29.0.tgz#12e609cdcb935127311d04d33334e2960a2a54e6" + debug@2.6.8: version "2.6.8" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" @@ -369,17 +415,17 @@ deep-extend@~0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" -deepmerge@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.0.1.tgz#25c1c24f110fb914f80001b925264dd77f3f4312" - delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" -dev-null@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/dev-null/-/dev-null-0.1.1.tgz#5a205ce3c2b2ef77b6238d6ba179eb74c6a0e818" +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + +detect-libc@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" diff@3.2.0: version "3.2.0" @@ -413,23 +459,16 @@ domutils@^1.5.1: dom-serializer "0" domelementtype "1" +duplexer@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" + ecc-jsbn@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" dependencies: jsbn "~0.1.0" -ejs@~2.5.6: - version "2.5.7" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.7.tgz#cc872c168880ae3c7189762fd5ffc00896c9518a" - -electron-chromedriver@~1.7.1: - version "1.7.1" - resolved "https://registry.yarnpkg.com/electron-chromedriver/-/electron-chromedriver-1.7.1.tgz#008c97976007aa4eb18491ee095e94d17ee47610" - dependencies: - electron-download "^4.1.0" - extract-zip "^1.6.5" - electron-download@^3.0.1: version "3.3.0" resolved "https://registry.yarnpkg.com/electron-download/-/electron-download-3.3.0.tgz#2cfd54d6966c019c4d49ad65fbe65cc9cdef68c8" @@ -444,20 +483,6 @@ electron-download@^3.0.1: semver "^5.3.0" sumchecker "^1.2.0" -electron-download@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/electron-download/-/electron-download-4.1.0.tgz#bf932c746f2f87ffcc09d1dd472f2ff6b9187845" - dependencies: - debug "^2.2.0" - env-paths "^1.0.0" - fs-extra "^2.0.0" - minimist "^1.2.0" - nugget "^2.0.0" - path-exists "^3.0.0" - rc "^1.1.2" - semver "^5.3.0" - sumchecker "^2.0.1" - electron@1.7.7: version "1.7.7" resolved "https://registry.yarnpkg.com/electron/-/electron-1.7.7.tgz#cfd89ca9eba79d763ac0b0c6dcc583792097b9b6" @@ -466,20 +491,10 @@ electron@1.7.7: electron-download "^3.0.1" extract-zip "^1.0.3" -end-of-stream@^1.0.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.0.tgz#7a90d833efda6cfa6eac0f4949dbb0fad3a63206" - dependencies: - once "^1.4.0" - entities@^1.1.1, entities@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" -env-paths@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-1.0.0.tgz#4168133b42bb05c38a35b1ae4397c8298ab369e0" - error-ex@^1.2.0: version "1.3.1" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc" @@ -490,23 +505,39 @@ es6-promise@^4.0.5: version "4.1.1" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.1.1.tgz#8811e90915d9a0dba36274f0b242dbda78f9c92a" -escape-string-regexp@1.0.5, escape-string-regexp@^1.0.5: +escape-string-regexp@1.0.5, escape-string-regexp@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" -extend@~3.0.1: +exec-sh@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.1.tgz#163b98a6e89e6b65b47c2a28d215bc1f63989c38" + dependencies: + merge "^1.1.3" + +expand-brackets@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" + dependencies: + is-posix-bracket "^0.1.0" + +expand-range@^1.8.1: + version "1.8.2" + resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" + dependencies: + fill-range "^2.1.0" + +extend@~3.0.0, extend@~3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" -external-editor@^2.0.4: - version "2.0.5" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.0.5.tgz#52c249a3981b9ba187c7cacf5beb50bf1d91a6bc" +extglob@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" dependencies: - iconv-lite "^0.4.17" - jschardet "^1.4.2" - tmp "^0.0.33" + is-extglob "^1.0.0" -extract-zip@^1.0.3, extract-zip@^1.6.5: +extract-zip@^1.0.3: version "1.6.6" resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.6.tgz#1290ede8d20d0872b429fd3f351ca128ec5ef85c" dependencies: @@ -533,11 +564,23 @@ fd-slicer@~1.0.1: dependencies: pend "~1.2.0" -figures@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" +filename-regex@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" + +fill-range@^2.1.0: + version "2.2.3" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" dependencies: - escape-string-regexp "^1.0.5" + is-number "^2.1.0" + isobject "^2.0.0" + randomatic "^1.1.3" + repeat-element "^1.1.2" + repeat-string "^1.5.2" + +find-index@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/find-index/-/find-index-0.1.1.tgz#675d358b2ca3892d795a1ab47232f8b6e2e0dde4" find-up@^1.0.0: version "1.1.2" @@ -546,10 +589,28 @@ find-up@^1.0.0: path-exists "^2.0.0" pinkie-promise "^2.0.0" +for-in@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + +for-own@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" + dependencies: + for-in "^1.0.1" + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" +form-data@~2.1.1: + version "2.1.4" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.5" + mime-types "^2.1.12" + form-data@~2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.1.tgz#6fb94fbd71885306d73d15cc497fe4cc4ecd44bf" @@ -568,22 +629,46 @@ fs-extra@^0.30.0: path-is-absolute "^1.0.0" rimraf "^2.2.8" -fs-extra@^2.0.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-2.1.2.tgz#046c70163cef9aad46b0e4a7fa467fb22d71de35" - dependencies: - graceful-fs "^4.1.2" - jsonfile "^2.1.0" - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" -gaze@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.2.tgz#847224677adb8870d679257ed3388fdb61e40105" +fsevents@^1.0.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.3.tgz#11f82318f5fe7bb2cd22965a108e9306208216d8" dependencies: - globule "^1.0.0" + nan "^2.3.0" + node-pre-gyp "^0.6.39" + +fstream-ignore@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105" + dependencies: + fstream "^1.0.0" + inherits "2" + minimatch "^3.0.0" + +fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: + version "1.0.11" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" + dependencies: + graceful-fs "^4.1.2" + inherits "~2.0.0" + mkdirp ">=0.5 0" + rimraf "2" + +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" get-stdin@^4.0.1: version "4.0.1" @@ -595,6 +680,25 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" +glob-base@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" + dependencies: + glob-parent "^2.0.0" + is-glob "^2.0.0" + +glob-parent@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" + dependencies: + is-glob "^2.0.0" + +glob2base@^0.0.12: + version "0.0.12" + resolved "https://registry.yarnpkg.com/glob2base/-/glob2base-0.0.12.tgz#9d419b3e28f12e83a362164a277055922c9c0d56" + dependencies: + find-index "^0.1.1" + glob@7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" @@ -606,7 +710,7 @@ glob@7.1.1: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.0, glob@^7.0.5, glob@~7.1.1: +glob@^7.0.5: version "7.1.2" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" dependencies: @@ -617,15 +721,7 @@ glob@^7.0.0, glob@^7.0.5, glob@~7.1.1: once "^1.3.0" path-is-absolute "^1.0.0" -globule@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/globule/-/globule-1.2.0.tgz#1dc49c6822dd9e8a2fa00ba2a295006e8664bd09" - dependencies: - glob "~7.1.1" - lodash "~4.17.4" - minimatch "~3.0.2" - -graceful-fs@^4.1.0, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9: +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" @@ -637,10 +733,21 @@ growl@1.9.2: version "1.9.2" resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" +har-schema@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" + har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" +har-validator@~4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" + dependencies: + ajv "^4.9.1" + har-schema "^1.0.5" + har-validator@~5.0.3: version "5.0.3" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd" @@ -648,13 +755,28 @@ har-validator@~5.0.3: ajv "^5.1.0" har-schema "^2.0.0" +has-ansi@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-0.1.0.tgz#84f265aae8c0e6a88a12d7022894b7568894c62e" + dependencies: + ansi-regex "^0.2.0" + has-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" -has-flag@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + +hawk@3.1.3, hawk@~3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" + dependencies: + boom "2.x.x" + cryptiles "2.x.x" + hoek "2.x.x" + sntp "1.x.x" hawk@~6.0.2: version "6.0.2" @@ -669,6 +791,10 @@ he@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" +hoek@2.x.x: + version "2.16.3" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" + hoek@4.x.x: version "4.2.0" resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d" @@ -692,6 +818,14 @@ htmlparser2@^3.9.2: inherits "^2.0.1" readable-stream "^2.0.2" +http-signature@~1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" + dependencies: + assert-plus "^0.2.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" @@ -700,10 +834,6 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" -iconv-lite@^0.4.17: - version "0.4.19" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" - indent-string@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" @@ -717,7 +847,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" @@ -725,35 +855,44 @@ ini@~1.3.0: version "1.3.4" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e" -inquirer@~3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9" - dependencies: - ansi-escapes "^3.0.0" - chalk "^2.0.0" - cli-cursor "^2.1.0" - cli-width "^2.0.0" - external-editor "^2.0.4" - figures "^2.0.0" - lodash "^4.3.0" - mute-stream "0.0.7" - run-async "^2.2.0" - rx-lite "^4.0.8" - rx-lite-aggregates "^4.0.8" - string-width "^2.1.0" - strip-ansi "^4.0.0" - through "^2.3.6" - is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" +is-binary-path@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" + dependencies: + binary-extensions "^1.0.0" + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + is-builtin-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" dependencies: builtin-modules "^1.0.0" +is-dotfile@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" + +is-equal-shallow@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534" + dependencies: + is-primitive "^2.0.0" + +is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + +is-extglob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" + is-finite@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" @@ -766,13 +905,31 @@ is-fullwidth-code-point@^1.0.0: dependencies: number-is-nan "^1.0.0" -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" +is-glob@^2.0.0, is-glob@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" + dependencies: + is-extglob "^1.0.0" -is-promise@^2.1.0: +is-number@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" + dependencies: + kind-of "^3.0.2" + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + dependencies: + kind-of "^3.0.2" + +is-posix-bracket@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" + +is-primitive@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" is-typedarray@~1.0.0: version "1.0.0" @@ -786,10 +943,16 @@ isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" -isarray@~1.0.0: +isarray@1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + dependencies: + isarray "1.0.0" + isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -798,10 +961,6 @@ jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" -jschardet@^1.4.2: - version "1.6.0" - resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-1.6.0.tgz#c7d1a71edcff2839db2f9ec30fc5d5ebd3c1a678" - json-schema-traverse@^0.3.0: version "0.3.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" @@ -810,6 +969,12 @@ json-schema@0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" +json-stable-stringify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" + dependencies: + jsonify "~0.0.0" + json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" @@ -824,6 +989,10 @@ jsonfile@^2.1.0: optionalDependencies: graceful-fs "^4.1.6" +jsonify@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -833,18 +1002,24 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" +kind-of@^3.0.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + dependencies: + is-buffer "^1.1.5" + klaw@^1.0.0: version "1.3.1" resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439" optionalDependencies: graceful-fs "^4.1.9" -lazystream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4" - dependencies: - readable-stream "^2.0.5" - load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" @@ -902,9 +1077,9 @@ lodash.keys@^3.0.0: lodash.isarguments "^3.0.0" lodash.isarray "^3.0.0" -lodash@^4.14.0, lodash@^4.3.0, lodash@^4.8.0, lodash@~4.17.4: - version "4.17.4" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" +lodash@^4.5.1: + version "4.17.5" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511" loud-rejection@^1.0.0: version "1.6.0" @@ -932,21 +1107,49 @@ meow@^3.1.0: redent "^1.0.0" trim-newlines "^1.0.0" +merge@^1.1.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da" + +micromatch@^2.1.5: + version "2.3.11" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" + dependencies: + arr-diff "^2.0.0" + array-unique "^0.2.1" + braces "^1.8.2" + expand-brackets "^0.1.4" + extglob "^0.3.1" + filename-regex "^2.0.0" + is-extglob "^1.0.0" + is-glob "^2.0.1" + kind-of "^3.0.2" + normalize-path "^2.0.1" + object.omit "^2.0.0" + parse-glob "^3.0.4" + regex-cache "^0.4.2" + mime-db@~1.30.0: version "1.30.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" +mime-db@~1.33.0: + version "1.33.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" + mime-types@^2.1.12, mime-types@~2.1.17: version "2.1.17" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" dependencies: mime-db "~1.30.0" -mimic-fn@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" +mime-types@~2.1.7: + version "2.1.18" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" + dependencies: + mime-db "~1.33.0" -minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2: +minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" dependencies: @@ -960,17 +1163,13 @@ minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" -minimist@~0.0.1: - version "0.0.10" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" - mkdirp@0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.0.tgz#1d73076a6df986cd9344e15e71fcc05a4c9abf12" dependencies: minimist "0.0.8" -mkdirp@0.5.1, mkdirp@^0.5.1, mkdirp@~0.5.1: +mkdirp@0.5.1, "mkdirp@>=0.5 0", mkdirp@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" dependencies: @@ -997,14 +1196,37 @@ ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" -mute-stream@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" +nan@^2.3.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f" ncp@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3" +node-pre-gyp@^0.6.39: + version "0.6.39" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz#c00e96860b23c0e1420ac7befc5044e1d78d8649" + dependencies: + detect-libc "^1.0.2" + hawk "3.1.3" + mkdirp "^0.5.1" + nopt "^4.0.1" + npmlog "^4.0.2" + rc "^1.1.7" + request "2.81.0" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^2.2.1" + tar-pack "^3.4.0" + +nopt@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" + dependencies: + abbrev "1" + osenv "^0.1.4" + normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: version "2.4.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" @@ -1014,15 +1236,20 @@ normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" -normalize-path@^2.0.0: +normalize-path@^2.0.0, normalize-path@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" dependencies: remove-trailing-separator "^1.0.1" -npm-install-package@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/npm-install-package/-/npm-install-package-2.1.0.tgz#d7efe3cfcd7ab00614b896ea53119dc9ab259125" +npmlog@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" nugget@^2.0.0: version "2.0.1" @@ -1040,11 +1267,11 @@ number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" -oauth-sign@~0.8.2: +oauth-sign@~0.8.1, oauth-sign@~0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" -object-assign@^4.0.1: +object-assign@^4.0.1, object-assign@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -1052,29 +1279,43 @@ object-keys@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-0.4.0.tgz#28a6aae7428dd2c3a92f3d95f21335dd204e0336" -once@^1.3.0, once@^1.4.0: +object.omit@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" + dependencies: + for-own "^0.1.4" + is-extendable "^0.1.1" + +once@^1.3.0, once@^1.3.3: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" dependencies: wrappy "1" -onetime@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" - dependencies: - mimic-fn "^1.0.0" - -optimist@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" - dependencies: - minimist "~0.0.1" - wordwrap "~0.0.2" +os-homedir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" -os-tmpdir@~1.0.2: +os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" +osenv@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.0" + +parse-glob@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" + dependencies: + glob-base "^0.3.0" + is-dotfile "^1.0.0" + is-extglob "^1.0.0" + is-glob "^2.0.0" + parse-json@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" @@ -1087,14 +1328,14 @@ path-exists@^2.0.0, path-exists@^2.1.0: dependencies: pinkie-promise "^2.0.0" -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" +path-parse@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" + path-type@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" @@ -1107,6 +1348,10 @@ pend@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" +performance-now@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" + performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" @@ -1133,6 +1378,10 @@ portastic@^1.0.1: commander "^2.8.1" debug "^2.2.0" +preserve@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" + pretty-bytes@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-1.0.4.tgz#0a22e8210609ad35542f8c8d5d2159aff0751c84" @@ -1144,6 +1393,10 @@ process-nextick-args@~1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" +process-nextick-args@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" + progress-stream@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/progress-stream/-/progress-stream-1.2.0.tgz#2cd3cfea33ba3a89c9c121ec3347abe9ab125f77" @@ -1151,25 +1404,24 @@ progress-stream@^1.1.0: speedometer "~0.1.2" through2 "~0.2.3" -punycode@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" - punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" -q@~1.5.0: - version "1.5.1" - resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" +qs@~6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" qs@~6.5.1: version "6.5.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" -querystring@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" +randomatic@^1.1.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c" + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" rc@^1.1.2: version "1.2.2" @@ -1180,6 +1432,15 @@ rc@^1.1.2: minimist "^1.2.0" strip-json-comments "~2.0.1" +rc@^1.1.7: + version "1.2.6" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.6.tgz#eb18989c6d4f4f162c399f79ddd29f3835568092" + dependencies: + deep-extend "~0.4.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + read-pkg-up@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" @@ -1195,7 +1456,7 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" -readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.2.2: +readable-stream@^2.0.2, readable-stream@^2.2.2: version "2.3.3" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" dependencies: @@ -1207,6 +1468,18 @@ readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable string_decoder "~1.0.3" util-deprecate "~1.0.1" +readable-stream@^2.0.6, readable-stream@^2.1.4: + version "2.3.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + readable-stream@~1.1.9: version "1.1.14" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" @@ -1216,6 +1489,15 @@ readable-stream@~1.1.9: isarray "0.0.1" string_decoder "~0.10.x" +readdirp@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78" + dependencies: + graceful-fs "^4.1.2" + minimatch "^3.0.2" + readable-stream "^2.0.2" + set-immediate-shim "^1.0.1" + redent@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" @@ -1227,17 +1509,58 @@ regenerator-runtime@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz#7e54fe5b5ccd5d6624ea6255c3473be090b802e1" +regex-cache@^0.4.2: + version "0.4.4" + resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd" + dependencies: + is-equal-shallow "^0.1.3" + remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" +repeat-element@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" + +repeat-string@^1.5.2: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + repeating@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" dependencies: is-finite "^1.0.0" -request@^2.45.0, request@^2.81.0, request@~2.83.0: +request@2.81.0: + version "2.81.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" + dependencies: + aws-sign2 "~0.6.0" + aws4 "^1.2.1" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.0" + forever-agent "~0.6.1" + form-data "~2.1.1" + har-validator "~4.2.1" + hawk "~3.1.3" + http-signature "~1.1.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.7" + oauth-sign "~0.8.1" + performance-now "^0.2.0" + qs "~6.4.0" + safe-buffer "^5.0.1" + stringstream "~0.0.4" + tough-cookie "~2.3.0" + tunnel-agent "^0.6.0" + uuid "^3.0.0" + +request@^2.45.0: version "2.83.0" resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356" dependencies: @@ -1264,42 +1587,21 @@ request@^2.45.0, request@^2.81.0, request@~2.83.0: tunnel-agent "^0.6.0" uuid "^3.1.0" -resolve-url@~0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - -restore-cursor@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" +resolve@^1.1.7: + version "1.7.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.0.tgz#2bdf5374811207285df0df652b78f118ab8f3c5e" dependencies: - onetime "^2.0.0" - signal-exit "^3.0.2" - -rgb2hex@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/rgb2hex/-/rgb2hex-0.1.0.tgz#ccd55f860ae0c5c4ea37504b958e442d8d12325b" + path-parse "^1.0.5" -rimraf@^2.2.8, rimraf@^2.6.1: +rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.6.1: version "2.6.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" dependencies: glob "^7.0.5" -run-async@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" - dependencies: - is-promise "^2.1.0" - -rx-lite-aggregates@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be" - dependencies: - rx-lite "*" - -rx-lite@*, rx-lite@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" +rx@2.3.24: + version "2.3.24" + resolved "https://registry.yarnpkg.com/rx/-/rx-2.3.24.tgz#14f950a4217d7e35daa71bbcbe58eff68ea4b2b7" safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.1" @@ -1309,7 +1611,24 @@ safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.4.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" -signal-exit@^3.0.0, signal-exit@^3.0.2: +set-blocking@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + +set-immediate-shim@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" + +shell-quote@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.6.1.tgz#f4781949cce402697127430ea3b3c5476f481767" + dependencies: + array-filter "~0.0.0" + array-map "~0.0.0" + array-reduce "~0.0.0" + jsonify "~0.0.0" + +signal-exit@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" @@ -1319,30 +1638,21 @@ single-line-log@^1.1.2: dependencies: string-width "^1.0.1" +sntp@1.x.x: + version "1.0.9" + resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" + dependencies: + hoek "2.x.x" + sntp@2.x.x: version "2.1.0" resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.1.0.tgz#2c6cec14fedc2222739caf9b5c3d85d1cc5a2cc8" dependencies: hoek "4.x.x" -source-map-resolve@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.3.1.tgz#610f6122a445b8dd51535a2a71b783dfc1248761" - dependencies: - atob "~1.1.0" - resolve-url "~0.2.1" - source-map-url "~0.3.0" - urix "~0.1.0" - -source-map-url@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.3.0.tgz#7ecaf13b57bcd09da8a40c5d269db33799d4aaf9" - -source-map@^0.1.38: - version "0.1.43" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346" - dependencies: - amdefine ">=0.0.4" +spawn-command@^0.0.2-1: + version "0.0.2-1" + resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0" spdx-correct@~1.0.0: version "1.0.2" @@ -1358,26 +1668,10 @@ spdx-license-ids@^1.0.2: version "1.2.2" resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" -spectron@^3.7.2: - version "3.7.2" - resolved "https://registry.yarnpkg.com/spectron/-/spectron-3.7.2.tgz#86f41306a9b70ed6ee1500f7f7d3adc389afb446" - dependencies: - dev-null "^0.1.1" - electron-chromedriver "~1.7.1" - request "^2.81.0" - split "^1.0.0" - webdriverio "^4.8.0" - speedometer@~0.1.2: version "0.1.4" resolved "https://registry.yarnpkg.com/speedometer/-/speedometer-0.1.4.tgz#9876dbd2a169d3115402d48e6ea6329c8816a50d" -split@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" - dependencies: - through "2" - sshpk@^1.7.0: version "1.13.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3" @@ -1392,7 +1686,7 @@ sshpk@^1.7.0: jsbn "~0.1.0" tweetnacl "~0.14.0" -string-width@^1.0.1: +string-width@^1.0.1, string-width@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" dependencies: @@ -1400,13 +1694,6 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -string-width@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" @@ -1417,22 +1704,28 @@ string_decoder@~1.0.3: dependencies: safe-buffer "~5.1.0" -stringstream@~0.0.5: +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + dependencies: + safe-buffer "~5.1.0" + +stringstream@~0.0.4, stringstream@~0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" -strip-ansi@^3.0.0: +strip-ansi@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.3.0.tgz#25f48ea22ca79187f3174a4db8759347bb126220" + dependencies: + ansi-regex "^0.2.1" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" dependencies: ansi-regex "^2.0.0" -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - dependencies: - ansi-regex "^3.0.0" - strip-bom@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" @@ -1449,6 +1742,12 @@ strip-json-comments@^2.0.1, strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" +subarg@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/subarg/-/subarg-1.0.0.tgz#f62cf17581e996b48fc965699f54c06ae268b8d2" + dependencies: + minimist "^1.1.0" + sumchecker@^1.2.0: version "1.3.1" resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-1.3.1.tgz#79bb3b4456dd04f18ebdbc0d703a1d1daec5105d" @@ -1456,38 +1755,42 @@ sumchecker@^1.2.0: debug "^2.2.0" es6-promise "^4.0.5" -sumchecker@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-2.0.2.tgz#0f42c10e5d05da5d42eea3e56c3399a37d6c5b3e" - dependencies: - debug "^2.2.0" - supports-color@3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5" dependencies: has-flag "^1.0.0" -supports-color@^4.0.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b" - dependencies: - has-flag "^2.0.0" +supports-color@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-0.2.0.tgz#d92de2694eb3f67323973d7ae3d8b55b4c22190a" -supports-color@~5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.0.0.tgz#1db26229f6ae02f9acdb5410907c36ce2e362b13" +supports-color@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" dependencies: - has-flag "^2.0.0" + has-flag "^1.0.0" -tar-stream@^1.5.0: - version "1.5.5" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.5.5.tgz#5cad84779f45c83b1f2508d96b09d88c7218af55" +tar-pack@^3.4.0: + version "3.4.1" + resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.1.tgz#e1dbc03a9b9d3ba07e896ad027317eb679a10a1f" + dependencies: + debug "^2.2.0" + fstream "^1.0.10" + fstream-ignore "^1.0.5" + once "^1.3.3" + readable-stream "^2.1.4" + rimraf "^2.5.1" + tar "^2.2.1" + uid-number "^0.0.6" + +tar@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" dependencies: - bl "^1.0.0" - end-of-stream "^1.0.0" - readable-stream "^2.0.0" - xtend "^4.0.0" + block-stream "*" + fstream "^1.0.2" + inherits "2" throttleit@0.0.2: version "0.0.2" @@ -1500,22 +1803,28 @@ through2@~0.2.3: readable-stream "~1.1.9" xtend "~2.1.1" -through@2, through@^2.3.6: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - -tmp@0.0.33, tmp@^0.0.33: +tmp@0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" dependencies: os-tmpdir "~1.0.2" +tough-cookie@~2.3.0: + version "2.3.4" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" + dependencies: + punycode "^1.4.1" + tough-cookie@~2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561" dependencies: punycode "^1.4.1" +tree-kill@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.0.tgz#5846786237b4239014f05db156b643212d4c6f36" + trim-newlines@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" @@ -1538,21 +1847,18 @@ typescript@2.5.2: version "2.5.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.5.2.tgz#038a95f7d9bbb420b1bf35ba31d4c5c1dd3ffe34" -urix@^0.1.0, urix@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - -url@~0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" - dependencies: - punycode "1.3.2" - querystring "0.2.0" +uid-number@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" +uuid@^3.0.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14" + uuid@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" @@ -1564,10 +1870,6 @@ validate-npm-package-license@^3.0.1: spdx-correct "~1.0.0" spdx-expression-parse "~1.0.0" -validator@~9.1.1: - version "9.1.1" - resolved "https://registry.yarnpkg.com/validator/-/validator-9.1.1.tgz#3bdd1065cbd28f9d96ac806dee01030d32fd97ef" - verror@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" @@ -1576,53 +1878,23 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" -wdio-dot-reporter@~0.0.8: - version "0.0.9" - resolved "https://registry.yarnpkg.com/wdio-dot-reporter/-/wdio-dot-reporter-0.0.9.tgz#929b2adafd49d6b0534fda068e87319b47e38fe5" - -webdriverio@^4.8.0: - version "4.9.8" - resolved "https://registry.yarnpkg.com/webdriverio/-/webdriverio-4.9.8.tgz#907180e715d3b9e16cabe20bad59854bec1e44fa" - dependencies: - archiver "~2.1.0" - babel-runtime "^6.26.0" - css-parse "~2.0.0" - css-value "~0.0.1" - deepmerge "~2.0.1" - ejs "~2.5.6" - gaze "~1.1.2" - glob "~7.1.1" - inquirer "~3.3.0" - json-stringify-safe "~5.0.1" - mkdirp "~0.5.1" - npm-install-package "~2.1.0" - optimist "~0.6.1" - q "~1.5.0" - request "~2.83.0" - rgb2hex "~0.1.0" - safe-buffer "~5.1.1" - supports-color "~5.0.0" - url "~0.11.0" - validator "~9.1.1" - wdio-dot-reporter "~0.0.8" - wgxpath "~1.0.0" - -wgxpath@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/wgxpath/-/wgxpath-1.0.0.tgz#eef8a4b9d558cc495ad3a9a2b751597ecd9af690" +watch@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/watch/-/watch-1.0.2.tgz#340a717bde765726fa0aa07d721e0147a551df0c" + dependencies: + exec-sh "^0.2.0" + minimist "^1.2.0" -wordwrap@~0.0.2: - version "0.0.3" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" +wide-align@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710" + dependencies: + string-width "^1.0.2" wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" -xtend@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" - xtend@~2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-2.1.2.tgz#6efecc2a4dad8e6962c4901b337ce7ba87b5d28b" @@ -1634,12 +1906,3 @@ yauzl@2.4.1: resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.4.1.tgz#9528f442dab1b2284e58b4379bb194e22e0c4005" dependencies: fd-slicer "~1.0.1" - -zip-stream@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-1.2.0.tgz#a8bc45f4c1b49699c6b90198baacaacdbcd4ba04" - dependencies: - archiver-utils "^1.3.0" - compress-commons "^1.2.0" - lodash "^4.8.0" - readable-stream "^2.0.0" diff --git a/tslint.json b/tslint.json index 9a59614556ed9..6e26e8166d0ed 100644 --- a/tslint.json +++ b/tslint.json @@ -442,6 +442,13 @@ "*" ] }, + { + "target": "**/test/smoke2/**", + "restrictions": [ + "**/test/smoke2/**", + "*" + ] + }, { "target": "{**/**.test.ts,**/test/**}", "restrictions": "{**/vs/**,assert,sinon,crypto}"