SQL Operations Studio Public Preview 1 (0.23) release source code

This commit is contained in:
Karl Burtram
2017-11-09 14:30:27 -08:00
parent b88ecb8d93
commit 3cdac41339
8829 changed files with 759707 additions and 286 deletions

View File

@@ -0,0 +1,188 @@
/*---------------------------------------------------------------------------------------------
* 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 { Util } from '../helpers/utilities';
/**
* Contains methods that are commonly used across test areas.
*/
export class CommonActions {
private util: Util;
constructor(private spectron: SpectronApplication) {
this.util = new Util();
}
public async getWindowTitle(): Promise<any> {
return this.spectron.client.getTitle();
}
public enter(): Promise<any> {
return this.spectron.client.keys(['Enter', 'NULL']);
}
public async addSetting(setting: string, value: string): Promise<any> {
await this.spectron.command('workbench.action.openGlobalSettings');
await this.spectron.wait();
await this.spectron.client.keys(['ArrowDown', 'NULL', 'ArrowRight', 'NULL'], false);
await this.spectron.client.keys(`"${setting}": "${value}"`);
await this.spectron.wait();
return this.saveOpenedFile();
}
public async newUntitledFile(): Promise<any> {
await this.spectron.command('workbench.action.files.newUntitledFile');
return this.spectron.wait();
}
public closeTab(): Promise<any> {
return this.spectron.client.keys(['Control', 'w', 'NULL']);
}
public async getTab(tabName: string, active?: boolean): Promise<any> {
await this.closeCurrentNotification(); // close any notification messages that could overlap tabs
let tabSelector = active ? '.tab.active' : 'div';
let el = await this.spectron.client.element(`.tabs-container ${tabSelector}[aria-label="${tabName}, tab"]`);
if (el.status === 0) {
return el;
}
return undefined;
}
public async selectTab(tabName: string): Promise<any> {
await this.closeCurrentNotification(); // close any notification messages that could overlap tabs
return this.spectron.client.click(`.tabs-container div[aria-label="${tabName}, tab"]`);
}
public async openFirstMatchFile(fileName: string): Promise<any> {
await this.openQuickOpen();
await this.type(fileName);
await this.spectron.wait();
await this.enter();
return this.spectron.wait();
}
public saveOpenedFile(): Promise<any> {
return this.spectron.command('workbench.action.files.save');
}
public type(text: string): Promise<any> {
let spectron = this.spectron;
return new Promise(function (res) {
let textSplit = text.split(' ');
async function type(i: number) {
if (!textSplit[i] || textSplit[i].length <= 0) {
return res();
}
const toType = textSplit[i + 1] ? `${textSplit[i]} ` : textSplit[i];
await spectron.client.keys(toType, false);
await spectron.client.keys(['NULL']);
await type(i + 1);
}
return type(0);
});
}
public showCommands(): Promise<any> {
return this.spectron.command('workbench.action.showCommands');
}
public openQuickOpen(): Promise<any> {
return this.spectron.command('workbench.action.quickOpen');
}
public closeQuickOpen(): Promise<any> {
return this.spectron.command('workbench.action.closeQuickOpen');
}
public selectNextQuickOpenElement(): Promise<any> {
return this.spectron.client.keys(['ArrowDown', 'NULL']);
}
public async getQuickOpenElements(): Promise<number> {
const elements = await this.spectron.waitFor(this.spectron.client.elements, 'div[aria-label="Quick Picker"] .monaco-tree-rows.show-twisties .monaco-tree-row');
return elements.value.length;
}
public async openFile(fileName: string, explorer?: boolean): Promise<any> {
let selector = `div[class="monaco-icon-label file-icon ${fileName}-name-file-icon ${this.getExtensionSelector(fileName)}`;
if (explorer) {
selector += ' explorer-item';
}
selector += '"]';
try {
await this.spectron.waitFor(this.spectron.client.doubleClick, selector);
} catch (e) {
return Promise.reject(`Cannot fine ${fileName} in a viewlet.`);
}
return this.spectron.wait();
}
public getExtensionSelector(fileName: string): string {
const extension = fileName.split('.')[1];
if (extension === 'js') {
return 'js-ext-file-icon javascript-lang-file-icon';
} else if (extension === 'json') {
return 'json-ext-file-icon json-lang-file-icon';
} else if (extension === 'md') {
return 'md-ext-file-icon markdown-lang-file-icon';
}
throw new Error('No class defined for this file extension');
}
public async getEditorFirstLinePlainText(): Promise<any> {
const trials = 3;
let retry = 0;
let error;
while (retry < trials) {
try {
const span = await this.spectron.client.getText('.view-lines span span');
if (Array.isArray(span)) {
return span[0];
}
return span;
} catch (e) {
error = e;
retry++;
if (retry < trials) {
await this.spectron.wait();
} else {
error = e;
}
}
}
return Promise.reject('Could not obtain text on the first line of an editor: ' + error);
}
public removeFile(filePath: string): void {
this.util.removeFile(filePath);
}
public removeDirectory(directory: string): Promise<any> {
try {
return this.util.rimraf(directory);
} catch (e) {
throw new Error(`Failed to remove ${directory} with an error: ${e}`);
}
}
private closeCurrentNotification(): Promise<any> {
return this.spectron.command('workbench.action.closeMessages');
}
}

View File

