mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-07 01:25:38 -05:00
Merge VS Code 1.23.1 (#1520)
This commit is contained in:
146
test/smoke/src/application.ts
Normal file
146
test/smoke/src/application.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. 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';
|
||||
import { Logger } from './logger';
|
||||
|
||||
export enum Quality {
|
||||
Dev,
|
||||
Insiders,
|
||||
Stable
|
||||
}
|
||||
|
||||
export interface ApplicationOptions extends SpawnOptions {
|
||||
quality: Quality;
|
||||
workspacePath: string;
|
||||
workspaceFilePath: string;
|
||||
waitTime: number;
|
||||
}
|
||||
|
||||
export class Application {
|
||||
|
||||
private _code: Code | undefined;
|
||||
private _workbench: Workbench;
|
||||
private keybindings: any[];
|
||||
|
||||
constructor(private options: ApplicationOptions) { }
|
||||
|
||||
get quality(): Quality {
|
||||
return this.options.quality;
|
||||
}
|
||||
|
||||
get code(): Code {
|
||||
return this._code!;
|
||||
}
|
||||
|
||||
get workbench(): Workbench {
|
||||
return this._workbench;
|
||||
}
|
||||
|
||||
get logger(): Logger {
|
||||
return this.options.logger;
|
||||
}
|
||||
|
||||
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(): Promise<any> {
|
||||
await this._start();
|
||||
await this.code.waitForElement('.explorer-folders-view');
|
||||
await this.code.waitForActiveElement(`.editor-container[id="workbench.editor.walkThroughPart"] > div > div[tabIndex="0"]`);
|
||||
}
|
||||
|
||||
async restart(options: { workspaceOrFolder?: string, extraArgs?: string[] }): Promise<any> {
|
||||
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<any> {
|
||||
await this.retrieveKeybindings();
|
||||
cp.execSync('git checkout .', { cwd: this.options.workspacePath });
|
||||
await this.startApplication(workspaceOrFolder, extraArgs);
|
||||
await this.checkWindowReady();
|
||||
}
|
||||
|
||||
async reload(): Promise<any> {
|
||||
this.code.reload()
|
||||
.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<any> {
|
||||
if (this._code) {
|
||||
this._code.dispose();
|
||||
this._code = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async capturePage(): Promise<string> {
|
||||
return this.code.capturePage();
|
||||
}
|
||||
|
||||
private async startApplication(workspaceOrFolder: string, extraArgs: string[] = []): Promise<any> {
|
||||
this._code = await spawn({
|
||||
codePath: this.options.codePath,
|
||||
workspacePath: workspaceOrFolder,
|
||||
userDataDir: this.options.userDataDir,
|
||||
extensionsPath: this.options.extensionsPath,
|
||||
logger: this.options.logger,
|
||||
extraArgs
|
||||
});
|
||||
|
||||
this._workbench = new Workbench(this._code, this.keybindings, this.userDataPath);
|
||||
}
|
||||
|
||||
private async checkWindowReady(): Promise<any> {
|
||||
if (!this.code) {
|
||||
console.error('No code instance found');
|
||||
return;
|
||||
}
|
||||
|
||||
await this.code.waitForWindowIds(ids => ids.length > 0);
|
||||
await this.code.waitForElement('.monaco-workbench');
|
||||
|
||||
// wait a bit, since focus might be stolen off widgets
|
||||
// as soon as they open (eg quick open)
|
||||
await new Promise(c => setTimeout(c, 500));
|
||||
}
|
||||
|
||||
private retrieveKeybindings(): Promise<void> {
|
||||
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}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,7 @@
|
||||
* Licensed under the Source EULA. 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<Element> {
|
||||
async waitForActivityBar(position: ActivityBarPosition): Promise<void> {
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
@@ -3,55 +3,44 @@
|
||||
* Licensed under the Source EULA. 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;
|
||||
// settings might take a while to update?
|
||||
this.timeout(40000);
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -3,13 +3,16 @@
|
||||
* Licensed under the Source EULA. 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`;
|
||||
@@ -20,152 +23,120 @@ const BREAKPOINT_GLYPH = '.debug-breakpoint';
|
||||
const PAUSE = `.debug-actions-widget .debug-action.pause`;
|
||||
const DEBUG_STATUS_BAR = `.statusbar.debugging`;
|
||||
const NOT_DEBUG_STATUS_BAR = `.statusbar:not(debugging)`;
|
||||
const TOOLBAR_HIDDEN = `.debug-actions-widget.builder-hidden`;
|
||||
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<any> {
|
||||
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<any> {
|
||||
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<any> {
|
||||
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<number> {
|
||||
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<any> {
|
||||
await this.spectron.client.waitAndClick(STEP_OVER);
|
||||
await this.code.waitAndClick(STEP_OVER);
|
||||
}
|
||||
|
||||
async stepIn(): Promise<any> {
|
||||
await this.spectron.client.waitAndClick(STEP_IN);
|
||||
await this.code.waitAndClick(STEP_IN);
|
||||
}
|
||||
|
||||
async stepOut(): Promise<any> {
|
||||
await this.spectron.client.waitAndClick(STEP_OUT);
|
||||
await this.code.waitAndClick(STEP_OUT);
|
||||
}
|
||||
|
||||
async continue(): Promise<any> {
|
||||
await this.spectron.client.waitAndClick(CONTINUE);
|
||||
await this.code.waitAndClick(CONTINUE);
|
||||
await this.waitForStackFrameLength(0);
|
||||
}
|
||||
|
||||
async stopDebugging(): Promise<any> {
|
||||
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<IStackFrame> {
|
||||
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<any> {
|
||||
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<any> {
|
||||
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<void> {
|
||||
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);
|
||||
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 getLocalVariableCount(): Promise<number> {
|
||||
return await this.spectron.webclient.selectorExecute(VARIABLE, div => (Array.isArray(div) ? div : [div]).length);
|
||||
async waitForVariableCount(count: number): Promise<void> {
|
||||
await this.code.waitForElements(VARIABLE, false, els => els.length === count);
|
||||
}
|
||||
|
||||
async getStackFramesLength(): Promise<number> {
|
||||
const stackFrames = await this.getStackFrames();
|
||||
return stackFrames.length;
|
||||
}
|
||||
|
||||
private async getStackFrames(): Promise<IStackFrame[]> {
|
||||
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<string[]> {
|
||||
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<string[]> {
|
||||
const elements = await this.code.waitForElements(CONSOLE_OUTPUT, false, elements => fn(elements.map(e => e.textContent)));
|
||||
return elements.map(e => e.textContent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,27 +3,23 @@
|
||||
* Licensed under the Source EULA. 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);
|
||||
const references = await app.workbench.editor.findReferences('www', 'app', 7);
|
||||
|
||||
await references.waitForReferencesCountInTitle(3);
|
||||
await references.waitForReferencesCount(3);
|
||||
@@ -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,19 +50,19 @@ 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.editor.gotoDefinition('app.js', '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);
|
||||
const peek = await app.workbench.editor.peekDefinition('app.js', 'express', 11);
|
||||
|
||||
await peek.waitForFile('index.d.ts');
|
||||
});
|
||||
|
||||
@@ -3,192 +3,131 @@
|
||||
* Licensed under the Source EULA. 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}"]`;
|
||||
const VIEW_LINES = filename => `${EDITOR(filename)} .view-lines`;
|
||||
const LINE_NUMBERS = filename => `${EDITOR(filename)} .margin .margin-view-overlays .line-numbers`;
|
||||
|
||||
export class Editor {
|
||||
|
||||
private static readonly VIEW_LINES = '.monaco-editor .view-lines';
|
||||
private static readonly LINE_NUMBERS = '.monaco-editor .margin .margin-view-overlays .line-numbers';
|
||||
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) {
|
||||
}
|
||||
constructor(private code: Code, private commands: Commands) { }
|
||||
|
||||
async openOutline(): Promise<QuickOutline> {
|
||||
const outline = new QuickOutline(this.spectron);
|
||||
await outline.open();
|
||||
return outline;
|
||||
}
|
||||
|
||||
async findReferences(term: string, line: number): Promise<References> {
|
||||
await this.clickOnTerm(term, line);
|
||||
await this.spectron.workbench.quickopen.runCommand('Find All References');
|
||||
const references = new References(this.spectron);
|
||||
async findReferences(filename: string, term: string, line: number): Promise<References> {
|
||||
await this.clickOnTerm(filename, term, line);
|
||||
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<void> {
|
||||
await this.clickOnTerm(from, line);
|
||||
await this.spectron.workbench.quickopen.runCommand('Rename Symbol');
|
||||
await this.clickOnTerm(filename, from, line);
|
||||
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<void> {
|
||||
await this.clickOnTerm(term, line);
|
||||
await this.spectron.workbench.quickopen.runCommand('Go to Definition');
|
||||
async gotoDefinition(filename: string, term: string, line: number): Promise<void> {
|
||||
await this.clickOnTerm(filename, term, line);
|
||||
await this.commands.runCommand('Go to Definition');
|
||||
}
|
||||
|
||||
async peekDefinition(term: string, line: number): Promise<References> {
|
||||
await this.clickOnTerm(term, line);
|
||||
await this.spectron.workbench.quickopen.runCommand('Peek Definition');
|
||||
const peek = new References(this.spectron);
|
||||
async peekDefinition(filename: string, term: string, line: number): Promise<References> {
|
||||
await this.clickOnTerm(filename, term, line);
|
||||
await this.commands.runCommand('Peek Definition');
|
||||
const peek = new References(this.code);
|
||||
await peek.waitUntilOpen();
|
||||
return peek;
|
||||
}
|
||||
|
||||
async waitForHighlightingLine(line: number): Promise<void> {
|
||||
const currentLineIndex = await this.getViewLineIndex(line);
|
||||
async waitForHighlightingLine(filename: string, line: number): Promise<void> {
|
||||
const currentLineIndex = await this.getViewLineIndex(filename, 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);
|
||||
}
|
||||
|
||||
async getSelector(term: string, line: number): Promise<string> {
|
||||
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');
|
||||
return `${Editor.VIEW_LINES}>:nth-child(${lineIndex}) span span.${classNames[0]}`;
|
||||
private async getSelector(filename: string, term: string, line: number): Promise<string> {
|
||||
const lineIndex = await this.getViewLineIndex(filename, line);
|
||||
const classNames = await this.getClassSelectors(filename, term, lineIndex);
|
||||
|
||||
return `${VIEW_LINES(filename)}>:nth-child(${lineIndex}) span span.${classNames[0]}`;
|
||||
}
|
||||
|
||||
async foldAtLine(line: number): Promise<any> {
|
||||
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));
|
||||
async foldAtLine(filename: string, line: number): Promise<any> {
|
||||
const lineIndex = await this.getViewLineIndex(filename, line);
|
||||
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<any> {
|
||||
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 unfoldAtLine(filename: string, line: number): Promise<any> {
|
||||
const lineIndex = await this.getViewLineIndex(filename, line);
|
||||
await this.code.waitAndClick(Editor.FOLDING_COLLAPSED.replace('${INDEX}', '' + lineIndex));
|
||||
await this.code.waitForElement(Editor.FOLDING_EXPANDED.replace('${INDEX}', '' + lineIndex));
|
||||
}
|
||||
|
||||
async waitUntilHidden(line: number): Promise<void> {
|
||||
await this.spectron.client.waitFor<number>(() => this.getViewLineIndexWithoutWait(line), lineNumber => lineNumber === undefined, 'Waiting until line number is hidden');
|
||||
private async clickOnTerm(filename: string, term: string, line: number): Promise<void> {
|
||||
const selector = await this.getSelector(filename, term, line);
|
||||
await this.code.waitAndClick(selector);
|
||||
}
|
||||
|
||||
async waitUntilShown(line: number): Promise<void> {
|
||||
await this.getViewLineIndex(line);
|
||||
}
|
||||
async waitForEditorFocus(filename: string, lineNumber: number, selectorPrefix = ''): Promise<void> {
|
||||
const editor = [selectorPrefix || '', EDITOR(filename)].join(' ');
|
||||
const line = `${editor} .view-lines > .view-line:nth-child(${lineNumber})`;
|
||||
const textarea = `${editor} textarea`;
|
||||
|
||||
async clickOnTerm(term: string, line: number): Promise<void> {
|
||||
const selector = await this.getSelector(term, line);
|
||||
await this.spectron.client.waitAndClick(selector);
|
||||
await this.code.waitAndClick(line, 0, 0);
|
||||
await this.code.waitForActiveElement(textarea);
|
||||
}
|
||||
|
||||
async waitForTypeInEditor(filename: string, text: string, selectorPrefix = ''): Promise<any> {
|
||||
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<any> {
|
||||
const selector = [
|
||||
selectorPrefix || '',
|
||||
`.monaco-editor[data-uri$="${filename}"] .view-lines`
|
||||
].join(' ');
|
||||
|
||||
return this.spectron.client.waitForTextContent(selector, undefined, c => accept(c.replace(/\u00a0/g, ' ')));
|
||||
const selector = [selectorPrefix || '', `${EDITOR(filename)} .view-lines`].join(' ');
|
||||
return this.code.waitForTextContent(selector, undefined, c => accept(c.replace(/\u00a0/g, ' ')));
|
||||
}
|
||||
|
||||
async waitForActiveEditor(filename: string): Promise<any> {
|
||||
const selector = `.editor-container .monaco-editor[data-uri$="${filename}"] textarea`;
|
||||
return this.spectron.client.waitForActiveElement(selector);
|
||||
private async getClassSelectors(filename: string, term: string, viewline: number): Promise<string[]> {
|
||||
const elements = await this.code.waitForElements(`${VIEW_LINES(filename)}>: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);
|
||||
}
|
||||
|
||||
// async waitForActiveEditorFirstLineText(filename: string): Promise<string> {
|
||||
// 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;
|
||||
// }
|
||||
private async getViewLineIndex(filename: string, line: number): Promise<number> {
|
||||
const elements = await this.code.waitForElements(LINE_NUMBERS(filename), false, els => {
|
||||
return els.some(el => el.textContent === `${line}`);
|
||||
});
|
||||
|
||||
// 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<string[]> {
|
||||
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);
|
||||
}
|
||||
|
||||
private async getViewLineIndex(line: number): Promise<number> {
|
||||
return await this.spectron.client.waitFor<number>(() => this.getViewLineIndexWithoutWait(line), void 0, 'Getting line index');
|
||||
}
|
||||
|
||||
private async getViewLineIndexWithoutWait(line: number): Promise<number | undefined> {
|
||||
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');
|
||||
}
|
||||
}
|
||||
44
test/smoke/src/areas/editor/editors.ts
Normal file
44
test/smoke/src/areas/editor/editors.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. 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<any> {
|
||||
await this.commands.runCommand('workbench.action.files.save');
|
||||
}
|
||||
|
||||
async selectTab(tabName: string, untitled: boolean = false): Promise<void> {
|
||||
await this.code.waitAndClick(`.tabs-container div.tab[aria-label="${tabName}, tab"]`);
|
||||
await this.waitForEditorFocus(tabName, untitled);
|
||||
}
|
||||
|
||||
async waitForActiveEditor(filename: string): Promise<any> {
|
||||
const selector = `.editor-container .monaco-editor[data-uri$="${filename}"] textarea`;
|
||||
return this.code.waitForActiveElement(selector);
|
||||
}
|
||||
|
||||
async waitForEditorFocus(fileName: string, untitled: boolean = false): Promise<void> {
|
||||
await this.waitForActiveTab(fileName);
|
||||
await this.waitForActiveEditor(fileName);
|
||||
}
|
||||
|
||||
async waitForActiveTab(fileName: string, isDirty: boolean = false): Promise<void> {
|
||||
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<void> {
|
||||
await this.code.waitForElement(`.tabs-container div.tab${isDirty ? '.dirty' : ''}[aria-label="${fileName}, tab"]`);
|
||||
}
|
||||
|
||||
async newUntitledFile(): Promise<void> {
|
||||
await this.commands.runCommand('workbench.action.files.newUntitledFile');
|
||||
await this.waitForEditorFocus('Untitled-1', true);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. 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,41 @@ 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) { }
|
||||
|
||||
async waitUntilOpen(): Promise<void> {
|
||||
await this.code.waitForElement(References.REFERENCES_WIDGET);
|
||||
}
|
||||
|
||||
public async waitUntilOpen(): Promise<void> {
|
||||
await this.spectron.client.waitForElement(References.REFERENCES_WIDGET);
|
||||
}
|
||||
|
||||
public async waitForReferencesCountInTitle(count: number): Promise<void> {
|
||||
await this.spectron.client.waitForText(References.REFERENCES_TITLE_COUNT, void 0, titleCount => {
|
||||
async waitForReferencesCountInTitle(count: number): Promise<void> {
|
||||
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<void> {
|
||||
await this.spectron.client.waitForElements(References.REFERENCES, result => result && result.length === count);
|
||||
async waitForReferencesCount(count: number): Promise<void> {
|
||||
await this.code.waitForElements(References.REFERENCES, false, result => result && result.length === count);
|
||||
}
|
||||
|
||||
public async waitForFile(file: string): Promise<void> {
|
||||
await this.spectron.client.waitForText(References.REFERENCES_TITLE_FILE_NAME, file);
|
||||
async waitForFile(file: string): Promise<void> {
|
||||
await this.code.waitForTextContent(References.REFERENCES_TITLE_FILE_NAME, file);
|
||||
}
|
||||
|
||||
public async close(): Promise<void> {
|
||||
await this.spectron.client.keys(['Escape', 'NULL']);
|
||||
await this.spectron.client.waitForElement(References.REFERENCES_WIDGET, element => !element);
|
||||
async close(): Promise<void> {
|
||||
// Sometimes someone else eats up the `Escape` key
|
||||
let count = 0;
|
||||
while (true) {
|
||||
await this.code.dispatchKeybinding('escape');
|
||||
|
||||
try {
|
||||
await this.code.waitForElement(References.REFERENCES_WIDGET, el => !el, 10);
|
||||
return;
|
||||
} catch (err) {
|
||||
if (++count > 5) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. 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<void> {
|
||||
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');
|
||||
}
|
||||
}
|
||||
@@ -3,16 +3,12 @@
|
||||
* Licensed under the Source EULA. 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');
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -3,33 +3,34 @@
|
||||
* Licensed under the Source EULA. 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<any> {
|
||||
return this.spectron.runCommand('workbench.view.explorer');
|
||||
openExplorerView(): Promise<any> {
|
||||
return this.commands.runCommand('workbench.view.explorer');
|
||||
}
|
||||
|
||||
public getOpenEditorsViewTitle(): Promise<string> {
|
||||
return this.spectron.client.waitForText(Explorer.OPEN_EDITORS_VIEW);
|
||||
async waitForOpenEditorsViewTitle(fn: (title: string) => boolean): Promise<void> {
|
||||
await this.code.waitForTextContent(Explorer.OPEN_EDITORS_VIEW, undefined, fn);
|
||||
}
|
||||
|
||||
public async openFile(fileName: string): Promise<any> {
|
||||
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<any> {
|
||||
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';
|
||||
|
||||
@@ -3,17 +3,12 @@
|
||||
* Licensed under the Source EULA. 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.extensions.openExtensionsViewlet();
|
||||
await app.workbench.runCommand('Smoke Test Check');
|
||||
await app.workbench.statusbar.waitForStatusbarText('smoke test', 'VS Code Smoke Test Check');
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -3,40 +3,32 @@
|
||||
* Licensed under the Source EULA. 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<any> {
|
||||
await this.spectron.runCommand('workbench.view.extensions');
|
||||
await this.waitForExtensionsViewlet();
|
||||
}
|
||||
|
||||
async waitForExtensionsViewlet(): Promise<any> {
|
||||
await this.spectron.client.waitForActiveElement(SEARCH_BOX);
|
||||
await this.commands.runCommand('workbench.view.extensions');
|
||||
await this.code.waitForActiveElement(SEARCH_BOX);
|
||||
}
|
||||
|
||||
async searchForExtension(name: string): Promise<any> {
|
||||
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:"${name}"`);
|
||||
}
|
||||
|
||||
async installExtension(name: string): Promise<boolean> {
|
||||
async installExtension(name: string): Promise<void> {
|
||||
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`);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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"]';
|
||||
@@ -12,39 +12,40 @@ const SYNC_STATUSBAR = 'div[id="workbench.parts.statusbar"] .statusbar-entry a[t
|
||||
export function setup() {
|
||||
describe('Git', () => {
|
||||
before(async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
app.suiteName = 'Git';
|
||||
const app = this.app as Application;
|
||||
|
||||
cp.execSync('git config user.name testuser', { cwd: app.workspacePath });
|
||||
cp.execSync('git config user.email monacotools@microsoft.com', { cwd: app.workspacePath });
|
||||
});
|
||||
|
||||
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 +59,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 +68,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 });
|
||||
});
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
* Licensed under the Source EULA. 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<any> {
|
||||
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<void> {
|
||||
return this.spectron.client.waitFor(async () => {
|
||||
const changes = await this.queryChanges(name, type);
|
||||
return changes.length;
|
||||
}, l => l > 0, 'Getting SCM changes') as Promise<any> as Promise<void>;
|
||||
async waitForChange(name: string, type?: string): Promise<void> {
|
||||
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<any> {
|
||||
await this.spectron.client.click(REFRESH_COMMAND);
|
||||
}
|
||||
|
||||
private async queryChanges(name: string, type?: string): Promise<Change[]> {
|
||||
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<void> {
|
||||
await this.spectron.client.waitAndClick(SCM_RESOURCE_CLICK(name));
|
||||
await this.code.waitAndClick(SCM_RESOURCE_CLICK(name));
|
||||
}
|
||||
|
||||
async stage(name: string): Promise<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -3,16 +3,13 @@
|
||||
* Licensed under the Source EULA. 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));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -3,26 +3,28 @@
|
||||
* Licensed under the Source EULA. 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<any> {
|
||||
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<any> {
|
||||
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}."]`);
|
||||
}
|
||||
}
|
||||
@@ -3,45 +3,34 @@
|
||||
* Licensed under the Source EULA. 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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
@@ -13,33 +16,28 @@ export enum ActivityBarPosition {
|
||||
}
|
||||
|
||||
const SEARCH_INPUT = '.settings-search-input input';
|
||||
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<void> {
|
||||
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.editor.waitForEditorFocus('settings.json', 1, '.editable-preferences-editor-container');
|
||||
|
||||
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<void> {
|
||||
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');
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,8 @@
|
||||
* Licensed under the Source EULA. 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<any> {
|
||||
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<any> {
|
||||
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<boolean> {
|
||||
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<void> {
|
||||
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-b-warning' : 'squiggly-c-error';
|
||||
let selector = problemType === ProblemSeverity.WARNING ? 'squiggly-warning' : 'squiggly-error';
|
||||
return `.view-overlays .cdr.${selector}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,86 +3,110 @@
|
||||
* Licensed under the Source EULA. 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 {
|
||||
|
||||
static QUICK_OPEN_HIDDEN = 'div.quick-open-widget[aria-hidden="true"]';
|
||||
static QUICK_OPEN = 'div.quick-open-widget[aria-hidden="false"]';
|
||||
static QUICK_OPEN_HIDDEN = 'div.monaco-quick-open-widget[aria-hidden="true"]';
|
||||
static QUICK_OPEN = 'div.monaco-quick-open-widget[aria-hidden="false"]';
|
||||
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<void> {
|
||||
await this.spectron.runCommand('workbench.action.quickOpen');
|
||||
await this.waitForQuickOpenOpened();
|
||||
let retries = 0;
|
||||
|
||||
// other parts of code might steal focus away from quickopen :(
|
||||
while (retries < 5) {
|
||||
await this.commands.runCommand('workbench.action.quickOpen');
|
||||
|
||||
try {
|
||||
await this.waitForQuickOpenOpened(10);
|
||||
break;
|
||||
} catch (err) {
|
||||
if (++retries > 5) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
await this.code.dispatchKeybinding('escape');
|
||||
}
|
||||
}
|
||||
|
||||
if (value) {
|
||||
await this.spectron.client.setValue(QuickOpen.QUICK_OPEN_INPUT, value);
|
||||
await this.code.waitForSetValue(QuickOpen.QUICK_OPEN_INPUT, value);
|
||||
}
|
||||
}
|
||||
|
||||
async closeQuickOpen(): Promise<void> {
|
||||
await this.spectron.runCommand('workbench.action.closeQuickOpen');
|
||||
await this.commands.runCommand('workbench.action.closeQuickOpen');
|
||||
await this.waitForQuickOpenClosed();
|
||||
}
|
||||
|
||||
async openFile(fileName: string): Promise<void> {
|
||||
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);
|
||||
await this.waitForQuickOpenElements(names => names[0] === fileName);
|
||||
await this.code.dispatchKeybinding('enter');
|
||||
await this.editors.waitForActiveTab(fileName);
|
||||
await this.editors.waitForEditorFocus(fileName);
|
||||
}
|
||||
|
||||
async runCommand(commandText: string): Promise<void> {
|
||||
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);
|
||||
}
|
||||
|
||||
async waitForQuickOpenOpened(): Promise<void> {
|
||||
await this.spectron.client.waitForActiveElement(QuickOpen.QUICK_OPEN_INPUT);
|
||||
async waitForQuickOpenOpened(retryCount?: number): Promise<void> {
|
||||
await this.code.waitForActiveElement(QuickOpen.QUICK_OPEN_INPUT, retryCount);
|
||||
}
|
||||
|
||||
private async waitForQuickOpenClosed(): Promise<void> {
|
||||
await this.spectron.client.waitForElement(QuickOpen.QUICK_OPEN_HIDDEN);
|
||||
await this.code.waitForElement(QuickOpen.QUICK_OPEN_HIDDEN);
|
||||
}
|
||||
|
||||
async submit(text: string): Promise<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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)));
|
||||
}
|
||||
|
||||
private async getQuickOpenElements(): Promise<string[]> {
|
||||
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 runCommand(command: string): Promise<void> {
|
||||
await this.openQuickOpen(`> ${command}`);
|
||||
|
||||
return Array.isArray(result) ? result : [];
|
||||
// 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);
|
||||
}
|
||||
|
||||
async openQuickOutline(): Promise<void> {
|
||||
let retries = 0;
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,16 +3,12 @@
|
||||
* Licensed under the Source EULA. 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,27 +28,25 @@ 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();
|
||||
await app.workbench.search.setReplaceText('ydob');
|
||||
await app.workbench.search.replaceFileMatch(1);
|
||||
await app.workbench.saveOpenedFile();
|
||||
|
||||
await app.workbench.search.waitForResultText('10 results in 4 files');
|
||||
|
||||
await app.workbench.search.searchFor('ydob');
|
||||
await app.workbench.search.setReplaceText('body');
|
||||
await app.workbench.search.replaceFileMatch(1);
|
||||
await app.workbench.saveOpenedFile();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -3,85 +3,77 @@
|
||||
* Licensed under the Source EULA. 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`;
|
||||
const INCLUDE_INPUT = `${VIEWLET} .query-details .monaco-inputbox input[aria-label="Search Include Patterns"]`;
|
||||
const INCLUDE_INPUT = `${VIEWLET} .query-details .file-types.includes .monaco-inputbox input`;
|
||||
|
||||
export class Search extends Viewlet {
|
||||
|
||||
constructor(spectron: SpectronApplication) {
|
||||
super(spectron);
|
||||
constructor(code: Code, private commands: Commands) {
|
||||
super(code);
|
||||
}
|
||||
|
||||
async openSearchViewlet(): Promise<any> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
if (!await this.areDetailsVisible()) {
|
||||
await this.spectron.client.waitAndClick(`${VIEWLET} .query-details .more`);
|
||||
}
|
||||
await this.code.waitAndClick(`${VIEWLET} .query-details .more`);
|
||||
}
|
||||
|
||||
async hideQueryDetails(): Promise<void> {
|
||||
if (await this.areDetailsVisible()) {
|
||||
await this.spectron.client.waitAndClick(`${VIEWLET} .query-details.more .more`);
|
||||
}
|
||||
}
|
||||
|
||||
async areDetailsVisible(): Promise<boolean> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,19 +3,13 @@
|
||||
* Licensed under the Source EULA. 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);
|
||||
@@ -81,11 +75,11 @@ export function setup() {
|
||||
await app.workbench.quickopen.waitForQuickOpenOpened();
|
||||
|
||||
await app.workbench.quickopen.submit(':15');
|
||||
await app.workbench.editor.waitForHighlightingLine(15);
|
||||
await app.workbench.editor.waitForHighlightingLine('app.js', 15);
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. 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) { }
|
||||
|
||||
async waitForStatusbarElement(element: StatusBarElement): Promise<void> {
|
||||
await this.code.waitForElement(this.getSelector(element));
|
||||
}
|
||||
|
||||
public async waitForStatusbarElement(element: StatusBarElement): Promise<void> {
|
||||
await this.spectron.client.waitForElement(this.getSelector(element));
|
||||
async clickOn(element: StatusBarElement): Promise<void> {
|
||||
await this.code.waitAndClick(this.getSelector(element));
|
||||
}
|
||||
|
||||
public async clickOn(element: StatusBarElement): Promise<void> {
|
||||
await this.spectron.client.waitAndClick(this.getSelector(element));
|
||||
async waitForEOL(eol: string): Promise<string> {
|
||||
return this.code.waitForTextContent(this.getSelector(StatusBarElement.EOL_STATUS), eol);
|
||||
}
|
||||
|
||||
public async waitForEOL(eol: string): Promise<string> {
|
||||
return this.spectron.client.waitForText(this.getSelector(StatusBarElement.EOL_STATUS), eol);
|
||||
}
|
||||
|
||||
public async getStatusbarTextByTitle(title: string): Promise<string> {
|
||||
return await this.spectron.client.waitForText(`${this.mainSelector} span[title="smoke test"]`);
|
||||
async waitForStatusbarText(title: string, text: string): Promise<void> {
|
||||
await this.code.waitForTextContent(`${this.mainSelector} span[title="${title}"]`, text);
|
||||
}
|
||||
|
||||
private getSelector(element: StatusBarElement): string {
|
||||
@@ -61,7 +60,7 @@ export class StatusBar {
|
||||
case StatusBarElement.LANGUAGE_STATUS:
|
||||
return `${this.mainSelector} ${this.rightSelector} .editor-status-mode`;
|
||||
case StatusBarElement.FEEDBACK_ICON:
|
||||
return `${this.mainSelector} ${this.rightSelector} .dropdown.send-feedback`;
|
||||
return `${this.mainSelector} ${this.rightSelector} .monaco-dropdown.send-feedback`;
|
||||
default:
|
||||
throw new Error(element);
|
||||
}
|
||||
|
||||
@@ -3,27 +3,24 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// import { SpectronApplication } from '../../spectron/application';
|
||||
// import { Application } from '../../application';
|
||||
|
||||
describe('Terminal', () => {
|
||||
// let app: SpectronApplication;
|
||||
// before(() => { app = new SpectronApplication(); return app.start('Terminal'); });
|
||||
// after(() => app.stop());
|
||||
export function setup() {
|
||||
describe('Terminal', () => {
|
||||
// it(`opens terminal, runs 'echo' and verifies the output`, async function () {
|
||||
// const app = this.app as Application;
|
||||
|
||||
// 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;
|
||||
// });
|
||||
// });
|
||||
});
|
||||
// const expected = new Date().getTime().toString();
|
||||
// await app.workbench.terminal.showTerminal();
|
||||
// await app.workbench.terminal.runCommand(`echo ${expected}`);
|
||||
// await app.workbench.terminal.waitForTerminalText(terminalText => {
|
||||
// for (let index = terminalText.length - 2; index >= 0; index--) {
|
||||
// if (!!terminalText[index] && terminalText[index].trim() === expected) {
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// });
|
||||
// });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,59 +3,29 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../../spectron/application';
|
||||
import { Code } from '../../vscode/code';
|
||||
import { Commands } from '../workbench/workbench';
|
||||
|
||||
const PANEL_SELECTOR = 'div[id="workbench.panel.terminal"]';
|
||||
const XTERM_SELECTOR = `${PANEL_SELECTOR} .terminal-wrapper`;
|
||||
const XTERM_TEXTAREA = `${XTERM_SELECTOR} textarea.xterm-helper-textarea`;
|
||||
|
||||
export class Terminal {
|
||||
|
||||
constructor(private spectron: SpectronApplication) { }
|
||||
constructor(private code: Code, private commands: Commands) { }
|
||||
|
||||
async showTerminal(): Promise<void> {
|
||||
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<boolean> {
|
||||
const element = await this.spectron.client.element(PANEL_SELECTOR);
|
||||
return !!element;
|
||||
await this.commands.runCommand('workbench.action.terminal.toggleTerminal');
|
||||
await this.code.waitForActiveElement(XTERM_TEXTAREA);
|
||||
await this.code.waitForTerminalBuffer(XTERM_SELECTOR, lines => lines.some(line => line.length > 0));
|
||||
}
|
||||
|
||||
async runCommand(commandText: string): Promise<void> {
|
||||
// TODO@Tyriar fix this. we should not use type but setValue
|
||||
// await this.spectron.client.type(commandText);
|
||||
await this.spectron.client.keys(['Enter', 'NULL']);
|
||||
await this.code.waitForPaste(XTERM_TEXTAREA, commandText);
|
||||
await this.code.dispatchKeybinding('enter');
|
||||
}
|
||||
|
||||
async waitForTerminalText(fn: (text: string[]) => boolean, timeOutDescription: string = 'Getting Terminal Text'): Promise<string[]> {
|
||||
return this.spectron.client.waitFor(async () => {
|
||||
const terminalText = await this.getTerminalText();
|
||||
if (fn(terminalText)) {
|
||||
return terminalText;
|
||||
}
|
||||
return undefined;
|
||||
}, void 0, timeOutDescription);
|
||||
}
|
||||
|
||||
getCurrentLineNumber(): Promise<number> {
|
||||
return this.getTerminalText().then(text => text.length);
|
||||
}
|
||||
|
||||
private async getTerminalText(): Promise<string[]> {
|
||||
return await this.spectron.webclient.selectorExecute(XTERM_SELECTOR,
|
||||
div => {
|
||||
const xterm = (<any>(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;
|
||||
}
|
||||
);
|
||||
async waitForTerminalText(accept: (buffer: string[]) => boolean): Promise<void> {
|
||||
await this.code.waitForTerminalBuffer(XTERM_SELECTOR, accept);
|
||||
}
|
||||
}
|
||||
@@ -3,39 +3,30 @@
|
||||
* Licensed under the Source EULA. 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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,130 +3,109 @@
|
||||
* Licensed under the Source EULA. 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);
|
||||
// if (!stableApp) {
|
||||
// this.skip();
|
||||
// return;
|
||||
// }
|
||||
|
||||
await app.stop();
|
||||
});
|
||||
// await stableApp.start();
|
||||
|
||||
it('checks if opened tabs are restored migrating from stable to latest', async function () {
|
||||
const stableApp = createApp(Quality.Stable);
|
||||
// const fileName1 = 'app.js', fileName2 = 'jsconfig.json', fileName3 = 'readme.md';
|
||||
|
||||
if (!stableApp) {
|
||||
this.skip();
|
||||
return;
|
||||
}
|
||||
// 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();
|
||||
|
||||
await stableApp.start();
|
||||
stableApp.suiteName = 'Data Migration';
|
||||
// const app = createApp(Quality.Insiders);
|
||||
|
||||
const fileName1 = 'app.js', fileName2 = 'jsconfig.json', fileName3 = 'readme.md';
|
||||
// await app.start(false);
|
||||
|
||||
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.workbench.editors.waitForTab(fileName1);
|
||||
// await app.workbench.editors.waitForTab(fileName2);
|
||||
// await app.workbench.editors.waitForTab(fileName3);
|
||||
|
||||
const app = createApp(Quality.Insiders);
|
||||
|
||||
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();
|
||||
// });
|
||||
});
|
||||
}
|
||||
@@ -3,15 +3,12 @@
|
||||
* Licensed under the Source EULA. 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));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -3,16 +3,15 @@
|
||||
* Licensed under the Source EULA. 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<string> {
|
||||
return this.spectron.client.waitForText('.monaco-workbench-container .part.sidebar > .title > .title-label > span');
|
||||
async waitForTitle(fn: (title: string) => boolean): Promise<void> {
|
||||
await this.code.waitForTextContent('.monaco-workbench-container .part.sidebar > .title > .title-label > span', undefined, fn);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
* Licensed under the Source EULA. 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,20 @@ import { StatusBar } from '../statusbar/statusbar';
|
||||
import { Problems } from '../problems/problems';
|
||||
import { SettingsEditor } from '../preferences/settings';
|
||||
import { KeybindingsEditor } from '../preferences/keybindings';
|
||||
import { Editors } from '../editor/editors';
|
||||
import { Code } from '../../vscode/code';
|
||||
import { Terminal } from '../terminal/terminal';
|
||||
|
||||
export class Workbench {
|
||||
export interface Commands {
|
||||
runCommand(command: string): Promise<any>;
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -34,47 +40,35 @@ export class Workbench {
|
||||
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);
|
||||
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);
|
||||
this.terminal = new Terminal(code, this);
|
||||
}
|
||||
|
||||
public async saveOpenedFile(): Promise<any> {
|
||||
await this.spectron.client.waitForElement('.tabs-container div.tab.active.dirty');
|
||||
await this.spectron.workbench.quickopen.runCommand('File: Save');
|
||||
}
|
||||
/**
|
||||
* 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<void> {
|
||||
const binding = this.keybindings.find(x => x['command'] === command);
|
||||
|
||||
public async selectTab(tabName: string, untitled: boolean = false): Promise<void> {
|
||||
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<void> {
|
||||
await this.waitForActiveTab(fileName);
|
||||
await this.editor.waitForActiveEditor(fileName);
|
||||
}
|
||||
|
||||
public async waitForActiveTab(fileName: string, isDirty: boolean = false): Promise<any> {
|
||||
return this.spectron.client.waitForElement(`.tabs-container div.tab.active${isDirty ? '.dirty' : ''}[aria-selected="true"][aria-label="${fileName}, tab"]`);
|
||||
}
|
||||
|
||||
public async waitForTab(fileName: string, isDirty: boolean = false): Promise<boolean> {
|
||||
return this.spectron.client.waitForElement(`.tabs-container div.tab${isDirty ? '.dirty' : ''}[aria-label="${fileName}, tab"]`).then(() => true);
|
||||
}
|
||||
|
||||
public async newUntitledFile(): Promise<void> {
|
||||
await this.spectron.runCommand('workbench.action.files.newUntitledFile');
|
||||
await this.waitForEditorFocus('Untitled-1', true);
|
||||
if (binding) {
|
||||
await this.code.dispatchKeybinding(binding.key);
|
||||
} else {
|
||||
await this.quickopen.runCommand(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. 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<void> {
|
||||
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()));
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import { dirname } from 'path';
|
||||
|
||||
export function nfcall<R>(fn: Function, ...args): Promise<R> {
|
||||
return new Promise<R>((c, e) => fn(...args, (err, r) => err ? e(err) : c(r)));
|
||||
}
|
||||
|
||||
export async function mkdirp(path: string, mode?: number): Promise<boolean> {
|
||||
const mkdir = async () => {
|
||||
try {
|
||||
await nfcall(fs.mkdir, path, mode);
|
||||
} catch (err) {
|
||||
if (err.code === 'EEXIST') {
|
||||
const stat = await nfcall<fs.Stats>(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, '');
|
||||
}
|
||||
42
test/smoke/src/logger.ts
Normal file
42
test/smoke/src/logger.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { appendFileSync, writeFileSync } from 'fs';
|
||||
import { format } from 'util';
|
||||
import { EOL } from 'os';
|
||||
|
||||
export interface Logger {
|
||||
log(message: string, ...args: any[]): void;
|
||||
}
|
||||
|
||||
export class ConsoleLogger implements Logger {
|
||||
|
||||
log(message: string, ...args: any[]): void {
|
||||
console.log('**', message, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
export class FileLogger implements Logger {
|
||||
|
||||
constructor(private path: string) {
|
||||
writeFileSync(path, '');
|
||||
}
|
||||
|
||||
log(message: string, ...args: any[]): void {
|
||||
const date = new Date().toISOString();
|
||||
appendFileSync(this.path, `[${date}] ${format(message, ...args)}${EOL}`);
|
||||
}
|
||||
}
|
||||
|
||||
export class MultiLogger implements Logger {
|
||||
|
||||
constructor(private loggers: Logger[]) { }
|
||||
|
||||
log(message: string, ...args: any[]): void {
|
||||
for (const logger of this.loggers) {
|
||||
logger.log(message, ...args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
@@ -24,9 +24,10 @@ import { setup as setupDataDebugTests } from './areas/debug/debug.test';
|
||||
import { setup as setupDataGitTests } from './areas/git/git.test';
|
||||
import { setup as setupDataStatusbarTests } from './areas/statusbar/statusbar.test';
|
||||
import { setup as setupDataExtensionTests } from './areas/extensions/extensions.test';
|
||||
import { setup as setupTerminalTests } from './areas/terminal/terminal.test';
|
||||
import { setup as setupDataMultirootTests } from './areas/multiroot/multiroot.test';
|
||||
import { setup as setupDataLocalizationTests } from './areas/workbench/localization.test';
|
||||
// import './areas/terminal/terminal.test';
|
||||
import { MultiLogger, Logger, ConsoleLogger, FileLogger } from './logger';
|
||||
|
||||
const tmpDir = tmp.dirSync({ prefix: 't' }) as { name: string; removeCallback: Function; };
|
||||
const testDataPath = tmpDir.name;
|
||||
@@ -37,13 +38,20 @@ const opts = minimist(args, {
|
||||
string: [
|
||||
'build',
|
||||
'stable-build',
|
||||
'log',
|
||||
'wait-time'
|
||||
]
|
||||
'wait-time',
|
||||
'test-repo',
|
||||
'keybindings',
|
||||
'screenshots',
|
||||
'log'
|
||||
],
|
||||
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');
|
||||
@@ -51,6 +59,12 @@ const keybindingsPath = path.join(testDataPath, 'keybindings.json');
|
||||
const extensionsPath = path.join(testDataPath, 'extensions-dir');
|
||||
mkdirp.sync(extensionsPath);
|
||||
|
||||
const screenshotsPath = opts.screenshots ? path.resolve(opts.screenshots) : null;
|
||||
|
||||
if (screenshotsPath) {
|
||||
mkdirp.sync(screenshotsPath);
|
||||
}
|
||||
|
||||
function fail(errorMessage): void {
|
||||
console.error(errorMessage);
|
||||
process.exit(1);
|
||||
@@ -96,16 +110,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 +161,106 @@ function toUri(path: string): string {
|
||||
return `${path}`;
|
||||
}
|
||||
|
||||
async function getKeybindings(): Promise<void> {
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function createWorkspaceFile(): Promise<void> {
|
||||
if (fs.existsSync(workspaceFilePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
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<void> {
|
||||
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 {
|
||||
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<void> {
|
||||
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);
|
||||
});
|
||||
|
||||
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'));
|
||||
}
|
||||
|
||||
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' });
|
||||
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): Application {
|
||||
const loggers: Logger[] = [];
|
||||
|
||||
function createApp(quality: Quality): SpectronApplication | null {
|
||||
const path = quality === Quality.Stable ? stablePath : electronPath;
|
||||
|
||||
if (!path) {
|
||||
return null;
|
||||
if (opts.verbose) {
|
||||
loggers.push(new ConsoleLogger());
|
||||
}
|
||||
|
||||
return new SpectronApplication({
|
||||
if (opts.log) {
|
||||
loggers.push(new FileLogger(opts.log));
|
||||
}
|
||||
|
||||
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,
|
||||
logger: new MultiLogger(loggers)
|
||||
});
|
||||
}
|
||||
|
||||
before(async function () {
|
||||
// allow two minutes for setup
|
||||
this.timeout(2 * 60 * 1000);
|
||||
@@ -244,6 +268,7 @@ before(async function () {
|
||||
});
|
||||
|
||||
after(async function () {
|
||||
await new Promise(c => setTimeout(c, 500)); // wait for shutdown
|
||||
await new Promise((c, e) => rimraf(testDataPath, { maxBusyTries: 10 }, err => err ? e(err) : c()));
|
||||
});
|
||||
|
||||
@@ -251,7 +276,7 @@ describe('Data Migration', () => {
|
||||
setupDataMigrationTests(userDataDir, createApp);
|
||||
});
|
||||
|
||||
describe('Everything Else', () => {
|
||||
describe('Test', () => {
|
||||
before(async function () {
|
||||
const app = createApp(quality);
|
||||
await app!.start();
|
||||
@@ -262,6 +287,36 @@ describe('Everything Else', () => {
|
||||
await this.app.stop();
|
||||
});
|
||||
|
||||
if (screenshotsPath) {
|
||||
afterEach(async function () {
|
||||
if (this.currentTest.state !== 'failed') {
|
||||
return;
|
||||
}
|
||||
|
||||
const app = this.app as Application;
|
||||
const raw = await app.capturePage();
|
||||
const buffer = new Buffer(raw, 'base64');
|
||||
|
||||
const name = this.currentTest.fullTitle().replace(/[^a-z0-9\-]/ig, '_');
|
||||
const screenshotPath = path.join(screenshotsPath, `${name}.png`);
|
||||
|
||||
if (opts.log) {
|
||||
app.logger.log('*** Screenshot recorded:', screenshotPath);
|
||||
}
|
||||
|
||||
fs.writeFileSync(screenshotPath, buffer);
|
||||
});
|
||||
}
|
||||
|
||||
if (opts.log) {
|
||||
beforeEach(async function () {
|
||||
const app = this.app as Application;
|
||||
const title = this.currentTest.fullTitle();
|
||||
|
||||
app.logger.log('*** Test start:', title);
|
||||
});
|
||||
}
|
||||
|
||||
setupDataLossTests();
|
||||
setupDataExplorerTests();
|
||||
setupDataPreferencesTests();
|
||||
@@ -272,6 +327,7 @@ describe('Everything Else', () => {
|
||||
setupDataGitTests();
|
||||
setupDataStatusbarTests();
|
||||
setupDataExtensionTests();
|
||||
setupTerminalTests();
|
||||
setupDataMultirootTests();
|
||||
setupDataLocalizationTests();
|
||||
});
|
||||
|
||||
@@ -1,351 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. 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<number> {
|
||||
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<void>) | 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<any> {
|
||||
await this._start();
|
||||
|
||||
if (waitForWelcome) {
|
||||
await this.waitForWelcome();
|
||||
}
|
||||
}
|
||||
|
||||
async restart(options: { workspaceOrFolder?: string, extraArgs?: string[] }): Promise<any> {
|
||||
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<any> {
|
||||
await this.retrieveKeybindings();
|
||||
cp.execSync('git checkout .', { cwd: this.options.workspacePath });
|
||||
await this.startApplication(workspaceOrFolder, extraArgs);
|
||||
await this.checkWindowReady();
|
||||
}
|
||||
|
||||
async reload(): Promise<any> {
|
||||
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<any> {
|
||||
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<any> {
|
||||
|
||||
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<any> {
|
||||
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<any> {
|
||||
await this.client.waitForElement('.explorer-folders-view');
|
||||
await this.client.waitForElement(`.editor-container[id="workbench.editor.walkThroughPart"] .welcomePage`);
|
||||
}
|
||||
|
||||
private retrieveKeybindings(): Promise<void> {
|
||||
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<any> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. 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<void> {
|
||||
this.spectron.client.keys(keys);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
async getText(selector: string, capture: boolean = true): Promise<any> {
|
||||
return this.spectron.client.getText(selector);
|
||||
}
|
||||
|
||||
async waitForText(selector: string, text?: string, accept?: (result: string) => boolean): Promise<string> {
|
||||
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<string> {
|
||||
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<any> {
|
||||
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<any> {
|
||||
return this.waitFor(() => this.spectron.client.click(selector), void 0, `click with selector ${selector}`);
|
||||
}
|
||||
|
||||
async click(selector: string): Promise<any> {
|
||||
return this.spectron.client.click(selector);
|
||||
}
|
||||
|
||||
async doubleClickAndWait(selector: string, capture: boolean = true): Promise<any> {
|
||||
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<any> {
|
||||
return this.spectron.client.leftClick(selector, xoffset, yoffset);
|
||||
}
|
||||
|
||||
async rightClick(selector: string, capture: boolean = true): Promise<any> {
|
||||
return this.spectron.client.rightClick(selector);
|
||||
}
|
||||
|
||||
async moveToObject(selector: string, capture: boolean = true): Promise<any> {
|
||||
return this.spectron.client.moveToObject(selector);
|
||||
}
|
||||
|
||||
async waitAndMoveToObject(selector: string): Promise<any> {
|
||||
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<any> {
|
||||
return this.spectron.client.setValue(selector, text);
|
||||
}
|
||||
|
||||
async waitForElements(selector: string, accept: (result: Element[]) => boolean = result => result.length > 0): Promise<Element[]> {
|
||||
return this.waitFor<RawResult<Element[]>>(() => 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<Element> {
|
||||
return this.waitFor<RawResult<Element>>(() => 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<any> {
|
||||
return this.waitFor(() => this.spectron.client.isVisible(selector), accept, `isVisible with selector ${selector}`);
|
||||
}
|
||||
|
||||
async element(selector: string): Promise<Element> {
|
||||
return this.spectron.client.element(selector)
|
||||
.then(result => result.value);
|
||||
}
|
||||
|
||||
async waitForActiveElement(selector: string): Promise<any> {
|
||||
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<string> {
|
||||
return this.waitFor<string>(() => this.spectron.client.getAttribute(selector), accept, `attribute with selector ${selector}`);
|
||||
}
|
||||
|
||||
async dragAndDrop(sourceElem: string, destinationElem: string, capture: boolean = true): Promise<any> {
|
||||
return this.spectron.client.dragAndDrop(sourceElem, destinationElem);
|
||||
}
|
||||
|
||||
async selectByValue(selector: string, value: string, capture: boolean = true): Promise<any> {
|
||||
return this.spectron.client.selectByValue(selector, value);
|
||||
}
|
||||
|
||||
async getValue(selector: string, capture: boolean = true): Promise<any> {
|
||||
return this.spectron.client.getValue(selector);
|
||||
}
|
||||
|
||||
async getAttribute(selector: string, attribute: string, capture: boolean = true): Promise<any> {
|
||||
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<any> {
|
||||
return this.spectron.client.isVisible(selector);
|
||||
}
|
||||
|
||||
async getTitle(): Promise<string> {
|
||||
return this.spectron.client.getTitle();
|
||||
}
|
||||
|
||||
private running = false;
|
||||
async waitFor<T>(func: () => T | Promise<T | undefined>, accept?: (result: T) => boolean | Promise<boolean>, timeoutMessage?: string, retryCount?: number): Promise<T>;
|
||||
async waitFor<T>(func: () => T | Promise<T>, accept: (result: T) => boolean | Promise<boolean> = result => !!result, timeoutMessage?: string, retryCount?: number): Promise<T> {
|
||||
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<any> {
|
||||
// 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);
|
||||
// });
|
||||
// }
|
||||
}
|
||||
324
test/smoke/src/vscode/code.ts
Normal file
324
test/smoke/src/vscode/code.ts
Normal file
@@ -0,0 +1,324 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as path from 'path';
|
||||
import * as cp from 'child_process';
|
||||
import * as os from 'os';
|
||||
import { tmpName } from 'tmp';
|
||||
import { IDriver, connect as connectDriver, IDisposable, IElement } from './driver';
|
||||
import { Logger } from '../logger';
|
||||
|
||||
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, logger: Logger): Promise<Code> {
|
||||
let errCount = 0;
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
const { client, driver } = await connectDriver(outPath, handlePath);
|
||||
return new Code(child, client, driver, logger);
|
||||
} 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<cp.ChildProcess>();
|
||||
process.once('exit', () => instances.forEach(code => code.kill()));
|
||||
|
||||
export interface SpawnOptions {
|
||||
codePath?: string;
|
||||
workspacePath: string;
|
||||
userDataDir: string;
|
||||
extensionsPath: string;
|
||||
logger: Logger;
|
||||
extraArgs?: string[];
|
||||
}
|
||||
|
||||
async function createDriverHandle(): Promise<string> {
|
||||
if ('win32' === os.platform()) {
|
||||
const name = [...Array(15)].map(() => Math.random().toString(36)[3]).join('');
|
||||
return `\\\\.\\pipe\\${name}`;
|
||||
} else {
|
||||
return await new Promise<string>((c, e) => tmpName((err, handlePath) => err ? e(err) : c(handlePath)));
|
||||
}
|
||||
}
|
||||
|
||||
export async function spawn(options: SpawnOptions): Promise<Code> {
|
||||
const codePath = options.codePath;
|
||||
const electronPath = codePath ? getBuildElectronPath(codePath) : getDevElectronPath();
|
||||
const outPath = codePath ? getBuildOutPath(codePath) : getDevOutPath();
|
||||
const handle = await createDriverHandle();
|
||||
|
||||
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', handle
|
||||
];
|
||||
|
||||
if (!codePath) {
|
||||
args.unshift(repoPath);
|
||||
}
|
||||
|
||||
if (options.extraArgs) {
|
||||
args.push(...options.extraArgs);
|
||||
}
|
||||
|
||||
const spawnOptions: cp.SpawnOptions = {};
|
||||
|
||||
const child = cp.spawn(electronPath, args, spawnOptions);
|
||||
|
||||
instances.add(child);
|
||||
child.once('exit', () => instances.delete(child));
|
||||
|
||||
return connect(child, outPath, handle, options.logger);
|
||||
}
|
||||
|
||||
async function poll<T>(
|
||||
fn: () => Promise<T>,
|
||||
acceptFn: (result: T) => boolean,
|
||||
timeoutMessage: string,
|
||||
retryCount: number = 200,
|
||||
retryInterval: number = 100 // millis
|
||||
): Promise<T> {
|
||||
let trial = 1;
|
||||
|
||||
while (true) {
|
||||
if (trial > retryCount) {
|
||||
throw new Error(`Timeout: ${timeoutMessage} after ${(retryCount * retryInterval) / 1000} seconds.`);
|
||||
}
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = await fn();
|
||||
|
||||
if (acceptFn(result)) {
|
||||
return result;
|
||||
}
|
||||
} catch (e) {
|
||||
// console.warn(e);
|
||||
|
||||
if (/Method not implemented/.test(e.message)) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, retryInterval));
|
||||
trial++;
|
||||
}
|
||||
}
|
||||
|
||||
export class Code {
|
||||
|
||||
private _activeWindowId: number | undefined = undefined;
|
||||
private driver: IDriver;
|
||||
|
||||
constructor(
|
||||
private process: cp.ChildProcess,
|
||||
private client: IDisposable,
|
||||
driver: IDriver,
|
||||
readonly logger: Logger
|
||||
) {
|
||||
this.driver = new Proxy(driver, {
|
||||
get(target, prop, receiver) {
|
||||
if (typeof target[prop] !== 'function') {
|
||||
return target[prop];
|
||||
}
|
||||
|
||||
return function (...args) {
|
||||
logger.log(`${prop}`, ...args.filter(a => typeof a === 'string'));
|
||||
return target[prop].apply(this, args);
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async capturePage(): Promise<string> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
return await this.driver.capturePage(windowId);
|
||||
}
|
||||
|
||||
async waitForWindowIds(fn: (windowIds: number[]) => boolean): Promise<void> {
|
||||
await poll(() => this.driver.getWindowIds(), fn, `get window ids`);
|
||||
}
|
||||
|
||||
async dispatchKeybinding(keybinding: string): Promise<void> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
await this.driver.dispatchKeybinding(windowId, keybinding);
|
||||
}
|
||||
|
||||
async reload(): Promise<void> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
await this.driver.reloadWindow(windowId);
|
||||
}
|
||||
|
||||
async waitForTextContent(selector: string, textContent?: string, accept?: (result: string) => boolean): Promise<string> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
accept = accept || (result => textContent !== void 0 ? textContent === result : !!result);
|
||||
return await 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<void> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
await poll(() => this.driver.click(windowId, selector, xoffset, yoffset), () => true, `click '${selector}'`);
|
||||
}
|
||||
|
||||
async waitAndDoubleClick(selector: string): Promise<void> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
await poll(() => this.driver.doubleClick(windowId, selector), () => true, `double click '${selector}'`);
|
||||
}
|
||||
|
||||
async waitAndMove(selector: string): Promise<void> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
await poll(() => this.driver.move(windowId, selector), () => true, `move '${selector}'`);
|
||||
}
|
||||
|
||||
async waitForSetValue(selector: string, value: string): Promise<void> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
await poll(() => this.driver.setValue(windowId, selector, value), () => true, `set value '${selector}'`);
|
||||
}
|
||||
|
||||
async waitForPaste(selector: string, value: string): Promise<void> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
await poll(() => this.driver.paste(windowId, selector, value), () => true, `paste '${selector}'`);
|
||||
}
|
||||
|
||||
async waitForElements(selector: string, recursive: boolean, accept: (result: IElement[]) => boolean = result => result.length > 0): Promise<IElement[]> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
return await poll(() => this.driver.getElements(windowId, selector, recursive), accept, `get elements '${selector}'`);
|
||||
}
|
||||
|
||||
async waitForElement(selector: string, accept: (result: IElement | undefined) => boolean = result => !!result, retryCount: number = 200): Promise<IElement> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
return await poll<IElement>(() => this.driver.getElements(windowId, selector).then(els => els[0]), accept, `get element '${selector}'`, retryCount);
|
||||
}
|
||||
|
||||
async waitForActiveElement(selector: string, retryCount: number = 200): Promise<void> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
await poll(() => this.driver.isActiveElement(windowId, selector), r => r, `is active element '${selector}'`, retryCount);
|
||||
}
|
||||
|
||||
async waitForTitle(fn: (title: string) => boolean): Promise<void> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
await poll(() => this.driver.getTitle(windowId), fn, `get title`);
|
||||
}
|
||||
|
||||
async waitForTypeInEditor(selector: string, text: string): Promise<void> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
await poll(() => this.driver.typeInEditor(windowId, selector, text), () => true, `type in editor '${selector}'`);
|
||||
}
|
||||
|
||||
async waitForTerminalBuffer(selector: string, accept: (result: string[]) => boolean): Promise<void> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
await poll(() => this.driver.getTerminalBuffer(windowId, selector), accept, `get terminal buffer '${selector}'`);
|
||||
}
|
||||
|
||||
private async getActiveWindowId(): Promise<number> {
|
||||
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;
|
||||
}
|
||||
12
test/smoke/src/vscode/driver.js
Normal file
12
test/smoke/src/vscode/driver.js
Normal file
@@ -0,0 +1,12 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. 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));
|
||||
};
|
||||
Reference in New Issue
Block a user