@@ -0,0 +1,64 @@
/*---------------------------------------------------------------------------------------------
* 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';
export enum ActivityBarPosition {
LEFT = 0,
RIGHT = 1
};
export class ConfigurationView {
// Stores key binding defined for the toggle of activity bar position
private keybinding: string[];
constructor(private spectron: SpectronApplication) {
// noop
}
public async getEditorLineNumbers(): Promise<any> {
const lineNumbers = await this.spectron.client.elements('.line-numbers');
return lineNumbers.value.length;
}
public enterKeybindingsView(): any {
return this.spectron.command('workbench.action.openGlobalKeybindings');
}
public selectFirstKeybindingsMatch(): any {
return this.spectron.waitFor(this.spectron.client.click, 'div[aria-label="Keybindings"] .monaco-list-row.keybinding-item');
}
public changeKeybinding(): any {
return this.spectron.command('editor.action.defineKeybinding');
}
public enterBinding(keys: string[]): any {
this.keybinding = keys;
return this.spectron.client.keys(keys);
}
public toggleActivityBarPosition(): any {
return this.spectron.client.keys(this.keybinding);
}
public async getActivityBar(position: ActivityBarPosition) {
let positionClass: string;
if (position === ActivityBarPosition.LEFT) {
positionClass = 'left';
} else if (position === ActivityBarPosition.RIGHT) {
positionClass = 'right';
} else {
throw new Error('No such position for activity bar defined.');
}
try {
return await this.spectron.waitFor(this.spectron.client.getHTML, `.part.activitybar.${positionClass}`);
} catch (e) {
return undefined;
};
}
}

View File

@@ -0,0 +1,62 @@
/*---------------------------------------------------------------------------------------------
* 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';
export enum CSSProblem {
WARNING = 0,
ERROR = 1
};
export class CSS {
constructor(private spectron: SpectronApplication) {
// noop
}
public openQuickOutline(): any {
return this.spectron.command('workbench.action.gotoSymbol');
}
public toggleProblemsView(): any {
return this.spectron.command('workbench.actions.view.problems');
}
public async getEditorProblem(problemType: CSSProblem): Promise<any> {
let selector;
if (problemType === CSSProblem.WARNING) {
selector = 'greensquiggly';
} else if (problemType === CSSProblem.ERROR) {
selector = 'redsquiggly';
} else {
throw new Error('No such problem type defined.');
}
let el = await this.spectron.client.element(`.view-overlays .cdr.${selector}`);
if (el.status === 0) {
return el;
}
return undefined;
}
public async getProblemsViewsProblem(problemType: CSSProblem): Promise<any> {
let selector;
if (problemType === CSSProblem.WARNING) {
selector = 'warning';
} else if (problemType === CSSProblem.ERROR) {
selector = 'error';
} else {
throw new Error('No such problem type defined.');
}
let el = await this.spectron.client.element(`div[aria-label="Problems grouped by files"] .icon.${selector}`);
if (el.status === 0) {
return el;
}
return undefined;
}
}

View File

@@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* 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';
export class DataLoss {
constructor(private spectron: SpectronApplication) {
}
public openExplorerViewlet(): Promise<any> {
return this.spectron.command('workbench.view.explorer');
}
public async verifyTabIsDirty(tabName: string, active?: boolean): Promise<any> {
let activeSelector = active ? '.active' : '';
let el = await this.spectron.client.element(`.tabs-container .tab.dirty${activeSelector}[aria-label="${tabName}, tab"]`);
if (el.status === 0) {
return el;
}
return undefined;
}
}

View File

@@ -0,0 +1,112 @@
/*---------------------------------------------------------------------------------------------
* 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 { CommonActions } from './common';
var htmlparser = require('htmlparser2');
export class Extensions {
private readonly extensionsViewletSelector = 'div[id="workbench.view.extensions"]';
private viewletExtensionIndex: number;
constructor(private spectron: SpectronApplication, private common: CommonActions) {
}
public async openExtensionsViewlet(): Promise<any> {
await this.spectron.command('workbench.view.extensions');
return this.spectron.wait();
}
public async searchForExtension(name: string): Promise<any> {
const searchBoxSelector = `${this.extensionsViewletSelector} .search-box`;
await this.spectron.client.clearElement(searchBoxSelector);
try {
await this.spectron.client.click(searchBoxSelector, false);
} catch (e) {
return Promise.reject('Failed to click on search box in extensions viewlet.');
}
await this.spectron.client.keys(name);
return this.spectron.client.keys(['NULL', 'Enter', 'NULL']);
}
public async installExtension(name: string): Promise<any> {
const extensionListSelector = `${this.extensionsViewletSelector} .monaco-list-rows`;
this.viewletExtensionIndex = await this.getExtensionIndex(name, extensionListSelector);
try {
return this.spectron.client.click(`${extensionListSelector}>:nth-child(${this.viewletExtensionIndex}) .extension .extension-action.install`);
} catch (e) {
return Promise.reject('Failed to click on install button for selected extension.');
}
}
public getExtensionReloadText(): Promise<any> {
try {
return this.spectron.waitFor(this.spectron.client.getText, `${this.extensionsViewletSelector} .monaco-list-rows>:nth-child(${this.viewletExtensionIndex}) .extension .extension-action.reload`);
} catch (e) {
return Promise.reject('Reload was not prompted for an installed extension.');
}
}
public async activateExtension(): Promise<any> {
await this.common.showCommands();
await this.common.type('Smoke Test Check');
await this.spectron.wait();
return this.common.enter();
}
public verifyStatusbarItem(): Promise<any> {
try {
return this.spectron.waitFor(this.spectron.client.getText, '.statusbar-item.statusbar-entry span[title="smoke test"]');
} catch (e) {
return Promise.reject('Failed to validate extension contribution.');
}
}
private getExtensionIndex(name: string, extensionListSelector: string): Promise<number> {
return this.spectron.waitFor(this.spectron.client.getHTML, extensionListSelector).then(html => {
return new Promise<number>((res, rej) => {
let extensionIndex: number = 0;
let extension: boolean;
let tags: string[] = [];
let parser = new htmlparser.Parser({
onopentag: function (name, attribs) {
if (name === 'div' && attribs.class === 'extension') {
extensionIndex++;
extension = true;
}
if (extension) {
tags.push(name);
}
},
ontext: function (text) {
if (extension && text === name) {
parser.end();
}
},
onclosetag: function (name) {
if (extension) {
tags.pop();
}
if (extension && tags.length === 0) {
extension = false;
}
},
onend: function () {
if (extensionIndex === 0) {
return rej(`${name} extension was not found.`);
}
return res(extensionIndex);
}
});
parser.write(html);
});
});
}
}

View File

@@ -0,0 +1,21 @@
/*---------------------------------------------------------------------------------------------
* 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';
export class FirstExperience {
constructor(private spectron: SpectronApplication) {
// noop
}
public async getWelcomeTab(): Promise<any> {
let el = await this.spectron.client.element('.vs_code_welcome_page-name-file-icon');
if (el.status === 0) {
return el;
}
return undefined;
}
}

167
test/smoke/src/areas/git.ts Normal file
View File

@@ -0,0 +1,167 @@
/*---------------------------------------------------------------------------------------------
* 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 { CommonActions } from './common';
var htmlparser = require('htmlparser2');
export class Git {
private editorChangeIndex: number;
constructor(private spectron: SpectronApplication, private commonActions: CommonActions) {
// noop
}
public openGitViewlet(): Promise<any> {
return this.spectron.command('workbench.view.scm');
}
public getScmIconChanges(): Promise<any> {
return this.spectron.waitFor(this.spectron.client.getText, 'div[id="workbench.parts.activitybar"] .badge.scm-viewlet-label .badge-content');
}
public async verifyScmChange(fileName: string): Promise<any> {
let el;
try {
el = await this.spectron.client.element(`div[class="monaco-icon-label file-icon ${fileName}-name-file-icon ${this.commonActions.getExtensionSelector(fileName)}"]`);
} catch (e) {
return Promise.reject(`${fileName} change is not present in SCM viewlet.`);
}
if (el.status === 0) {
return el;
}
return undefined;
}
public async getOriginalAppJsBodyVarName(): Promise<any> {
this.editorChangeIndex = await this.getFirstChangeIndex('cdr line-delete', '.editor.original .view-overlays');
return this.spectron.waitFor(this.spectron.client.getText, `.editor.original .view-lines>:nth-child(${this.editorChangeIndex}) .mtk11`);
}
public getModifiedAppJsBodyVarName(): Promise<any> {
return this.spectron.waitFor(this.spectron.client.getText, `.editor.modified .view-lines>:nth-child(${this.editorChangeIndex}) .mtk11`);
}
public async stageFile(fileName: string): Promise<any> {
try {
await this.spectron.client.moveToObject(`div[class="monaco-icon-label file-icon ${fileName}-name-file-icon ${this.commonActions.getExtensionSelector(fileName)}"`);
} catch (e) {
return Promise.reject(`${fileName} was not found in SCM viewlet`);
}
await this.spectron.wait();
try {
await this.spectron.client.click('.action-label.icon.contrib-cmd-icon-4');
} catch (e) {
return Promise.reject('Stage button was not found');
}
return this.spectron.wait();
}
public async unstageFile(fileName: string): Promise<any> {
try {
await this.spectron.client.moveToObject(`div[class="monaco-icon-label file-icon ${fileName}-name-file-icon ${this.commonActions.getExtensionSelector(fileName)}"`);
} catch (e) {
return Promise.reject(`${fileName} was not found in SCM viewlet`);
}
try {
await this.spectron.client.click('.action-label.icon.contrib-cmd-icon-6');
} catch (e) {
return Promise.reject('Unstage button was not found.');
}
return this.spectron.wait();
}
public async getStagedCount(): Promise<any> {
let scmHeaders: Array<string>;
try {
scmHeaders = await this.spectron.waitFor(this.spectron.client.getText, '.scm-status.show-file-icons .monaco-list-rows .name'); // get all headers
}
catch (e) {
return Promise.reject('No row names in SCM viewlet were found.');
}
const stagedTitle = scmHeaders.find((val) => {
return val.match(/staged/i) ? true : false;
});
if (!stagedTitle) {
return Promise.reject(`No 'Staged' header title found in SCM viewlet`);
}
const monacoRowIndex = scmHeaders.indexOf(stagedTitle);
try {
return this.spectron.waitFor(this.spectron.client.getText, `.scm-status.show-file-icons .monaco-list-rows>:nth-child(${monacoRowIndex + 1}) .monaco-count-badge`);
} catch (e) {
return Promise.reject('Stage count badge cannot be found');
}
}
public focusOnCommitBox(): Promise<any> {
try {
return this.spectron.client.click('div[id="workbench.view.scm"] textarea');
} catch (e) {
return Promise.reject('Failed to focus on commit box: ' + e);
}
}
public async pressCommit(): Promise<any> {
try {
await this.spectron.client.click('.action-label.icon.contrib-cmd-icon-10');
} catch (e) {
return Promise.reject('Failed to press commit: ' + e);
}
return this.spectron.wait();
}
public getOutgoingChanges(): Promise<string> {
try {
return this.spectron.client.getText('a[title="Synchronize Changes"]');
} catch (e) {
return Promise.reject(`Failed to obtain 'synchronize changes' title value from the status bar.`);
}
}
private getFirstChangeIndex(changeClass: string, selector: string): Promise<number> {
return this.spectron.waitFor(this.spectron.client.getHTML, selector).then(html => {
return new Promise<number>((res, rej) => {
let lineIndex: number = 0;
let changeFound: boolean;
let tags: string[] = [];
let parser = new htmlparser.Parser({
onopentag: function (name: string, attribs: any) {
tags.push(name);
if (name === 'div' && !attribs.class) {
lineIndex++;
} else if (name === 'div' && attribs.class === changeClass) {
changeFound = true;
parser.end();
}
},
onclosetag: function (name) {
// Terminate once last tag is closed
tags.pop();
if (!changeFound && tags.length === 0) {
parser.end();
}
},
onend: function () {
if (!changeFound) {
return rej(`No changes in the diff found.`);
}
return res(lineIndex);
}
});
parser.write(html);
});
});
}
}

View File

@@ -0,0 +1,54 @@
/*---------------------------------------------------------------------------------------------
* 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 { CommonActions } from './common';
export class IntegratedTerminal {
public static terminalSelector = 'div[id="workbench.panel.terminal"]';
public static terminalRowsSelector = 'div[id="workbench.panel.terminal"] .xterm-rows';
constructor(private spectron: SpectronApplication) {
// noop
}
public async openTerminal(commonActions: CommonActions): Promise<any> {
// Backquote dispatching does not work in OS X
if (process.platform === 'darwin') {
await commonActions.showCommands();
await commonActions.type('Toggle Integrated Terminal');
return commonActions.enter();
}
await this.spectron.command('workbench.action.terminal.toggleTerminal');
// If no terminal panel was opened, try triggering terminal from quick open
try {
await this.spectron.client.getHTML(IntegratedTerminal.terminalSelector);
} catch (e) {
await commonActions.openQuickOpen();
await this.spectron.client.keys('>Toggle Integrated Terminal');
await this.spectron.client.keys(['Enter', 'NULL']);
}
}
public async commandOutputHas(result: string): Promise<boolean> {
const rows = await this.spectron.client.elements(`${IntegratedTerminal.terminalRowsSelector} div`);
for (let i = 0; i < rows.value.length; i++) {
let rowText;
try {
rowText = await this.spectron.client.getText(`${IntegratedTerminal.terminalRowsSelector}>:nth-child(${i + 1})`);
} catch (e) {
return Promise.reject(`Failed to obtain text from line ${i + 1} from the terminal.`);
}
if (rowText.trim() === result) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,53 @@
/*---------------------------------------------------------------------------------------------
* 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';
var stripJsonComments = require('strip-json-comments');
export class JavaScriptDebug {
private readonly sidebarSelector = '.margin-view-overlays';
constructor(private spectron: SpectronApplication) {
// noop
}
public openDebugViewlet(): Promise<any> {
return this.spectron.command('workbench.view.debug');
}
public async pressConfigureLaunchJson(): Promise<any> {
try {
await this.spectron.waitFor(this.spectron.client.click, 'ul[aria-label="Debug actions"] .action-label.icon.debug-action.configure');
} catch (e) {
return Promise.reject('Clicking on debug configuration gear failed.');
}
await this.spectron.wait();
await this.spectron.client.keys(['ArrowDown', 'NULL', 'Enter']);
return this.spectron.wait();
}
public async getProgramConfigValue(): Promise<any> {
const lines = stripJsonComments(await this.spectron.client.getText('.view-lines'));
const json = JSON.parse(lines);
return json.configurations[0].program;
}
public setBreakpointOnLine(lineNumber: number): Promise<any> {
try {
return this.spectron.client.leftClick(`${this.sidebarSelector}>:nth-child(${lineNumber})`, 5, 5);
} catch (e) {
return Promise.reject('Setting breakpoint failed: ' + e);
}
}
public async verifyBreakpointOnLine(lineNumber: number): Promise<any> {
let el = await this.spectron.client.element(`${this.sidebarSelector}>:nth-child(${lineNumber}) .cgmr.debug-breakpoint-glyph`);
if (el.status === 0) {
return el;
}
return undefined;
}
}

View File

@@ -0,0 +1,186 @@
/*---------------------------------------------------------------------------------------------
* 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';
var htmlparser = require('htmlparser2');
export class JavaScript {
private appVarSelector: string;
private expressVarSelector: string;
private foldSelector: string;
private foldLine: number;
constructor(private spectron: SpectronApplication) {
// noop
}
public openQuickOutline(): Promise<any> {
return this.spectron.command('workbench.action.gotoSymbol');
}
public async findAppReferences(): Promise<any> {
await this.setAppVarSelector();
try {
await this.spectron.client.click(this.appVarSelector, false);
} catch (e) {
return Promise.reject(`Failed to select 'app' variable.`);
}
return this.spectron.command('editor.action.referenceSearch.trigger');
}
public async getTitleReferencesCount(): Promise<any> {
const meta = await this.spectron.client.getText('.reference-zone-widget.results-loaded .peekview-title .meta');
return meta.match(/\d+/)[0];
}
public async getTreeReferencesCount(): Promise<any> {
const treeElems = await this.spectron.client.elements('.reference-zone-widget.results-loaded .ref-tree.inline .show-twisties .monaco-tree-row');
return treeElems.value.length;
}
public async renameApp(newValue: string): Promise<any> {
await this.setAppVarSelector();
try {
await this.spectron.client.click(this.appVarSelector);
} catch (e) {
return Promise.reject(`Failed to select 'app' variable.`);
}
await this.spectron.command('editor.action.rename');
await this.spectron.wait();
return this.spectron.client.keys(newValue, false);
}
public async getNewAppName(): Promise<any> {
return this.spectron.client.getText(this.appVarSelector);
}
public async toggleFirstCommentFold(): Promise<any> {
this.foldLine = await this.getLineIndexOfFirstFoldableElement(`.margin-view-overlays`);
this.foldSelector = `.margin-view-overlays>:nth-child(${this.foldLine})`;
try {
return this.spectron.client.click(`${this.foldSelector} .cldr.folding`);
} catch (e) {
return Promise.reject('Clicking on fold element failed ' + e);
}
}
public async getFirstCommentFoldedIcon(): Promise<any> {
if (!this.foldSelector) {
return Promise.reject('No code folding happened to be able to check for a folded icon.');
}
return this.spectron.client.getHTML(`${this.foldSelector} .cldr.folding.collapsed`);
}
public async getNextLineNumberAfterFold(): Promise<any> {
if (!this.foldLine) {
return Promise.reject('Folded line was not set, most likely because fold was not toggled initially.');
}
return this.spectron.client.getText(`.margin-view-overlays>:nth-child(${this.foldLine + 1}) .line-numbers`);
}
public async goToExpressDefinition(): Promise<any> {
await this.setExpressVarSelector();
try {
await this.spectron.client.click(this.expressVarSelector);
} catch (e) {
return Promise.reject(`Clicking on express variable failed: ` + e);
}
return this.spectron.command('editor.action.goToDeclaration');
}
public async peekExpressDefinition(): Promise<any> {
await this.setExpressVarSelector();
try {
await this.spectron.client.click(this.expressVarSelector);
} catch (e) {
return Promise.reject('Clicking on express variable failed: ' + e);
}
return this.spectron.command('editor.action.previewDeclaration');
}
public async getPeekExpressResultName(): Promise<any> {
return this.spectron.client.getText('.reference-zone-widget.results-loaded .filename');
}
private async setAppVarSelector(): Promise<any> {
if (!this.appVarSelector) {
const lineIndex = await this.getLineIndexOfFirst('app', '.view-lines');
this.appVarSelector = `.view-lines>:nth-child(${lineIndex}) .mtk11`;
}
}
private async setExpressVarSelector(): Promise<any> {
if (!this.expressVarSelector) {
const lineIndex = await this.getLineIndexOfFirst('express', '.view-lines');
this.expressVarSelector = `.view-lines>:nth-child(${lineIndex}) .mtk10`;
}
}
private getLineIndexOfFirst(string: string, selector: string): Promise<number> {
return this.spectron.waitFor(this.spectron.client.getHTML, selector).then(html => {
return new Promise<number>((res, rej) => {
let lineIndex: number = 0;
let stringFound: boolean;
let parser = new htmlparser.Parser({
onopentag: function (name: string, attribs: any) {
if (name === 'div' && attribs.class === 'view-line') {
lineIndex++;
}
},
ontext: function (text) {
if (!stringFound && text === string) {
stringFound = true;
parser.end();
}
},
onend: function () {
if (!stringFound) {
return rej(`No ${string} in editor found.`);
}
return res(lineIndex);
}
});
parser.write(html);
});
});
}
private getLineIndexOfFirstFoldableElement(selector: string): Promise<number> {
return this.spectron.waitFor(this.spectron.client.getHTML, selector).then(html => {
return new Promise<number>((res, rej) => {
let lineIndex: number = 0;
let foldFound: boolean;
let parser = new htmlparser.Parser({
onopentag: function (name: string, attribs: any) {
if (name === 'div' && !attribs.class) {
lineIndex++;
} else if (name === 'div' && attribs.class.indexOf('cldr folding') !== -1) {
foldFound = true;
parser.end();
}
},
onend: function () {
if (!foldFound) {
return rej(`No foldable elements found.`);
}
return res(lineIndex);
}
});
parser.write(html);
});
});
}
}

View File

@@ -0,0 +1,70 @@
/*---------------------------------------------------------------------------------------------
* 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';
export enum ViewletType {
SEARCH = 0,
SCM = 1,
DEBUG = 2,
EXTENSIONS = 3
}
export class Localization {
constructor(private spectron: SpectronApplication) {
// noop
}
public async getOpenEditorsText(): Promise<string> {
let explorerTitles;
try {
explorerTitles = await this.spectron.client.getText('div[id="workbench.view.explorer"] .title span');
} catch (e) {
return Promise.reject('Failed to get span of title in explorer viewlet.');
}
return explorerTitles[0];
}
public async openViewlet(type: ViewletType): Promise<any> {
let command;
switch (type) {
case ViewletType.SEARCH:
command = 'workbench.view.search';
break;
case ViewletType.SCM:
command = 'workbench.view.scm';
break;
case ViewletType.DEBUG:
command = 'workbench.view.debug';
break;
case ViewletType.EXTENSIONS:
command = 'workbench.view.extensions';
break;
}
await this.spectron.command(command, false);
return this.spectron.wait();
}
public getOpenedViewletTitle(): Promise<string> {
try {
return this.spectron.client.getText('div[id="workbench.parts.sidebar"] .title-label span');
} catch (e) {
return Promise.reject('Failed to get span of title label in explorer viewlet.');
}
}
public getExtensionsSearchPlaceholder(): Promise<string> {
try {
return this.spectron.client.getAttribute('div[id="workbench.view.extensions"] .search-box', 'placeholder');
} catch (e) {
return Promise.reject('Failed to get extension viewlet search box placeholder.');
}
}
}

View File

@@ -0,0 +1,74 @@
/*---------------------------------------------------------------------------------------------
* 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';
export class Search {
constructor(private spectron: SpectronApplication) {
// noop
}
public openSearchViewlet(): Promise<any> {
return this.spectron.command('workbench.view.search');
}
public async searchFor(text: string): Promise<any> {
await this.spectron.client.keys(text);
return this.spectron.client.keys(['NULL', 'Enter', 'NULL'], false);
}
public setReplaceText(text: string): any {
try {
return this.spectron.client.setValue('.viewlet .input[title="Replace"]', text);
} catch (e) {
return Promise.reject('Cannot set replace input in the viewlet: ' + e);
}
}
public replaceFirstMatch(): any {
try {
return this.spectron.client.click('.monaco-tree-rows.show-twisties .action-label.icon.action-replace-all');
} catch (e) {
return Promise.reject('Cannot replace the search first match: ' + e);
}
}
public getResultText(): any {
return this.spectron.waitFor(this.spectron.client.getText, '.search-viewlet .message>p');
}
public toggleSearchDetails(): any {
try {
return this.spectron.client.click('.query-details .more');
} catch (e) {
return Promise.reject('Toggling search details failed: ' + e);
}
}
public toggleReplace(): any {
try {
return this.spectron.client.click('.monaco-button.toggle-replace-button.collapse');
} catch (e) {
return Promise.reject('Toggling replace failed: ' + e);
}
}
public hoverOverResultCount(): any {
try {
return this.spectron.waitFor(this.spectron.client.moveToObject, '.monaco-count-badge');
} catch (e) {
return Promise.reject('Hovering over result count failed: ' + e);
}
}
public dismissResult(): any {
try {
return this.spectron.client.click('.action-label.icon.action-remove');
} catch (e) {
return Promise.reject('Clicking on dismissing result failed: ' + e);
}
}
}

View File

@@ -0,0 +1,103 @@
/*---------------------------------------------------------------------------------------------
* 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';
export enum StatusBarElement {
BRANCH_STATUS = 0,
SYNC_STATUS = 1,
PROBLEMS_STATUS = 2,
SELECTION_STATUS = 3,
INDENTATION_STATUS = 4,
ENCODING_STATUS = 5,
EOL_STATUS = 6,
LANGUAGE_STATUS = 7,
FEEDBACK_ICON = 8
}
export class StatusBar {
private selectorsMap: Map<StatusBarElement, string>;
private readonly mainSelector = 'div[id="workbench.parts.statusbar"]';
constructor(private spectron: SpectronApplication) {
this.populateSelectorsMap();
}
public async isVisible(element: StatusBarElement): Promise<boolean> {
const selector = this.selectorsMap.get(element);
if (!selector) {
throw new Error('No such element in the status bar defined.');
}
return this.spectron.client.isVisible(selector);
}
public async clickOn(element: StatusBarElement): Promise<any> {
const selector = this.selectorsMap.get(element);
if (!selector) {
throw new Error('No such element in the status bar defined.');
}
try {
return this.spectron.client.click(selector);
} catch (e) {
return Promise.reject(`Clicking on status bar element ${selector} failed.`);
}
}
public async getProblemsView(): Promise<any> {
let el = await this.spectron.client.element('div[id="workbench.panel.markers"]');
if (el.status === 0) {
return el;
}
return undefined;
}
public async getFeedbackView(): Promise<any> {
let el = await this.spectron.client.element('.feedback-form');
if (el.status === 0) {
return el;
}
return undefined;
}
public isQuickOpenWidgetVisible(): Promise<any> {
return this.spectron.client.isVisible('.quick-open-widget');
}
public async getEditorHighlightedLine(lineNumber: number): Promise<any> {
let el = await this.spectron.client.element(`.monaco-editor .view-overlays>:nth-child(${lineNumber}) .current-line`);
if (el.status === 0) {
return el;
}
return undefined;
}
public async getEOLMode(): Promise<any> {
const selector = this.selectorsMap.get(StatusBarElement.EOL_STATUS);
if (!selector) {
throw new Error('No such element in the status bar defined.');
}
return this.spectron.client.getText(selector);
}
private populateSelectorsMap(): void {
this.selectorsMap = new Map<StatusBarElement, string>();
this.selectorsMap.set(StatusBarElement.BRANCH_STATUS, `${this.mainSelector} .octicon.octicon-git-branch`);
this.selectorsMap.set(StatusBarElement.SYNC_STATUS, `${this.mainSelector} .octicon.octicon-sync`);
this.selectorsMap.set(StatusBarElement.PROBLEMS_STATUS, `${this.mainSelector} .task-statusbar-item[title="Problems"]`);
this.selectorsMap.set(StatusBarElement.SELECTION_STATUS, `${this.mainSelector} .editor-status-selection`);
this.selectorsMap.set(StatusBarElement.INDENTATION_STATUS, `${this.mainSelector} .editor-status-indentation`);
this.selectorsMap.set(StatusBarElement.ENCODING_STATUS, `${this.mainSelector} .editor-status-encoding`);
this.selectorsMap.set(StatusBarElement.EOL_STATUS, `${this.mainSelector} .editor-status-eol`);
this.selectorsMap.set(StatusBarElement.LANGUAGE_STATUS, `${this.mainSelector} .editor-status-mode`);
this.selectorsMap.set(StatusBarElement.FEEDBACK_ICON, `${this.mainSelector} .dropdown.send-feedback`);
}
}

View File

@@ -0,0 +1,89 @@
/*---------------------------------------------------------------------------------------------
* 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 { IntegratedTerminal } from './integrated-terminal';
export class Tasks {
private readonly outputViewSelector = IntegratedTerminal.terminalRowsSelector;
private readonly workbenchPanelSelector = 'div[id="workbench.parts.panel"]';
private readonly problemsViewSelector = 'div[id="workbench.panel.markers"] .monaco-tree-row.expanded';
constructor(private spectron: SpectronApplication) {
// noop
}
public async build(): Promise<any> {
await this.spectron.command('workbench.action.tasks.build');
await this.spectron.wait(); // wait for build to finish
// Validate that it has finished
let trial = 0;
while (trial < 3) {
// Determine build status based on the statusbar indicator, don't continue until task has been terminated
try {
return await this.spectron.client.getValue('.task-statusbar-item-progress.builder-hidden');
} catch (e) {
await this.spectron.wait();
trial++;
}
}
return Promise.reject('Could not determine if the task was terminated based on status bar progress spinner.');
}
public openProblemsView(): Promise<any> {
return this.spectron.command('workbench.actions.view.problems');
}
public async outputContains(string: string): Promise<boolean> {
const output: string = await this.spectron.waitFor(this.spectron.client.getText, this.outputViewSelector);
if (output.indexOf(string) !== -1) {
return true;
}
return false;
}
public async selectOutputViewType(type: string): Promise<any> {
await this.openOutputView();
try {
return this.spectron.client.selectByValue(`${this.workbenchPanelSelector} .select-box`, type);
} catch (e) {
return Promise.reject(`Failed to select ${type} as workbench panel output.`);
}
}
public getOutputViewType(): Promise<any> {
return this.spectron.client.getValue(`${this.workbenchPanelSelector} .select-box`);
}
public getProblemsViewFirstElementName(): Promise<any> {
try {
return this.spectron.waitFor(this.spectron.client.getText, `${this.problemsViewSelector} .label-name`);
} catch (e) {
return Promise.reject('Failed to get problem label from Problems view: ' + e);
}
}
public getProblemsViewFirstElementCount(): Promise<any> {
try {
return this.spectron.waitFor(this.spectron.client.getText, `${this.problemsViewSelector} .monaco-count-badge`);
} catch (e) {
return Promise.reject('Failed to get problem count from Problems view: ' + e);
}
}
private openOutputView(): Promise<any> {
try {
return this.spectron.command('workbench.action.output.toggleOutput');
} catch (e) {
return Promise.reject('Failed to toggle output view');
}
}
}

View File

@@ -0,0 +1,40 @@
/*---------------------------------------------------------------------------------------------
* 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';
var fs = require('fs');
const __testTime = new Date().toISOString();
export class Screenshot {
private index: number = 0;
private testPath: string;
constructor(private spectron: SpectronApplication, testName: string, testRetry: number) {
const testTime = this.sanitizeFolderName(__testTime);
testName = this.sanitizeFolderName(testName);
this.testPath = `test_data/screenshots/${testTime}/${testName}/${testRetry}`;
this.createFolder(this.testPath);
}
public async capture(): Promise<any> {
const image = await this.spectron.app.browserWindow.capturePage();
await new Promise((c, e) => fs.writeFile(`${this.testPath}/${this.index++}.png`, image, err => err ? e(err) : c()));
}
private createFolder(name: string): void {
name.split('/').forEach((folderName, i, fullPath) => {
const folder = fullPath.slice(0, i + 1).join('/');
if (!fs.existsSync(folder)) {
fs.mkdirSync(folder);
}
});
}
private sanitizeFolderName(name: string): string {
return name.replace(/[&*:\/]/g, '');
}
}

View File

@@ -0,0 +1,37 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var fs = require('fs');
var rimraf = require('rimraf');
/**
* Contains methods that are commonly used across test areas.
*/
export class Util {
constructor() {
// noop
}
public removeFile(filePath: string): void {
try {
fs.unlinkSync(`${filePath}`);
} catch (e) {
if (e.code !== 'ENOENT') {
throw e;
}
}
}
public rimraf(directory: string): Promise<any> {
return new Promise((res, rej) => {
rimraf(directory, (err) => {
if (err) {
rej(err);
}
res();
});
});
}
}

222
test/smoke/src/main.ts Normal file
View File

@@ -0,0 +1,222 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const fs = require('fs');
const https = require('https');
const program = require('commander');
const git = require('simple-git')();
const child_process = require('child_process');
const path = require('path');
const mkdirp = require('mkdirp');
const testDataPath = path.join(process.cwd(), 'test_data');
const codeWorkspacePath = path.join(testDataPath, 'smoketest.code-workspace');
const testRepoUrl = 'https://github.com/Microsoft/vscode-smoketest-express';
const testRepoLocalDir = path.join(testDataPath, 'vscode-smoketest-express');
const keybindingsUrl = 'https://raw.githubusercontent.com/Microsoft/vscode-docs/master/scripts/keybindings';
mkdirp.sync(testDataPath);
program
.option('-l, --latest <file path>', 'path to the latest VS Code to test')
.option('-s, --stable [file path]', 'path to the stable VS Code to be used in data migration tests');
program.on('--help', () => {
console.log(' Examples:');
console.log('');
console.log(' $ npm test -- --latest path/to/binary');
console.log(' $ npm test -- -l path/to/binary');
console.log('');
console.log(' $ npm test -- --latest path/to/latest/binary --stable path/to/stable/binary');
console.log(' $ npm test -- -l path/to/latest/binary -s path/to/stable/binary');
console.log('');
});
program.parse(process.argv);
if (!program.latest) {
fail('You must specify the binary to run the smoke test against');
}
if (!binaryExists(program.latest) || (program.stable && !binaryExists(program.stable))) {
fail('The file path to electron binary does not exist or permissions do not allow to execute it. Please check the path provided.');
}
if (parseInt(process.version.substr(1)) < 6) {
fail('Please update your Node version to greater than 6 to run the smoke test.');
}
// Setting up environment variables
process.env.VSCODE_LATEST_PATH = program.latest;
if (program.stable) {
process.env.VSCODE_STABLE_PATH = program.stable;
}
process.env.SMOKETEST_REPO = testRepoLocalDir;
if (program.latest && (program.latest.indexOf('Code - Insiders') /* macOS/Windows */ || program.latest.indexOf('code-insiders') /* Linux */) >= 0) {
process.env.VSCODE_EDITION = 'insiders';
}
process.env.VSCODE_WORKSPACE_PATH = codeWorkspacePath;
// Setting up 'vscode-smoketest-express' project
let os = process.platform.toString();
if (os === 'darwin') {
os = 'osx';
}
else if (os === 'win32') {
os = 'win';
}
main().catch(err => console.error(err));
async function main(): Promise<void> {
await getKeybindings(`${keybindingsUrl}/doc.keybindings.${os}.json`, path.join(testDataPath, 'keybindings.json'));
const workspace = {
id: (Date.now() + Math.round(Math.random() * 1000)).toString(),
folders: [
toUri(path.join(testRepoLocalDir, 'public')),
toUri(path.join(testRepoLocalDir, 'routes')),
toUri(path.join(testRepoLocalDir, 'views'))
]
};
await createWorkspaceFile(codeWorkspacePath, workspace);
await cleanOrClone(testRepoUrl, testRepoLocalDir);
await execute('npm install', testRepoLocalDir);
await runTests();
}
function fail(errorMessage): void {
console.error(errorMessage);
process.exit(1);
}
function toUri(path: string): string {
if (os === 'win') {
return `file:///${path.replace(/\\/g, '/')}`;
}
return `file://${path}`;
}
function runTests(): void {
console.log('Running tests...');
var proc = child_process.spawn(process.execPath, [
'out/mocha-runner.js'
]);
proc.stdout.on('data', data => {
console.log(data.toString());
});
proc.stderr.on('data', data => {
var date = new Date().toLocaleString();
fs.appendFile(path.join(testDataPath, 'errors.log'), `${date}: ${data.toString()}`, (err) => {
if (err) {
throw new Error(`Could not write stderr to errors.log with the following error: ${err}`);
};
});
});
proc.on('exit', (code) => {
process.exit(code);
});
}
async function cleanOrClone(repo: string, dir: string): Promise<any> {
console.log('Cleaning or cloning test project repository...');
if (!folderExists(dir)) {
await gitClone(repo, dir);
} else {
git.cwd(dir);
await new Promise((c, e) => git.fetch(err => err ? e(err) : c()));
await gitResetAndClean();
}
}
function gitClone(repo: string, dir: string): Promise<any> {
return new Promise((res, rej) => {
git.clone(repo, dir, () => {
console.log('Test repository successfully cloned.');
res();
});
});
}
async function gitResetAndClean(): Promise<any> {
await new Promise((c, e) => git.reset(['FETCH_HEAD', '--hard'], err => err ? e(err) : c()));
await new Promise((c, e) => git.clean('f', ['-d'], err => err ? e(err) : c()));
console.log('Test project was successfully reset to initial state.');
}
function execute(cmd: string, dir: string): Promise<any> {
return new Promise((res, rej) => {
console.log(`Running ${cmd}...`);
child_process.exec(cmd, { cwd: dir, stdio: [0, 1, 2] }, (error, stdout, stderr) => {
if (error) {
rej(error);
}
if (stderr) {
console.error(stderr);
}
console.log(stdout);
res();
});
});
}
function getKeybindings(url: string, location: string): Promise<any> {
console.log(`Fetching keybindings from ${url}...`);
return new Promise((resolve, reject) => {
https.get(url, (res) => {
if (res.statusCode !== 200) {
reject(`Failed to obtain key bindings with response code: ${res.statusCode}`);
}
var buffer: Buffer[] = [];
res.on('data', (chunk) => buffer.push(chunk));
res.on('end', () => {
fs.writeFile(location, Buffer.concat(buffer), 'utf8', () => {
console.log('Keybindings were successfully fetched.');
resolve();
});
});
}).on('error', (e) => {
reject(`Failed to obtain key bindings with an error: ${e}`);
});
});
}
function createWorkspaceFile(path: string, workspace: any): Promise<any> {
console.log(`Creating workspace file at ${path}...`);
return new Promise((resolve, reject) => {
fs.exists(path, exists => {
if (exists) {
return resolve();
}
fs.writeFile(path, JSON.stringify(workspace, null, '\t'), error => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
});
}
function folderExists(folder: string): boolean {
try {
fs.accessSync(folder, 'rw');
return true;
} catch (e) {
return false;
}
}
function binaryExists(filePath: string): boolean {
try {
fs.accessSync(filePath, 'x');
return true;
} catch (e) {
return false;
}
}

View File

@@ -0,0 +1,16 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const MochaTest = require('mocha');
const mochaTest = new MochaTest({
timeout: 60000,
slow: 10000,
useColors: true
});
mochaTest.addFile(require('path').join(process.cwd(), 'out/test.js'));
mochaTest.run((failures) => {
process.exit(failures);
});

View File

@@ -0,0 +1,171 @@
/*---------------------------------------------------------------------------------------------
* 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 { SpectronClient } from './client';
import { Screenshot } from '../helpers/screenshot';
var fs = require('fs');
var path = require('path');
export const LATEST_PATH = process.env.VSCODE_LATEST_PATH;
export const STABLE_PATH = process.env.VSCODE_STABLE_PATH;
export const WORKSPACE_PATH = process.env.SMOKETEST_REPO;
export const CODE_WORKSPACE_PATH = process.env.VSCODE_WORKSPACE_PATH;
export const USER_DIR = 'test_data/temp_user_dir';
export const EXTENSIONS_DIR = 'test_data/temp_extensions_dir';
/**
* Wraps Spectron's Application instance with its used methods.
*/
export class SpectronApplication {
public client: SpectronClient;
private spectron: Application;
private keybindings: any[];
private screenshot: Screenshot;
private readonly sampleExtensionsDir: string = 'test_data/sample_extensions_dir';
private readonly pollTrials = 50;
private readonly pollTimeout = 1; // in secs
constructor(electronPath: string, testName: string, private testRetry: number, args?: string[], chromeDriverArgs?: string[]) {
if (!args) {
args = [];
}
// Prevent 'Getting Started' web page from opening on clean user-data-dir
args.push('--skip-getting-started');
// Ensure that running over custom extensions directory, rather than picking up the one that was used by a tester previously
let extensionDirIsSet = false;
for (let arg of args) {
if (arg.startsWith('--extensions-dir')) {
extensionDirIsSet = true;
break;
}
}
if (!extensionDirIsSet) {
args.push(`--extensions-dir=${this.sampleExtensionsDir}`);
}
this.spectron = new Application({
path: electronPath,
args: args,
chromeDriverArgs: chromeDriverArgs,
startTimeout: 10000,
requireName: 'nodeRequire'
});
this.testRetry += 1; // avoid multiplication by 0 for wait times
this.screenshot = new Screenshot(this, testName, testRetry);
this.client = new SpectronClient(this.spectron, this.screenshot);
this.retrieveKeybindings();
}
public get app(): Application {
return this.spectron;
}
public async start(): Promise<any> {
await this.spectron.start();
await this.focusOnWindow(1); // focuses on main renderer window
await this.checkWindowReady();
}
public async stop(): Promise<any> {
if (this.spectron && this.spectron.isRunning()) {
return await this.spectron.stop();
}
}
public waitFor(func: (...args: any[]) => any, args: any): Promise<any> {
return this.callClientAPI(func, args);
}
public wait(): Promise<any> {
return new Promise(resolve => setTimeout(resolve, this.testRetry * this.pollTimeout * 1000));
}
public focusOnWindow(index: number): Promise<any> {
return this.client.windowByIndex(index);
}
private async checkWindowReady(): Promise<any> {
await this.waitFor(this.spectron.client.getHTML, '[id="workbench.main.container"]');
}
private retrieveKeybindings() {
fs.readFile(path.join(process.cwd(), `test_data/keybindings.json`), 'utf8', (err, data) => {
if (err) {
throw err;
}
try {
this.keybindings = JSON.parse(data);
} catch (e) {
throw new Error(`Error parsing keybindings JSON: ${e}`);
}
});
}
private async callClientAPI(func: (...args: any[]) => Promise<any>, args: any): Promise<any> {
let trial = 1;
while (true) {
if (trial > this.pollTrials) {
throw new Error(`Could not retrieve the element in ${this.testRetry * this.pollTrials * this.pollTimeout} seconds.`);
}
let result;
try {
result = await func.call(this.client, args, false);
} catch (e) { }
if (result && result !== '') {
await this.screenshot.capture();
return result;
}
await this.wait();
trial++;
}
}
/**
* Retrieves the command from keybindings file and executes it with WebdriverIO client API
* @param command command (e.g. 'workbench.action.files.newUntitledFile')
*/
public command(command: string, capture?: boolean): Promise<any> {
const binding = this.keybindings.find(x => x['command'] === command);
if (!binding) {
return Promise.reject(`Key binding for ${command} was not found.`);
}
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, capture);
}
/**
* 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);
};
}
}

View File

@@ -0,0 +1,127 @@
/*---------------------------------------------------------------------------------------------
* 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 { Screenshot } from '../helpers/screenshot';
/**
* Abstracts the Spectron's WebdriverIO managed client property on the created Application instances.
*/
export class SpectronClient {
constructor(private spectron: Application, private shot: Screenshot) {
// noop
}
public windowByIndex(index: number): Promise<any> {
return this.spectron.client.windowByIndex(index);
}
public async keys(keys: string[] | string, capture: boolean = true): Promise<any> {
await this.screenshot(capture);
return this.spectron.client.keys(keys);
}
public async getText(selector: string, capture: boolean = true): Promise<any> {
await this.screenshot(capture);
return this.spectron.client.getText(selector);
}
public async getHTML(selector: string, capture: boolean = true): Promise<any> {
await this.screenshot(capture);
return this.spectron.client.getHTML(selector);
}
public async click(selector: string, capture: boolean = true): Promise<any> {
await this.screenshot(capture);
return this.spectron.client.click(selector);
}
public async doubleClick(selector: string, capture: boolean = true): Promise<any> {
await this.screenshot(capture);
return this.spectron.client.doubleClick(selector);
}
public async leftClick(selector: string, xoffset: number, yoffset: number, capture: boolean = true): Promise<any> {
await this.screenshot(capture);
return this.spectron.client.leftClick(selector, xoffset, yoffset);
}
public async rightClick(selector: string, capture: boolean = true): Promise<any> {
await this.screenshot(capture);
return this.spectron.client.rightClick(selector);
}
public async moveToObject(selector: string, capture: boolean = true): Promise<any> {
await this.screenshot(capture);
return this.spectron.client.moveToObject(selector);
}
public async setValue(selector: string, text: string, capture: boolean = true): Promise<any> {
await this.screenshot(capture);
return this.spectron.client.setValue(selector, text);
}
public async elements(selector: string, capture: boolean = true): Promise<any> {
await this.screenshot(capture);
return this.spectron.client.elements(selector);
}
public async element(selector: string, capture: boolean = true): Promise<any> {
await this.screenshot(capture);
return this.spectron.client.element(selector);
}
public async dragAndDrop(sourceElem: string, destinationElem: string, capture: boolean = true): Promise<any> {
await this.screenshot(capture);
return this.spectron.client.dragAndDrop(sourceElem, destinationElem);
}
public async selectByValue(selector: string, value: string, capture: boolean = true): Promise<any> {
await this.screenshot(capture);
return this.spectron.client.selectByValue(selector, value);
}
public async getValue(selector: string, capture: boolean = true): Promise<any> {
await this.screenshot(capture);
return this.spectron.client.getValue(selector);
}
public async getAttribute(selector: string, attribute: string, capture: boolean = true): Promise<any> {
await this.screenshot(capture);
return Promise.resolve(this.spectron.client.getAttribute(selector, attribute));
}
public clearElement(selector: string): any {
return this.spectron.client.clearElement(selector);
}
public buttonDown(): any {
return this.spectron.client.buttonDown();
}
public buttonUp(): any {
return this.spectron.client.buttonUp();
}
public async isVisible(selector: string, capture: boolean = true): Promise<any> {
await this.screenshot(capture);
return this.spectron.client.isVisible(selector);
}
public getTitle(): string {
return this.spectron.client.getTitle();
}
private async screenshot(capture: boolean): Promise<any> {
if (capture) {
try {
await this.shot.capture();
} catch (e) {
throw new Error(`Screenshot could not be captured: ${e}`);
}
}
}
}

40
test/smoke/src/test.ts Normal file
View File

@@ -0,0 +1,40 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { testDataMigration } from './tests/data-migration';
import { testDataLoss } from './tests/data-loss';
import { testExplorer } from './tests/explorer';
import { testConfigViews } from './tests/configuration-views';
import { testSearch } from './tests/search';
import { testCSS } from './tests/css';
import { testJavaScript } from './tests/javascript';
import { testJavaScriptDebug } from './tests/javascript-debug';
import { testGit } from './tests/git';
import { testIntegratedTerminal } from './tests/integrated-terminal';
import { testStatusbar } from './tests/statusbar';
import { testTasks } from './tests/tasks';
import { testExtensions } from './tests/extensions';
import { testLocalization } from './tests/localization';
import { testMultiRoot } from './tests/multiroot';
describe('Smoke:', () => {
testDataMigration();
testDataLoss();
testExplorer();
testConfigViews();
testSearch();
testCSS();
testJavaScript();
testJavaScriptDebug();
testGit();
testIntegratedTerminal();
testStatusbar();
testTasks();
testExtensions();
testLocalization();
if (process.env.VSCODE_EDITION === 'insiders') {
testMultiRoot(); // only enabled in insiders
}
});

View File

@@ -0,0 +1,57 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { SpectronApplication, LATEST_PATH, WORKSPACE_PATH } from '../spectron/application';
import { CommonActions } from '../areas/common';
import { ConfigurationView, ActivityBarPosition } from '../areas/configuration-views';
let app: SpectronApplication;
let common: CommonActions;
export function testConfigViews() {
describe('Configuration and views', () => {
let configView: ConfigurationView;
beforeEach(async function () {
app = new SpectronApplication(LATEST_PATH, this.currentTest.fullTitle(), (this.currentTest as any).currentRetry(), [WORKSPACE_PATH]);
common = new CommonActions(app);
configView = new ConfigurationView(app);
return await app.start();
});
afterEach(async function () {
return await app.stop();
});
it('turns off editor line numbers and verifies the live change', async function () {
await common.newUntitledFile();
await app.wait();
let elementsCount = await configView.getEditorLineNumbers();
assert.equal(elementsCount, 1, 'Line numbers are not present in the editor before disabling them.');
await common.addSetting('editor.lineNumbers', 'off');
await app.wait();
elementsCount = await configView.getEditorLineNumbers();
assert.equal(elementsCount, 0, 'Line numbers are still present in the editor after disabling them.');
});
it(`changes 'workbench.action.toggleSidebarPosition' command key binding and verifies it`, async function () {
await configView.enterKeybindingsView();
await common.type('workbench.action.toggleSidebarPosition');
await app.wait();
await configView.selectFirstKeybindingsMatch();
await configView.changeKeybinding();
await configView.enterBinding(['Control', 'u', 'NULL']);
await common.enter();
let html = await configView.getActivityBar(ActivityBarPosition.RIGHT);
assert.equal(html, undefined, 'Activity bar is positioned on the right, whereas should not be.');
await app.wait();
await configView.toggleActivityBarPosition();
html = await configView.getActivityBar(ActivityBarPosition.RIGHT);
assert.ok(html, 'Activity bar was not moved to right after toggling its position.');
});
});
}

View File

@@ -0,0 +1,61 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { SpectronApplication, LATEST_PATH, WORKSPACE_PATH } from '../spectron/application';
import { CommonActions } from '../areas/common';
import { CSS, CSSProblem } from '../areas/css';
let app: SpectronApplication;
let common: CommonActions;
export function testCSS() {
describe('CSS', () => {
let css: CSS;
beforeEach(async function () {
app = new SpectronApplication(LATEST_PATH, this.currentTest.fullTitle(), (this.currentTest as any).currentRetry(), [WORKSPACE_PATH]);
common = new CommonActions(app);
css = new CSS(app);
return await app.start();
});
afterEach(async function () {
return await app.stop();
});
it('verifies quick outline', async function () {
await common.openFirstMatchFile('style.css');
await css.openQuickOutline();
await app.wait();
const count = await common.getQuickOpenElements();
assert.equal(count, 2, 'Quick outline symbol count is wrong.');
});
it('verifies warnings for the empty rule', async function () {
await common.openFirstMatchFile('style.css');
await common.type('.foo{}');
await app.wait();
let warning = await css.getEditorProblem(CSSProblem.WARNING);
assert.ok(warning, `Warning squiggle is not shown in 'style.css'.`);
await css.toggleProblemsView();
warning = await css.getProblemsViewsProblem(CSSProblem.WARNING);
assert.ok(warning, 'Warning does not appear in Problems view.');
});
it('verifies that warning becomes an error once setting changed', async function () {
await common.addSetting('css.lint.emptyRules', 'error');
await common.openFirstMatchFile('style.css');
await common.type('.foo{}');
await app.wait();
let error = await css.getEditorProblem(CSSProblem.ERROR);
assert.ok(error, `Error squiggle is not shown in 'style.css'.`);
await css.toggleProblemsView();
error = await css.getProblemsViewsProblem(CSSProblem.ERROR);
assert.ok(error, `Error does not appear in Problems view`);
});
});
}

View File

@@ -0,0 +1,74 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { SpectronApplication, USER_DIR, LATEST_PATH, WORKSPACE_PATH } from '../spectron/application';
import { CommonActions } from '../areas/common';
import { DataLoss } from '../areas/data-loss';
let app: SpectronApplication;
let common: CommonActions;
let dl: DataLoss;
export function testDataLoss() {
describe('Data Loss', () => {
beforeEach(async function () {
app = new SpectronApplication(LATEST_PATH, this.currentTest.fullTitle(), (this.currentTest as any).currentRetry(), [WORKSPACE_PATH], [`--user-data-dir=${USER_DIR}`]);
common = new CommonActions(app);
dl = new DataLoss(app);
await common.removeDirectory(USER_DIR);
await app.start();
});
afterEach(async function () {
return await app.stop();
});
it(`verifies that 'hot exit' works for dirty files`, async function () {
const textToType = 'Hello, Code', fileName = 'readme.md', untitled = 'Untitled-1';
await common.newUntitledFile();
await common.type(textToType);
await dl.openExplorerViewlet();
await common.openFile(fileName, true);
await common.type(textToType);
await app.stop();
await app.start();
// check tab presence
assert.ok(await common.getTab(untitled), `${untitled} tab is not present after reopening.`);
assert.ok(await common.getTab(fileName, true), `${fileName} tab is not present or is not active after reopening.`);
// check if they marked as dirty (icon) and active tab is the last opened
assert.ok(await dl.verifyTabIsDirty(untitled), `${untitled} tab is not dirty after reopening.`);
assert.ok(await dl.verifyTabIsDirty(fileName, true), `${fileName} tab is not dirty after reopening.`);
});
it(`verifies that contents of the dirty files are restored after 'hot exit'`, async function () {
// make one dirty file,
// create one untitled file
const textToType = 'Hello, Code';
// create one untitled file
await common.newUntitledFile();
await common.type(textToType);
// make one dirty file,
await common.openFile('readme.md', true);
await common.type(textToType);
await app.stop();
await app.start();
// check their contents
let fileDirt = await common.getEditorFirstLinePlainText();
assert.equal(fileDirt, textToType, 'Active file contents are different after restore.');
await common.selectTab('Untitled-1');
fileDirt = await common.getEditorFirstLinePlainText();
assert.equal(fileDirt, textToType, 'Untitled file edit are different after restore.');
});
});
}

View File

@@ -0,0 +1,105 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { SpectronApplication, USER_DIR, STABLE_PATH, LATEST_PATH, WORKSPACE_PATH, EXTENSIONS_DIR } from '../spectron/application';
import { CommonActions } from '../areas/common';
let app: SpectronApplication;
let common: CommonActions;
export function testDataMigration() {
if (!STABLE_PATH) {
return;
}
describe('Data Migration', () => {
afterEach(async function () {
await app.stop();
await common.removeDirectory(USER_DIR);
return await common.removeDirectory(EXTENSIONS_DIR);
});
function setupSpectron(context: Mocha.ITestCallbackContext, appPath: string, args?: string[]): void {
if (!args) {
args = [];
}
args.push(`--extensions-dir=${EXTENSIONS_DIR}`);
app = new SpectronApplication(appPath, context.test.fullTitle(), context.test.currentRetry(), args, [`--user-data-dir=${USER_DIR}`]);
common = new CommonActions(app);
}
it('checks if the Untitled file is restored migrating from stable to latest', async function () {
const textToType = 'Very dirty file';
// Setting up stable version
setupSpectron(this, STABLE_PATH);
await app.start();
await common.newUntitledFile();
await common.type(textToType);
await app.stop();
await app.wait(); // wait until all resources are released (e.g. locked local storage)
// Checking latest version for the restored state
setupSpectron(this, LATEST_PATH);
await app.start();
assert.ok(await common.getTab('Untitled-1'), 'Untitled-1 tab was not restored after migration.');
await common.selectTab('Untitled-1');
const editorText = await common.getEditorFirstLinePlainText();
assert.equal(editorText, textToType, 'Typed editor text does not match to the one after migration.');
});
it('checks if the newly created dirty file is restored migrating from stable to latest', async function () {
const fileName = 'test_data/plainFile',
firstTextPart = 'This is going to be an unsaved file', secondTextPart = '_that is dirty.';
// Setting up stable version
setupSpectron(this, STABLE_PATH, [fileName]);
await common.removeFile(`${fileName}`);
await app.start();
await common.type(firstTextPart);
await common.saveOpenedFile();
await app.wait();
await common.type(secondTextPart);
await app.stop();
await app.wait(); // wait until all resources are released (e.g. locked local storage)
// Checking latest version for the restored state
setupSpectron(this, LATEST_PATH);
await app.start();
assert.ok(await common.getTab(fileName.split('/')[1]), `${fileName} was not restored after migration.`);
await common.selectTab(fileName.split('/')[1]);
const editorText = await common.getEditorFirstLinePlainText();
assert.equal(editorText, firstTextPart.concat(secondTextPart), 'Entered text was not correctly restored after migration.');
// Cleanup
await common.removeFile(`${fileName}`);
});
it('cheks if opened tabs are restored migrating from stable to latest', async function () {
const fileName1 = 'app.js', fileName2 = 'jsconfig.json', fileName3 = 'readme.md';
setupSpectron(this, STABLE_PATH, [WORKSPACE_PATH]);
await app.start();
await common.openFile(fileName1, true);
await common.openFile(fileName2, true);
await common.openFile(fileName3, true);
await app.stop();
setupSpectron(this, LATEST_PATH, [WORKSPACE_PATH]);
await app.start();
assert.ok(await common.getTab(fileName1), `${fileName1} tab was not restored after migration.`);
assert.ok(await common.getTab(fileName2), `${fileName2} tab was not restored after migration.`);
assert.ok(await common.getTab(fileName3), `${fileName3} tab was not restored after migration.`);
});
});
}

View File

@@ -0,0 +1,43 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { SpectronApplication, LATEST_PATH, WORKSPACE_PATH } from '../spectron/application';
import { CommonActions } from '../areas/common';
let app: SpectronApplication;
let common: CommonActions;
export function testExplorer() {
describe('Explorer', () => {
beforeEach(async function () {
app = new SpectronApplication(LATEST_PATH, this.currentTest.fullTitle(), (this.currentTest as any).currentRetry(), [WORKSPACE_PATH]);
common = new CommonActions(app);
return await app.start();
});
afterEach(async function () {
return await app.stop();
});
it('quick open search produces correct result', async function () {
await common.openQuickOpen();
await common.type('.js');
await app.wait();
const elCount = await common.getQuickOpenElements();
assert.equal(elCount, 7);
});
it('quick open respects fuzzy matching', async function () {
await common.openQuickOpen();
await common.type('a.s');
await app.wait();
const elCount = await common.getQuickOpenElements();
assert.equal(elCount, 3);
});
});
}

View File

@@ -0,0 +1,74 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { SpectronApplication, LATEST_PATH, WORKSPACE_PATH, EXTENSIONS_DIR } from '../spectron/application';
import { CommonActions } from '../areas/common';
import { Extensions } from '../areas/extensions';
var dns = require('dns');
let app: SpectronApplication;
let common: CommonActions;
export function testExtensions() {
describe('Extensions', () => {
let extensions: Extensions;
const extensionName = 'vscode-smoketest-check';
beforeEach(async function () {
const network = await networkAttached();
if (!network) {
return Promise.reject('There is no network connection for testing extensions.');
}
app = new SpectronApplication(LATEST_PATH, this.currentTest.fullTitle(), (this.currentTest as any).currentRetry(), [WORKSPACE_PATH, `--extensions-dir=${EXTENSIONS_DIR}`]);
common = new CommonActions(app);
extensions = new Extensions(app, common);
await common.removeDirectory(EXTENSIONS_DIR);
return await app.start();
});
afterEach(async function () {
await app.stop();
return await common.removeDirectory(EXTENSIONS_DIR);
});
it(`installs 'vscode-smoketest-check' extension and verifies reload is prompted`, async function () {
await extensions.openExtensionsViewlet();
await extensions.searchForExtension(extensionName);
await app.wait();
await extensions.installExtension(extensionName);
await app.wait();
assert.ok(await extensions.getExtensionReloadText(), 'Reload was not prompted after extension installation.');
});
it(`installs an extension and checks if it works on restart`, async function () {
await extensions.openExtensionsViewlet();
await extensions.searchForExtension(extensionName);
await app.wait();
await extensions.installExtension(extensionName);
await app.wait();
await extensions.getExtensionReloadText();
await app.stop();
await app.wait(); // wait until all resources are released (e.g. locked local storage)
await app.start();
await extensions.activateExtension();
const statusbarText = await extensions.verifyStatusbarItem();
assert.equal(statusbarText, 'VS Code Smoke Test Check', 'Extension contribution text does not match expected.');
});
});
}
function networkAttached(): Promise<boolean> {
return new Promise((res, rej) => {
dns.resolve('marketplace.visualstudio.com', (err) => {
err ? res(false) : res(true);
});
});
}

View File

@@ -0,0 +1,69 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { SpectronApplication, LATEST_PATH, WORKSPACE_PATH } from '../spectron/application';
import { CommonActions } from '../areas/common';
import { Git } from '../areas/git';
let app: SpectronApplication;
let common: CommonActions;
export function testGit() {
describe('Git', () => {
let git: Git;
beforeEach(async function () {
app = new SpectronApplication(LATEST_PATH, this.currentTest.fullTitle(), (this.currentTest as any).currentRetry(), [WORKSPACE_PATH]);
common = new CommonActions(app);
git = new Git(app, common);
return await app.start();
});
afterEach(async function () {
return await app.stop();
});
it('verifies current changes are picked up by Git viewlet', async function () {
const changesCount = await git.getScmIconChanges();
assert.equal(changesCount, 2);
await git.openGitViewlet();
assert.ok(await git.verifyScmChange('app.js'), 'app.js change does not appear in SCM viewlet.');
assert.ok(await git.verifyScmChange('launch.json'), 'launch.json change does not appear in SCM viewlet.');
});
it(`verifies 'app.js' diff viewer changes`, async function () {
await git.openGitViewlet();
await common.openFile('app.js');
const original = await git.getOriginalAppJsBodyVarName();
assert.equal(original, 'bodyParser', 'Original value from diff view is wrong.');
const modified = await git.getModifiedAppJsBodyVarName();
assert.equal(modified, 'ydobParser', 'Modified value from diff view is wrong.');
});
it(`stages 'app.js' changes and checks stage count`, async function () {
await git.openGitViewlet();
await app.wait();
await git.stageFile('app.js');
const stagedCount = await git.getStagedCount();
assert.equal(stagedCount, 1);
// Return back to unstaged state
await git.unstageFile('app.js');
});
it(`stages, commits change to 'app.js' locally and verifies outgoing change`, async function () {
await git.openGitViewlet();
await app.wait();
await git.stageFile('app.js');
await git.focusOnCommitBox();
await common.type('Test commit');
await git.pressCommit();
const changes = await git.getOutgoingChanges();
assert.equal(changes, ' 0↓ 1↑', 'Changes indicator is wrong in a status bar.');
});
});
}

View File

@@ -0,0 +1,40 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { SpectronApplication, LATEST_PATH, WORKSPACE_PATH } from '../spectron/application';
import { CommonActions } from '../areas/common';
import { IntegratedTerminal } from '../areas/integrated-terminal';
let app: SpectronApplication;
let common: CommonActions;
export function testIntegratedTerminal() {
describe('Integrated Terminal', () => {
let terminal: IntegratedTerminal;
beforeEach(async function () {
app = new SpectronApplication(LATEST_PATH, this.currentTest.fullTitle(), (this.currentTest as any).currentRetry(), [WORKSPACE_PATH]);
common = new CommonActions(app);
terminal = new IntegratedTerminal(app);
return await app.start();
});
afterEach(async function () {
return await app.stop();
});
it(`opens terminal, runs 'echo' and verifies the output`, async function () {
const command = 'echo test';
await terminal.openTerminal(common);
await app.wait();
await common.type(command);
await common.enter();
await app.wait();
assert.ok(await terminal.commandOutputHas('test'), 'Terminal output does not contain echo.');
});
});
}

View 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 * as assert from 'assert';
import { SpectronApplication, LATEST_PATH, WORKSPACE_PATH } from '../spectron/application';
import { CommonActions } from '../areas/common';
import { JavaScriptDebug } from '../areas/javascript-debug';
let app: SpectronApplication;
let common: CommonActions;
export function testJavaScriptDebug() {
describe('Debugging JavaScript', () => {
let jsDebug: JavaScriptDebug;
beforeEach(async function () {
app = new SpectronApplication(LATEST_PATH, this.currentTest.fullTitle(), (this.currentTest as any).currentRetry(), [WORKSPACE_PATH]);
common = new CommonActions(app);
jsDebug = new JavaScriptDebug(app);
return await app.start();
});
afterEach(async function () {
return await app.stop();
});
it('autodetects program attribute for launch.json', async function () {
await jsDebug.openDebugViewlet();
await jsDebug.pressConfigureLaunchJson();
const value = await jsDebug.getProgramConfigValue();
process.platform === 'win32' ? assert.equal(value, '${workspaceRoot}\\bin\\www') : assert.equal(value, '${workspaceRoot}/bin/www');
});
it(`can set a breakpoint and verify if it's set`, async function () {
await common.openFirstMatchFile('index.js');
await jsDebug.setBreakpointOnLine(6);
const breakpoint = await jsDebug.verifyBreakpointOnLine(6);
assert.ok(breakpoint, 'Breakpoint was not found on line 6.');
});
});
}

View File

@@ -0,0 +1,87 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { SpectronApplication, LATEST_PATH, WORKSPACE_PATH } from '../spectron/application';
import { CommonActions } from '../areas/common';
import { JavaScript } from '../areas/javascript';
let app: SpectronApplication;
let common: CommonActions;
export function testJavaScript() {
describe('JavaScript', () => {
let js: JavaScript;
beforeEach(async function () {
app = new SpectronApplication(LATEST_PATH, this.currentTest.fullTitle(), (this.currentTest as any).currentRetry(), [WORKSPACE_PATH]);
common = new CommonActions(app);
js = new JavaScript(app);
return await app.start();
});
afterEach(async function () {
return await app.stop();
});
it('shows correct quick outline', async function () {
await common.openFirstMatchFile('bin/www');
await js.openQuickOutline();
await app.wait();
const symbols = await common.getQuickOpenElements();
assert.equal(symbols, 12, 'Quick outline elements count does not match to expected.');
});
it(`finds 'All References' to 'app'`, async function () {
await common.openFirstMatchFile('bin/www');
await js.findAppReferences();
await app.wait();
const titleCount = await js.getTitleReferencesCount();
assert.equal(titleCount, 3, 'References count in widget title is not as expected.');
const treeCount = await js.getTreeReferencesCount();
assert.equal(treeCount, 3, 'References count in tree is not as expected.');
});
it(`renames local 'app' variable`, async function () {
await common.openFirstMatchFile('bin/www');
const newVarName = 'newApp';
await js.renameApp(newVarName);
await common.enter();
const newName = await js.getNewAppName();
assert.equal(newName, newVarName);
});
it('folds/unfolds the code correctly', async function () {
await common.openFirstMatchFile('bin/www');
// Fold
await js.toggleFirstCommentFold();
const foldedIcon = await js.getFirstCommentFoldedIcon();
assert.ok(foldedIcon, 'Folded icon was not found in the margin.');
let nextLineNumber = await js.getNextLineNumberAfterFold();
assert.equal(nextLineNumber, 7, 'Line number after folded code is wrong.');
// Unfold
await js.toggleFirstCommentFold();
nextLineNumber = await js.getNextLineNumberAfterFold();
assert.equal(nextLineNumber, 4, 'Line number with unfolded code is wrong.');
});
it(`verifies that 'Go To Definition' works`, async function () {
await common.openFirstMatchFile('app.js');
await js.goToExpressDefinition();
await app.wait();
assert.ok(await common.getTab('index.d.ts'), 'Tab opened when navigating to definition is not as expected.');
});
it(`verifies that 'Peek Definition' works`, async function () {
await common.openFirstMatchFile('app.js');
await js.peekExpressDefinition();
await app.wait();
const definitionFilename = await js.getPeekExpressResultName();
assert.equal(definitionFilename, 'index.d.ts', 'Peek result is not as expected.');
});
});
}

View File

@@ -0,0 +1,50 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { SpectronApplication, LATEST_PATH, WORKSPACE_PATH, USER_DIR } from '../spectron/application';
import { CommonActions } from '../areas/common';
import { Localization, ViewletType } from '../areas/localization';
let app: SpectronApplication;
let common: CommonActions;
export function testLocalization() {
describe('Localization', () => {
afterEach(async function () {
return await app.stop();
});
it(`starts with 'DE' locale and verifies title and viewlets text is in German`, async function () {
app = new SpectronApplication(LATEST_PATH, this.test.fullTitle(), this.test.currentRetry(), [WORKSPACE_PATH, '--locale=DE'], [`--user-data-dir=${USER_DIR}`]);
common = new CommonActions(app);
const locale = new Localization(app);
common.removeDirectory(USER_DIR);
await app.start();
let text = await locale.getOpenEditorsText();
assert.equal(text.toLowerCase(), 'geöffnete editoren');
await locale.openViewlet(ViewletType.SEARCH);
text = await locale.getOpenedViewletTitle();
assert.equal(text.toLowerCase(), 'suchen');
await locale.openViewlet(ViewletType.SCM);
await app.wait(); // wait until git extension is loaded
text = await locale.getOpenedViewletTitle();
assert.equal(text.toLowerCase(), 'quellcodeverwaltung: git');
await locale.openViewlet(ViewletType.DEBUG);
text = await locale.getOpenedViewletTitle();
assert.equal(text.toLowerCase(), 'debuggen');
await locale.openViewlet(ViewletType.EXTENSIONS);
text = await locale.getExtensionsSearchPlaceholder();
assert.equal(text.toLowerCase(), 'nach erweiterungen im marketplace suchen');
});
});
}

View File

@@ -0,0 +1,43 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { SpectronApplication, LATEST_PATH, CODE_WORKSPACE_PATH } from '../spectron/application';
import { CommonActions } from '../areas/common';
let app: SpectronApplication;
let common: CommonActions;
export function testMultiRoot() {
describe('Multi Root', () => {
beforeEach(async function () {
app = new SpectronApplication(LATEST_PATH, this.currentTest.fullTitle(), (this.currentTest as any).currentRetry(), [CODE_WORKSPACE_PATH]);
common = new CommonActions(app);
return await app.start();
});
afterEach(async function () {
return await app.stop();
});
it('shows results from all folders', async function () {
await common.openQuickOpen();
await app.wait();
await common.type('*.*');
await app.wait();
const elCount = await common.getQuickOpenElements();
assert.equal(elCount, 6);
});
it('shows workspace name in title', async function () {
await app.wait();
const title = await common.getWindowTitle();
assert.ok(title.indexOf('smoketest (Workspace)') >= 0);
});
});
}

View File

@@ -0,0 +1,73 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { SpectronApplication, LATEST_PATH, WORKSPACE_PATH } from '../spectron/application';
import { CommonActions } from '../areas/common';
import { Search } from '../areas/search';
let app: SpectronApplication;
let common: CommonActions;
export function testSearch() {
describe('Search', () => {
let search: Search;
beforeEach(async function () {
app = new SpectronApplication(LATEST_PATH, this.currentTest.fullTitle(), (this.currentTest as any).currentRetry(), [WORKSPACE_PATH]);
common = new CommonActions(app);
search = new Search(app);
return await app.start();
});
afterEach(async function () {
return await app.stop();
});
it('searches for body & checks for correct result number', async function () {
const s = search;
await s.openSearchViewlet();
await s.searchFor('body');
const result = await s.getResultText();
assert.equal(result, '7 results in 4 files');
});
it('searches only for *.js files & checks for correct result number', async function () {
const s = search;
await s.openSearchViewlet();
await s.searchFor('body');
await s.toggleSearchDetails();
await s.searchFor('*.js');
const results = await s.getResultText();
assert.equal(results, '4 results in 1 file');
});
it('dismisses result & checks for correct result number', async function () {
const s = search;
await s.openSearchViewlet();
await s.searchFor('body');
await s.hoverOverResultCount();
await s.dismissResult();
await app.wait();
const result = await s.getResultText();
assert.equal(result, '3 results in 3 files', 'Result number after dismissal does not match to expected.');
});
it('replaces first search result with a replace term', async function () {
const s = search;
await s.openSearchViewlet();
await s.searchFor('body');
await s.toggleReplace();
await s.setReplaceText('ydob');
await s.hoverOverResultCount();
await s.replaceFirstMatch();
await app.wait();
await common.saveOpenedFile();
const result = await s.getResultText();
assert.equal(result, '3 results in 3 files', 'Result number after replacemenet does not match to expected.');
});
});
}

View File

@@ -0,0 +1,94 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { SpectronApplication, LATEST_PATH, WORKSPACE_PATH } from '../spectron/application';
import { CommonActions } from '../areas/common';
import { StatusBarElement, StatusBar } from '../areas/statusbar';
let app: SpectronApplication;
let common: CommonActions;
export function testStatusbar() {
describe('Status Bar', () => {
let statusBar: StatusBar;
beforeEach(async function () {
app = new SpectronApplication(LATEST_PATH, this.currentTest.fullTitle(), (this.currentTest as any).currentRetry(), [WORKSPACE_PATH]);
common = new CommonActions(app);
statusBar = new StatusBar(app);
return await app.start();
});
afterEach(async function () {
return await app.stop();
});
it('verifies presence of all default status bar elements', async function () {
await app.wait();
assert.ok(await statusBar.isVisible(StatusBarElement.BRANCH_STATUS), 'Branch indicator is not visible.');
assert.ok(await statusBar.isVisible(StatusBarElement.FEEDBACK_ICON), 'Feedback icon is not visible.');
assert.ok(await statusBar.isVisible(StatusBarElement.SYNC_STATUS), 'Sync indicator is not visible.');
assert.ok(await statusBar.isVisible(StatusBarElement.PROBLEMS_STATUS), 'Problems indicator is not visible.');
await common.openFirstMatchFile('app.js');
assert.ok(await statusBar.isVisible(StatusBarElement.ENCODING_STATUS), 'Encoding indicator is not visible.');
assert.ok(await statusBar.isVisible(StatusBarElement.EOL_STATUS), 'EOL indicator is not visible.');
assert.ok(await statusBar.isVisible(StatusBarElement.INDENTATION_STATUS), 'Indentation indicator is not visible.');
assert.ok(await statusBar.isVisible(StatusBarElement.LANGUAGE_STATUS), 'Language indicator is not visible.');
assert.ok(await statusBar.isVisible(StatusBarElement.SELECTION_STATUS), 'Selection indicator is not visible.');
});
it(`verifies that 'quick open' opens when clicking on status bar elements`, async function () {
await app.wait();
await statusBar.clickOn(StatusBarElement.BRANCH_STATUS);
assert.ok(await statusBar.isQuickOpenWidgetVisible(), 'Quick open is not opened for branch indicator.');
await common.closeQuickOpen();
await common.openFirstMatchFile('app.js');
await statusBar.clickOn(StatusBarElement.INDENTATION_STATUS);
assert.ok(await statusBar.isQuickOpenWidgetVisible(), 'Quick open is not opened for indentation indicator.');
await common.closeQuickOpen();
await statusBar.clickOn(StatusBarElement.ENCODING_STATUS);
assert.ok(await statusBar.isQuickOpenWidgetVisible(), 'Quick open is not opened for encoding indicator.');
await common.closeQuickOpen();
await statusBar.clickOn(StatusBarElement.EOL_STATUS);
assert.ok(await statusBar.isQuickOpenWidgetVisible(), 'Quick open is not opened for EOL indicator.');
await common.closeQuickOpen();
await statusBar.clickOn(StatusBarElement.LANGUAGE_STATUS);
assert.ok(await statusBar.isQuickOpenWidgetVisible(), 'Quick open is not opened for language indicator.');
await common.closeQuickOpen();
});
it(`verifies that 'Problems View' appears when clicking on 'Problems' status element`, async function () {
await statusBar.clickOn(StatusBarElement.PROBLEMS_STATUS);
assert.ok(await statusBar.getProblemsView());
});
it(`verifies that 'Tweet us feedback' pop-up appears when clicking on 'Feedback' icon`, async function () {
await statusBar.clickOn(StatusBarElement.FEEDBACK_ICON);
assert.ok(await statusBar.getFeedbackView());
});
it(`checks if 'Go to Line' works if called from the status bar`, async function () {
await common.openFirstMatchFile('app.js');
await statusBar.clickOn(StatusBarElement.SELECTION_STATUS);
const lineNumber = 15;
await common.type(lineNumber.toString());
await common.enter();
assert.ok(await statusBar.getEditorHighlightedLine(lineNumber), 'Editor does not highlight the line.');
});
it(`verifies if changing EOL is reflected in the status bar`, async function () {
await common.openFirstMatchFile('app.js');
await statusBar.clickOn(StatusBarElement.EOL_STATUS);
await common.selectNextQuickOpenElement();
await common.enter();
const currentEOL = await statusBar.getEOLMode();
assert.equal(currentEOL, 'CRLF');
});
});
}

View File

@@ -0,0 +1,56 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { SpectronApplication, LATEST_PATH, WORKSPACE_PATH } from '../spectron/application';
import { Tasks } from '../areas/tasks';
let app: SpectronApplication;
export function testTasks() {
describe('Tasks', () => {
let tasks: Tasks;
beforeEach(async function () {
app = new SpectronApplication(LATEST_PATH, this.currentTest.fullTitle(), (this.currentTest as any).currentRetry(), [WORKSPACE_PATH]);
tasks = new Tasks(app);
return await app.start();
});
afterEach(async function () {
return await app.stop();
});
it('verifies that eslint task results in 1 problem', async function () {
const expectedOutput = '1 problem (0 errors, 1 warning)';
await tasks.build();
const actualOutput = await tasks.outputContains(expectedOutput);
assert.ok(actualOutput, `Output does not contain the following string: '${expectedOutput}'`);
});
it(`is able to select 'Git' output`, async function () {
await tasks.build();
await app.wait();
await tasks.selectOutputViewType('Git');
const viewType = await tasks.getOutputViewType();
assert.equal(viewType, 'Git');
});
it('ensures that build task produces no-unused-vars message', async function () {
await tasks.build();
assert.ok(await tasks.outputContains(`'next' is defined but never used`), `Output does not contain no-unused-vars message`);
});
it(`verifies build error is reflected in 'Problems View'`, async function () {
await tasks.build();
await tasks.openProblemsView();
const problemName = await tasks.getProblemsViewFirstElementName();
assert.equal(problemName, 'index.js', `'index.js' is not a build error.`);
const problemsCount = await tasks.getProblemsViewFirstElementCount();
assert.equal(problemsCount, '1', `Problem count is different to expected.`);
});
});
}