Refresh master with initial release/0.24 snapshot (#332)

* Initial port of release/0.24 source code

* Fix additional headers

* Fix a typo in launch.json
This commit is contained in:
Karl Burtram
2017-12-15 15:38:57 -08:00
committed by GitHub
parent 271b3a0b82
commit 6ad0df0e3e
7118 changed files with 107999 additions and 56466 deletions

View File

@@ -4,7 +4,7 @@
"name": "Jxck/assert",
"license": "MIT",
"licenseDetail": [
"The MIT License (MIT)",
"The Source EULA (MIT)",
"",
"Copyright (c) 2011 Jxck",
"",

View File

@@ -48,7 +48,6 @@ function main() {
console.error(e.stack || e);
});
// {{SQL CARBON EDIT}}
var loaderConfig = {
nodeRequire: require,
nodeMain: __filename,
@@ -76,7 +75,6 @@ function main() {
]
};
if (argv.coverage) {
var instrumenter = new istanbul.Instrumenter();
@@ -107,9 +105,7 @@ function main() {
if (seenSources[source]) {
return false;
}
// {{SQL CARBON EDIT}}
if (minimatch(source, SQL_TEST_GLOB)) {
if (minimatch(source, TEST_GLOB)) {
return false;
}
if (/fixtures/.test(source)) {
@@ -276,9 +272,38 @@ function main() {
process.stderr.write = write;
if (!argv.run && !argv.runGlob) {
// set up last test
suite('Loader', function () {
test('should not explode while loading', function () {
assert.ok(!didErr, 'should not explode while loading');
});
});
}
// {{SQL CARBON EDIT}}
/*
// report failing test for every unexpected error during any of the tests
var unexpectedErrors = [];
suite('Errors', function () {
test('should not have unexpected errors in tests', function () {
if (unexpectedErrors.length) {
unexpectedErrors.forEach(function (stack) {
console.error('');
console.error(stack);
});
assert.ok(false);
}
});
});
*/
// replace the default unexpected error handler to be useful during tests
loader(['vs/base/common/errors'], function(errors) {
errors.setUnexpectedErrorHandler(function (err) {
let stack = (err && err.stack) || (new Error().stack);
unexpectedErrors.push((err && err.message ? err.message : err) + '\n' + stack);
});
// fire up mocha

View File

@@ -2,4 +2,3 @@
--ui tdd
--timeout 10000
test/all.js

View File

@@ -1,64 +1,46 @@
# VS Code Smoke Testing
# VS Code Smoke Test
- Run `npm install`
- Start the tests: `npm test -- --latest "path/to/binary"`.
## How to run
If you want to include 'Data Migration' area tests use `npm test -- --latest path/to/binary --stable path/to/currentStable` respectively.
Detailed prerequisites and running steps are described [in our smoke test wiki](https://github.com/Microsoft/vscode/wiki/Smoke-Test#automated-smoke-test).
# Architecture
* `main.js` is used to prepare all smoke test dependencies (fetching key bindings and 'Express' repository, running `npm install` there).
* `mocha-runner.js` launches Mocha programmatically. It is spawned in Node environment from main.js to ensure that it is possible to listen on `stderr`s (primary `process.stderr` is not readable otherwise). This is accomplished because WebDriverIO command deprecation warnings need to be redirected to a separate log. Those warnings are coming from WebDriverIO because ChromeDriver has not migrated from JsonWire to W3C WebDriver protocol.
* `test.ts` contains the main smoke test suite calling the tests that are bundled in areas and defined in `./tests/`. It includes all tests separated into mocha `describe()` groups that represent each of the areas of [Smoke Test document](https://github.com/Microsoft/vscode/wiki/Smoke-Test).
* `./areas/` folder contains a `.ts` file per each area of the document. E.g. `'Search'` area goes under `'search.ts'`. Every area file contains a list of methods with the name that represents the action that can be performed in the corresponding test. This reduces the amount of test suite code and means that if the UI changes, the fix need only be applied in one place. The name of the method reflects the action the tester would do if he would perform the test manually. See [Selenium Page Objects Wiki](https://github.com/SeleniumHQ/selenium/wiki/PageObjects) and [Selenium Bot Style Tests Wiki](https://github.com/SeleniumHQ/selenium/wiki/Bot-Style-Tests) for a good explanation of the implementation. Every smoke test area contains methods that are used in a bot-style approach in `main.ts`.
* `./spectron/` wraps the Spectron, with WebDriverIO API wrapped in `client.ts` and instance of Spectron Application is wrapped in `application.ts`.
* `./test_data/` folder contains temporary data used by smoke test (cloned express repository, temporary user-data-dir/extensions-dir).
* `./test_data/screenshots` has action screenshots captured by a smoke test when performing actions during runtime. Screenshots are split in folders per each test.
# Adding new area
To contribute a new smoke test area, add `${area}.ts` file under `./areas/`. All related tests to the area should go to the alike named file under `./tests/${area}.ts`. This has to follow the bot-style approach described in the links mentioned above. Methods should be calling WebDriverIO API through `SpectronClient` class. If there is no existing WebDriverIO method, add it to the class.
# Adding new test
To add new test, `./test/${area}.ts` should be updated. The same instruction-style principle needs to be followed with the called area method names that reflect manual tester's actions.
# Debugging
1. Add the following configuration to launch.json, specifying binaries in `args`:
```json
{
"type": "node",
"request": "launch",
"name": "Launch Smoke Test",
"program": "${workspaceRoot}/test/smoke/out/main.js",
"cwd": "${workspaceRoot}/test/smoke",
"timeout": 240000,
"port": 9999,
"args": [
"-l",
"path/to/Code.exe"
],
"outFiles": [
"${cwd}/out/**/*.js"
]
}
```
2. In main.js add `--debug-brk=9999` as a first argument to the place where `out/mocha-runner.js` is spawned.
# Dev
npm run smoketest
# Screenshots
Almost on every automated test action it captures a screenshot. These help to determine an issue, if smoke test fails. The normal workflow is that you understand what code is doing and then try to match it up with screenshots obtained from the test.
# Specific build
npm run smoketest -- --build "path/to/code"
# Running "Out of Sources"
If you did a fix in VS Code that you need in order for the smoke test to succeed, here is how you can run the smoke test against the sources of VS Code:
* Set related environment variables in the console:
* `export NODE_ENV=development`
* `export VSCODE_DEV=1`
* `export VSCODE_CLI=1`
* open `application.ts`
* pass in the vscode folder as argument to the application
* e.g. instead of `args: args` type `args: ['/Users/bpasero/Development/vscode', ...args]`
* `cd test/smoke`
* `npm install`
* `npm test -- --latest <path to electron>`
* e.g. on macOS: `npm test -- --latest <path to vscode>/.build/electron/Code\ -\ OSS.app/Contents/MacOS/Electron`
# Data Migration tests
npm run smoketest -- --build "path/to/code-insiders" --stable "path/to/code"
```
The script calls mocha, so all mocha arguments should work fine. For example, use `-f Git` to only run the `Git` tests.
By default, screenshots are not captured. To run tests with screenshots use the argument `--screenshots`.
## Pitfalls
- Beware of **state**. The tests within a single suite will share the same state.
- Beware of **singletons**. This evil can, and will, manifest itself under the form of FS paths, TCP ports, IPC handles. Whenever writing a test, or setting up more smoke test architecture, make sure it can run simultaneously with any other tests and even itself. All test suites should be able to run many times in parallel.
- Beware of **focus**. **Never** depend on DOM elements having focus using `.focused` classes or `:focus` pseudo-classes, since they will lose that state as soon as another window appears on top of the running VS Code window. A safe approach which avoids this problem is to use the `waitForActiveElement` API. Many tests use this whenever they need to wait for a specific element to _have focus_.
- Beware of **timing**. You need to read from or write to the DOM... yeah I know. But is it the right time to do that? Can you 100% promise that that `input` box will be visible and in the DOM at this point in time? Or are you just hoping that it will be so? Every time you want to interact with the DOM, be absolutely sure that you can. Eg. just because you triggered Quick Open, it doesn't mean that it's open; you must wait for the widget to be in the DOM and for its input field to be the active element.
- Beware of **waiting**. **Never** wait longer than a couple of seconds for anything, unless it's justified. Think of it as a human using Code. Would a human take 10 minutes to run through the Search viewlet smoke test? Then, the computer should even be faster. **Don't** use `setTimeout` just because. Think about what you should wait for in the DOM to be ready, then wait for that instead.
## Common Issues
### Certain keys don't appear in input boxes (eg: <kbd>Space</kbd>)
This is a **waiting** issue. Everytime you send keys to Code, you must be aware that the keybinding service can handle them. Even if you're sure that input box is focused.
Here's an example: when opening quick open, focus goes from its list to its input. We used to simply wait for the input to have focus and then send some text to be typed, like `Workbench: Show Editor`; yet, only `Workbench:ShowEditor` would be rendered in the input box. This happened due to the fact that the [`ListService` takes 50ms to unset the context key which indicates a list is focused](https://github.com/Microsoft/vscode/blob/c8dee4c016d3a3d475011106e04d8e394d9f138c/src/vs/platform/list/browser/listService.ts#L59). The fix was to [wait 50ms as well on the smoke test](https://github.com/Microsoft/vscode/blob/b82fa8dcb06bbf9c85c1502d0d43322e2e9d1a59/test/smoke/src/areas/quickopen/quickopen.ts#L65).
### I type in a Monaco editor instance, but the text doesn't appear to be there
This is a **waiting** issue. When you type in a Monaco editor instance, you're really typing in a `textarea`. The `textarea` is then polled for its contents, then the editor model gets updated and finally the editor view gets updated. It's a good idea to always wait for the text to appear rendered in the editor after you type in it.
### I type in a Monaco editor instance, but the text appears scrambled
This is an issue which is **not yet fixed**. Unfortunately this seems to happen whenever the CPU load of the system is high. Rerunning the test will often result in a successful outcome.

View File

@@ -3,24 +3,28 @@
"version": "0.1.0",
"main": "./src/main.js",
"scripts": {
"compile": "tsc",
"pretest": "tsc",
"test": "node out/main.js"
"postinstall": "tsc",
"watch": "tsc --watch",
"mocha": "mocha"
},
"devDependencies": {
"@types/mocha": "^2.2.41",
"@types/node": "^6.0.70",
"@types/webdriverio": "^4.6.1",
"@types/electron": "~1.4.37",
"@types/rimraf": "^0.0.28",
"@types/htmlparser2": "^3.7.29",
"@types/htmlparser2": "3.7.29",
"@types/mkdirp": "0.5.1",
"@types/mocha": "2.2.41",
"@types/ncp": "2.0.1",
"@types/node": "8.0.33",
"@types/rimraf": "2.0.2",
"@types/webdriverio": "4.6.1",
"electron": "1.7.7",
"htmlparser2": "^3.9.2",
"mkdirp": "^0.5.1",
"mocha": "^3.2.0",
"spectron": "~3.6.4",
"typescript": "^2.2.2",
"ncp": "^2.0.0",
"portastic": "^1.0.1",
"rimraf": "^2.6.1",
"commander": "^2.9.0",
"simple-git": "^1.73.0",
"spectron": "^3.7.2",
"strip-json-comments": "^2.0.1",
"htmlparser2": "^3.9.2"
"tmp": "0.0.33",
"typescript": "2.5.2"
}
}

View File

@@ -0,0 +1,33 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Element } from 'webdriverio';
import { SpectronApplication } from '../../spectron/application';
export enum ActivityBarPosition {
LEFT = 0,
RIGHT = 1
};
export class ActivityBar {
constructor(private spectron: SpectronApplication) {
// noop
}
public async getActivityBar(position: ActivityBarPosition): Promise<Element> {
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.');
}
return this.spectron.client.waitForElement(`.part.activitybar.${positionClass}`);
}
}

View File

@@ -1,188 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SpectronApplication } from '../spectron/application';
import { 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

@@ -1,64 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SpectronApplication } from '../spectron/application';
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

@@ -1,62 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SpectronApplication } from '../spectron/application';
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,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 } from '../../spectron/application';
import { ProblemSeverity, Problems } from '../problems/problems';
describe('CSS', () => {
before(function () {
this.app.suiteName = 'CSS';
});
it('verifies quick outline', async function () {
const app = this.app as SpectronApplication;
await app.workbench.quickopen.openFile('style.css');
await app.workbench.editor.openOutline();
await app.workbench.quickopen.waitForQuickOpenElements(names => names.length === 2);
});
it('verifies warnings for the empty rule', async function () {
const app = this.app as SpectronApplication;
await app.workbench.quickopen.openFile('style.css');
await app.workbench.editor.waitForTypeInEditor('style.css', '.foo{}');
let warning = await app.client.waitForElement(Problems.getSelectorInEditor(ProblemSeverity.WARNING));
await app.screenCapturer.capture('CSS Warning in editor');
assert.ok(warning, `Warning squiggle is not shown in 'style.css'.`);
await app.workbench.problems.showProblemsView();
warning = await app.client.waitForElement(Problems.getSelectorInProblemsView(ProblemSeverity.WARNING));
await app.screenCapturer.capture('CSS Warning in problems view');
assert.ok(warning, 'Warning does not appear in Problems view.');
await app.workbench.problems.hideProblemsView();
});
it('verifies that warning becomes an error once setting changed', async function () {
const app = this.app as SpectronApplication;
await app.workbench.settingsEditor.addUserSetting('css.lint.emptyRules', '"error"');
await app.workbench.quickopen.openFile('style.css');
await app.workbench.editor.waitForTypeInEditor('style.css', '.foo{}');
let error = await app.client.waitForElement(Problems.getSelectorInEditor(ProblemSeverity.ERROR));
await app.screenCapturer.capture('CSS Error in editor');
assert.ok(error, `Warning squiggle is not shown in 'style.css'.`);
const problems = new Problems(app);
await problems.showProblemsView();
error = await app.client.waitForElement(Problems.getSelectorInProblemsView(ProblemSeverity.ERROR));
await app.screenCapturer.capture('CSS Error in probles view');
assert.ok(error, 'Warning does not appear in Problems view.');
await problems.hideProblemsView();
});
});

View File

@@ -1,26 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SpectronApplication } from '../spectron/application';
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,154 @@
/*---------------------------------------------------------------------------------------------
* 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 * as http from 'http';
import * as os from 'os';
import * as path from 'path';
import * as fs from 'fs';
import * as stripJsonComments from 'strip-json-comments';
import { SpectronApplication, Quality } from '../../spectron/application';
describe('Debug', () => {
before(async function () {
const app = this.app as SpectronApplication;
if (app.quality === Quality.Dev) {
const extensionsPath = path.join(os.homedir(), '.vscode-oss-dev', 'extensions');
const debugPath = path.join(extensionsPath, 'vscode-node-debug');
const debugExists = fs.existsSync(debugPath);
const debug2Path = path.join(extensionsPath, 'vscode-node-debug2');
const debug2Exists = fs.existsSync(debug2Path);
if (!debugExists) {
console.warn(`Skipping debug tests because vscode-node-debug extension was not found in ${extensionsPath}`);
return;
}
if (!debug2Exists) {
console.warn(`Skipping debug tests because vscode-node-debug2 extension was not found in ${extensionsPath}`);
return;
}
await new Promise((c, e) => fs.symlink(debugPath, path.join(app.extensionsPath, 'vscode-node-debug'), err => err ? e(err) : c()));
await new Promise((c, e) => fs.symlink(debug2Path, path.join(app.extensionsPath, 'vscode-node-debug2'), err => err ? e(err) : c()));
await app.reload();
}
this.app.suiteName = 'Debug';
});
it('configure launch json', async function () {
const app = this.app as SpectronApplication;
await app.workbench.debug.openDebugViewlet();
await app.workbench.quickopen.openFile('app.js');
await app.workbench.debug.configure();
const launchJsonPath = path.join(app.workspacePath, '.vscode', 'launch.json');
const content = fs.readFileSync(launchJsonPath, 'utf8');
const config = JSON.parse(stripJsonComments(content));
config.configurations[0].protocol = 'inspector';
fs.writeFileSync(launchJsonPath, JSON.stringify(config, undefined, 4), 'utf8');
await app.workbench.editor.waitForEditorContents('launch.json', contents => /"protocol": "inspector"/.test(contents));
await app.screenCapturer.capture('launch.json file');
assert.equal(config.configurations[0].request, 'launch');
assert.equal(config.configurations[0].type, 'node');
if (process.platform === 'win32') {
assert.equal(config.configurations[0].program, '${workspaceFolder}\\bin\\www');
} else {
assert.equal(config.configurations[0].program, '${workspaceFolder}/bin/www');
}
});
it('breakpoints', async function () {
const app = this.app as SpectronApplication;
await app.workbench.quickopen.openFile('index.js');
await app.workbench.debug.setBreakpointOnLine(6);
await app.screenCapturer.capture('breakpoints are set');
});
let port: number;
it('start debugging', async function () {
const app = this.app as SpectronApplication;
port = await app.workbench.debug.startDebugging();
await app.screenCapturer.capture('debugging has started');
await new Promise((c, e) => {
const request = http.get(`http://localhost:${port}`);
request.on('error', e);
app.workbench.debug.waitForStackFrame(sf => sf.name === 'index.js' && sf.lineNumber === 6, 'looking for index.js and line 6').then(c, e);
});
await app.screenCapturer.capture('debugging is paused');
});
it('focus stack frames and variables', async function () {
const app = this.app as SpectronApplication;
await app.client.waitFor(() => app.workbench.debug.getLocalVariableCount(), c => c === 4, 'there should be 4 local variables');
await app.workbench.debug.focusStackFrame('layer.js', 'looking for layer.js');
await app.client.waitFor(() => app.workbench.debug.getLocalVariableCount(), c => c === 5, 'there should be 5 local variables');
await app.workbench.debug.focusStackFrame('route.js', 'looking for route.js');
await app.client.waitFor(() => app.workbench.debug.getLocalVariableCount(), c => c === 3, 'there should be 3 local variables');
await app.workbench.debug.focusStackFrame('index.js', 'looking for index.js');
await app.client.waitFor(() => app.workbench.debug.getLocalVariableCount(), c => c === 4, 'there should be 4 local variables');
});
it('stepOver, stepIn, stepOut', async function () {
const app = this.app as SpectronApplication;
await app.workbench.debug.stepIn();
await app.screenCapturer.capture('debugging has stepped in');
const first = await app.workbench.debug.waitForStackFrame(sf => sf.name === 'response.js', 'looking for response.js');
await app.workbench.debug.stepOver();
await app.screenCapturer.capture('debugging has stepped over');
await app.workbench.debug.waitForStackFrame(sf => sf.name === 'response.js' && sf.lineNumber === first.lineNumber + 1, `looking for response.js and line ${first.lineNumber + 1}`);
await app.workbench.debug.stepOut();
await app.screenCapturer.capture('debugging has stepped out');
await app.workbench.debug.waitForStackFrame(sf => sf.name === 'index.js' && sf.lineNumber === 7, `looking for index.js and line 7`);
});
it('continue', async function () {
const app = this.app as SpectronApplication;
await app.workbench.debug.continue();
await app.screenCapturer.capture('debugging has continued');
await new Promise((c, e) => {
const request = http.get(`http://localhost:${port}`);
request.on('error', e);
app.workbench.debug.waitForStackFrame(sf => sf.name === 'index.js' && sf.lineNumber === 6, `looking for index.js and line 6`).then(c, e);
});
await app.screenCapturer.capture('debugging is paused');
});
it('debug console', async function () {
const app = this.app as SpectronApplication;
await app.workbench.debug.waitForReplCommand('2 + 2', r => r === '4');
});
it('stop debugging', async function () {
const app = this.app as SpectronApplication;
await app.workbench.debug.stopDebugging();
await app.screenCapturer.capture('debugging has stopped');
});
});

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 { SpectronApplication } from '../../spectron/application';
import { Viewlet } from '../workbench/viewlet';
const VIEWLET = 'div[id="workbench.view.debug"]';
const DEBUG_VIEW = `${VIEWLET} .debug-view-content`;
const CONFIGURE = `div[id="workbench.parts.sidebar"] .actions-container .configure`;
const START = `.icon[title="Start Debugging"]`;
const STOP = `.debug-actions-widget .debug-action.stop`;
const STEP_OVER = `.debug-actions-widget .debug-action.step-over`;
const STEP_IN = `.debug-actions-widget .debug-action.step-into`;
const STEP_OUT = `.debug-actions-widget .debug-action.step-out`;
const CONTINUE = `.debug-actions-widget .debug-action.continue`;
const GLYPH_AREA = '.margin-view-overlays>:nth-child';
const BREAKPOINT_GLYPH = '.debug-breakpoint-glyph';
const PAUSE = `.debug-actions-widget .debug-action.pause`;
const DEBUG_STATUS_BAR = `.statusbar.debugging`;
const NOT_DEBUG_STATUS_BAR = `.statusbar:not(debugging)`;
const TOOLBAR_HIDDEN = `.debug-actions-widget.builder-hidden`;
const STACK_FRAME = `${VIEWLET} .monaco-tree-row .stack-frame`;
const VARIABLE = `${VIEWLET} .debug-variables .monaco-tree-row .expression`;
const CONSOLE_OUTPUT = `.repl .output.expression`;
const CONSOLE_INPUT_OUTPUT = `.repl .input-output-pair .output.expression .value`;
const REPL_FOCUSED = '.repl-input-wrapper .monaco-editor textarea';
export interface IStackFrame {
id: string;
name: string;
lineNumber: number;
}
export class Debug extends Viewlet {
constructor(spectron: SpectronApplication) {
super(spectron);
}
async openDebugViewlet(): Promise<any> {
await this.spectron.runCommand('workbench.view.debug');
await this.spectron.client.waitForElement(DEBUG_VIEW);
}
async configure(): Promise<any> {
await this.spectron.client.waitAndClick(CONFIGURE);
await this.spectron.workbench.waitForEditorFocus('launch.json');
}
async setBreakpointOnLine(lineNumber: number): Promise<any> {
await this.spectron.client.waitForElement(`${GLYPH_AREA}(${lineNumber})`);
await this.spectron.client.leftClick(`${GLYPH_AREA}(${lineNumber})`, 5, 5);
await this.spectron.client.waitForElement(BREAKPOINT_GLYPH);
}
async startDebugging(): Promise<number> {
await this.spectron.client.waitAndClick(START);
await this.spectron.client.waitForElement(PAUSE);
await this.spectron.client.waitForElement(DEBUG_STATUS_BAR);
const portPrefix = 'Port: ';
await this.spectron.client.waitFor(async () => {
const output = await this.getConsoleOutput();
return output.join('');
}, text => !!text && text.indexOf(portPrefix) >= 0);
const output = await this.getConsoleOutput();
const lastOutput = output.pop();
return lastOutput ? parseInt(lastOutput.substr(portPrefix.length)) : 3000;
}
async stepOver(): Promise<any> {
await this.spectron.client.waitAndClick(STEP_OVER);
}
async stepIn(): Promise<any> {
await this.spectron.client.waitAndClick(STEP_IN);
}
async stepOut(): Promise<any> {
await this.spectron.client.waitAndClick(STEP_OUT);
}
async continue(): Promise<any> {
await this.spectron.client.waitAndClick(CONTINUE);
await this.waitForStackFrameLength(0);
}
async stopDebugging(): Promise<any> {
await this.spectron.client.waitAndClick(STOP);
await this.spectron.client.waitForElement(TOOLBAR_HIDDEN);
await this.spectron.client.waitForElement(NOT_DEBUG_STATUS_BAR);
}
async waitForStackFrame(func: (stackFrame: IStackFrame) => boolean, message: string): Promise<IStackFrame> {
return await this.spectron.client.waitFor(async () => {
const stackFrames = await this.getStackFrames();
return stackFrames.filter(func)[0];
}, void 0, `Waiting for Stack Frame: ${message}`);
}
async waitForStackFrameLength(length: number): Promise<any> {
return await this.spectron.client.waitFor(() => this.getStackFrames(), stackFrames => stackFrames.length === length);
}
async focusStackFrame(name: string, message: string): Promise<any> {
const stackFrame = await this.waitForStackFrame(sf => sf.name === name, message);
await this.spectron.client.spectron.client.elementIdClick(stackFrame.id);
await this.spectron.workbench.waitForTab(name);
}
async waitForReplCommand(text: string, accept: (result: string) => boolean): Promise<void> {
await this.spectron.workbench.quickopen.runCommand('Debug: Focus Debug Console');
await this.spectron.client.waitForActiveElement(REPL_FOCUSED);
await this.spectron.client.setValue(REPL_FOCUSED, text);
// Wait for the keys to be picked up by the editor model such that repl evalutes what just got typed
await this.spectron.workbench.editor.waitForEditorContents('debug:input', s => s.indexOf(text) >= 0);
await this.spectron.client.keys(['Enter', 'NULL']);
await this.spectron.client.waitForElement(CONSOLE_INPUT_OUTPUT);
await this.spectron.client.waitFor(async () => {
const result = await this.getConsoleOutput();
return result[result.length - 1] || '';
}, accept);
}
async getLocalVariableCount(): Promise<number> {
return await this.spectron.webclient.selectorExecute(VARIABLE, div => (Array.isArray(div) ? div : [div]).length);
}
async getStackFramesLength(): Promise<number> {
const stackFrames = await this.getStackFrames();
return stackFrames.length;
}
private async getStackFrames(): Promise<IStackFrame[]> {
const result = await this.spectron.webclient.selectorExecute(STACK_FRAME,
div => (Array.isArray(div) ? div : [div]).map(element => {
const name = element.querySelector('.file-name') as HTMLElement;
const line = element.querySelector('.line-number') as HTMLElement;
const lineNumber = line.textContent ? parseInt(line.textContent.split(':').shift() || '0') : 0;
return {
name: name.textContent,
lineNumber,
element
};
})
);
if (!Array.isArray(result)) {
return [];
}
return result
.map(({ name, lineNumber, element }) => ({ name, lineNumber, id: element.ELEMENT }));
}
private async getConsoleOutput(): Promise<string[]> {
const result = await this.spectron.webclient.selectorExecute(CONSOLE_OUTPUT,
div => (Array.isArray(div) ? div : [div]).map(element => {
const value = element.querySelector('.value') as HTMLElement;
return value && value.textContent;
}).filter(line => !!line)
);
return result;
}
}

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 { SpectronApplication } from '../../spectron/application';
describe('Editor', () => {
before(function () {
this.app.suiteName = 'Editor';
});
it('shows correct quick outline', async function () {
const app = this.app as SpectronApplication;
await app.workbench.quickopen.openFile('www');
await app.workbench.editor.openOutline();
await app.workbench.quickopen.waitForQuickOpenElements(names => names.length >= 6);
});
it(`finds 'All References' to 'app'`, async function () {
const app = this.app as SpectronApplication;
await app.workbench.quickopen.openFile('www');
const references = await app.workbench.editor.findReferences('app', 7);
await references.waitForReferencesCountInTitle(3);
await references.waitForReferencesCount(3);
await references.close();
});
it(`renames local 'app' variable`, async function () {
const app = this.app as SpectronApplication;
await app.workbench.quickopen.openFile('www');
await app.workbench.editor.rename('www', 7, 'app', 'newApp');
await app.workbench.editor.waitForEditorContents('www', contents => contents.indexOf('newApp') > -1);
await app.screenCapturer.capture('Rename result');
});
// it('folds/unfolds the code correctly', async function () {
// await app.workbench.quickopen.openFile('www');
// // Fold
// await app.workbench.editor.foldAtLine(3);
// await app.workbench.editor.waitUntilShown(3);
// await app.workbench.editor.waitUntilHidden(4);
// await app.workbench.editor.waitUntilHidden(5);
// // Unfold
// await app.workbench.editor.unfoldAtLine(3);
// await app.workbench.editor.waitUntilShown(3);
// await app.workbench.editor.waitUntilShown(4);
// await app.workbench.editor.waitUntilShown(5);
// });
it(`verifies that 'Go To Definition' works`, async function () {
const app = this.app as SpectronApplication;
await app.workbench.quickopen.openFile('app.js');
await app.workbench.editor.gotoDefinition('express', 11);
await app.workbench.waitForActiveTab('index.d.ts');
});
it(`verifies that 'Peek Definition' works`, async function () {
const app = this.app as SpectronApplication;
await app.workbench.quickopen.openFile('app.js');
const peek = await app.workbench.editor.peekDefinition('express', 11);
await peek.waitForFile('index.d.ts');
});
});

View File

@@ -0,0 +1,194 @@
/*---------------------------------------------------------------------------------------------
* 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 { QuickOutline } from './quickoutline';
import { References } from './peek';
const RENAME_BOX = '.monaco-editor .monaco-editor.rename-box';
const RENAME_INPUT = `${RENAME_BOX} .rename-input`;
export class Editor {
private static VIEW_LINES = '.monaco-editor .view-lines';
private static LINE_NUMBERS = '.monaco-editor .margin .margin-view-overlays .line-numbers';
private static FOLDING_EXPANDED = '.monaco-editor .margin .margin-view-overlays>:nth-child(${INDEX}) .folding';
private static FOLDING_COLLAPSED = `${Editor.FOLDING_EXPANDED}.collapsed`;
constructor(private spectron: SpectronApplication) {
}
async openOutline(): Promise<QuickOutline> {
const outline = new QuickOutline(this.spectron);
await outline.open();
return outline;
}
async findReferences(term: string, line: number): Promise<References> {
await this.clickOnTerm(term, line);
await this.spectron.workbench.quickopen.runCommand('Find All References');
const references = new References(this.spectron);
await references.waitUntilOpen();
return references;
}
async rename(filename: string, line: number, from: string, to: string): Promise<void> {
await this.clickOnTerm(from, line);
await this.spectron.workbench.quickopen.runCommand('Rename Symbol');
await this.spectron.client.waitForActiveElement(RENAME_INPUT);
await this.spectron.client.setValue(RENAME_INPUT, to);
await this.spectron.client.keys(['Enter', 'NULL']);
}
async gotoDefinition(term: string, line: number): Promise<void> {
await this.clickOnTerm(term, line);
await this.spectron.workbench.quickopen.runCommand('Go to Definition');
}
async peekDefinition(term: string, line: number): Promise<References> {
await this.clickOnTerm(term, line);
await this.spectron.workbench.quickopen.runCommand('Peek Definition');
const peek = new References(this.spectron);
await peek.waitUntilOpen();
return peek;
}
async waitForHighlightingLine(line: number): Promise<void> {
const currentLineIndex = await this.getViewLineIndex(line);
if (currentLineIndex) {
await this.spectron.client.waitForElement(`.monaco-editor .view-overlays>:nth-child(${currentLineIndex}) .current-line`);
return;
}
throw new Error('Cannot find line ' + line);
}
async getSelector(term: string, line: number): Promise<string> {
const lineIndex = await this.getViewLineIndex(line);
const classNames = await this.spectron.client.waitFor(() => this.getClassSelectors(term, lineIndex), classNames => classNames && !!classNames.length, 'Getting class names for editor lines');
return `${Editor.VIEW_LINES}>:nth-child(${lineIndex}) span span.${classNames[0]}`;
}
async foldAtLine(line: number): Promise<any> {
const lineIndex = await this.getViewLineIndex(line);
await this.spectron.client.waitAndClick(Editor.FOLDING_EXPANDED.replace('${INDEX}', '' + lineIndex));
await this.spectron.client.waitForElement(Editor.FOLDING_COLLAPSED.replace('${INDEX}', '' + lineIndex));
}
async unfoldAtLine(line: number): Promise<any> {
const lineIndex = await this.getViewLineIndex(line);
await this.spectron.client.waitAndClick(Editor.FOLDING_COLLAPSED.replace('${INDEX}', '' + lineIndex));
await this.spectron.client.waitForElement(Editor.FOLDING_EXPANDED.replace('${INDEX}', '' + lineIndex));
}
async waitUntilHidden(line: number): Promise<void> {
await this.spectron.client.waitFor<number>(() => this.getViewLineIndexWithoutWait(line), lineNumber => lineNumber === undefined, 'Waiting until line number is hidden');
}
async waitUntilShown(line: number): Promise<void> {
await this.getViewLineIndex(line);
}
async clickOnTerm(term: string, line: number): Promise<void> {
const selector = await this.getSelector(term, line);
await this.spectron.client.waitAndClick(selector);
}
async waitForTypeInEditor(filename: string, text: string, selectorPrefix = ''): Promise<any> {
const editor = [
selectorPrefix || '',
`.monaco-editor[data-uri$="${filename}"]`
].join(' ');
await this.spectron.client.element(editor);
const textarea = `${editor} textarea`;
await this.spectron.client.waitForActiveElement(textarea);
// https://github.com/Microsoft/vscode/issues/34203#issuecomment-334441786
await this.spectron.client.spectron.client.selectorExecute(textarea, (elements, text) => {
const textarea = (Array.isArray(elements) ? elements : [elements])[0] as HTMLTextAreaElement;
const start = textarea.selectionStart;
const newStart = start + text.length;
const value = textarea.value;
const newValue = value.substr(0, start) + text + value.substr(start);
textarea.value = newValue;
textarea.setSelectionRange(newStart, newStart);
const event = new Event('input', { 'bubbles': true, 'cancelable': true });
textarea.dispatchEvent(event);
}, text);
await this.waitForEditorContents(filename, c => c.indexOf(text) > -1, selectorPrefix);
}
async waitForEditorContents(filename: string, accept: (contents: string) => boolean, selectorPrefix = ''): Promise<any> {
const selector = [
selectorPrefix || '',
`.monaco-editor[data-uri$="${filename}"] .view-lines`
].join(' ');
return this.spectron.client.waitForTextContent(selector, undefined, c => accept(c.replace(/\u00a0/g, ' ')));
}
async waitForActiveEditor(filename: string): Promise<any> {
const selector = `.editor-container .monaco-editor[data-uri$="${filename}"] textarea`;
return this.spectron.client.waitForActiveElement(selector);
}
// async waitForActiveEditorFirstLineText(filename: string): Promise<string> {
// const selector = `.editor-container .monaco-editor[data-uri$="${filename}"] textarea`;
// const result = await this.spectron.client.waitFor(
// () => this.spectron.client.spectron.client.execute(s => {
// if (!document.activeElement.matches(s)) {
// return undefined;
// }
// let element: Element | null = document.activeElement;
// while (element && !/monaco-editor/.test(element.className) && element !== document.body) {
// element = element.parentElement;
// }
// if (element && /monaco-editor/.test(element.className)) {
// const firstLine = element.querySelector('.view-lines span span:nth-child(1)');
// if (firstLine) {
// return (firstLine.textContent || '').replace(/\u00a0/g, ' '); // DAMN
// }
// }
// return undefined;
// }, selector),
// r => typeof r.value === 'string',
// `wait for active editor first line: ${selector}`
// );
// return result.value;
// }
private async getClassSelectors(term: string, viewline: number): Promise<string[]> {
const result: { text: string, className: string }[] = await this.spectron.webclient.selectorExecute(`${Editor.VIEW_LINES}>:nth-child(${viewline}) span span`,
elements => (Array.isArray(elements) ? elements : [elements])
.map(element => ({ text: element.textContent, className: element.className })));
return result.filter(r => r.text === term).map(({ className }) => className);
}
private async getViewLineIndex(line: number): Promise<number> {
return await this.spectron.client.waitFor<number>(() => this.getViewLineIndexWithoutWait(line), void 0, 'Getting line index');
}
private async getViewLineIndexWithoutWait(line: number): Promise<number | undefined> {
const lineNumbers = await this.spectron.webclient.selectorExecute(Editor.LINE_NUMBERS,
elements => (Array.isArray(elements) ? elements : [elements]).map(element => element.textContent));
for (let index = 0; index < lineNumbers.length; index++) {
if (lineNumbers[index] === `${line}`) {
return index + 1;
}
}
return undefined;
}
}

View File

@@ -0,0 +1,41 @@
/*---------------------------------------------------------------------------------------------
* 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 References {
private static REFERENCES_WIDGET = '.monaco-editor .zone-widget .zone-widget-container.peekview-widget.reference-zone-widget.results-loaded';
private static REFERENCES_TITLE_FILE_NAME = `${References.REFERENCES_WIDGET} .head .peekview-title .filename`;
private static REFERENCES_TITLE_COUNT = `${References.REFERENCES_WIDGET} .head .peekview-title .meta`;
private static REFERENCES = `${References.REFERENCES_WIDGET} .body .ref-tree.inline .monaco-tree-row .reference`;
constructor(private spectron: SpectronApplication) {
}
public async waitUntilOpen(): Promise<void> {
await this.spectron.client.waitForElement(References.REFERENCES_WIDGET);
}
public async waitForReferencesCountInTitle(count: number): Promise<void> {
await this.spectron.client.waitForText(References.REFERENCES_TITLE_COUNT, void 0, titleCount => {
const matches = titleCount.match(/\d+/);
return matches ? parseInt(matches[0]) === count : false;
});
}
public async waitForReferencesCount(count: number): Promise<void> {
await this.spectron.client.waitForElements(References.REFERENCES, result => result && result.length === count);
}
public async waitForFile(file: string): Promise<void> {
await this.spectron.client.waitForText(References.REFERENCES_TITLE_FILE_NAME, file);
}
public async close(): Promise<void> {
await this.spectron.client.keys(['Escape', 'NULL']);
await this.spectron.client.waitForElement(References.REFERENCES_WIDGET, element => !element);
}
}

View File

@@ -0,0 +1,28 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SpectronApplication } from '../../spectron/application';
import { QuickOpen } from '../quickopen/quickopen';
export class QuickOutline extends QuickOpen {
constructor(spectron: SpectronApplication) {
super(spectron);
}
public async open(): Promise<void> {
await this.spectron.client.waitFor(async () => {
await this.spectron.runCommand('workbench.action.gotoSymbol');
const entry = await this.spectron.client.element('div[aria-label="Quick Picker"] .monaco-tree-rows.show-twisties div.monaco-tree-row .quick-open-entry');
if (entry) {
const text = await this.spectron.client.getText('div[aria-label="Quick Picker"] .monaco-tree-rows.show-twisties div.monaco-tree-row .quick-open-entry .monaco-icon-label .label-name .monaco-highlighted-label span');
if (text !== 'No symbol information for the file') {
return entry;
}
}
await this.closeQuickOpen();
}, undefined, 'Opening Outline');
}
}

View File

@@ -0,0 +1,42 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SpectronApplication } from '../../spectron/application';
describe('Explorer', () => {
before(function () {
this.app.suiteName = 'Explorer';
});
it('quick open search produces correct result', async function () {
const app = this.app as SpectronApplication;
const expectedNames = [
'.eslintrc.json',
'tasks.json',
'app.js',
'index.js',
'users.js',
'package.json',
'jsconfig.json'
];
await app.workbench.quickopen.openQuickOpen('.js');
await app.workbench.quickopen.waitForQuickOpenElements(names => expectedNames.every(n => names.some(m => n === m)));
await app.client.keys(['Escape', 'NULL']);
});
it('quick open respects fuzzy matching', async function () {
const app = this.app as SpectronApplication;
const expectedNames = [
'tasks.json',
'app.js',
'package.json'
];
await app.workbench.quickopen.openQuickOpen('a.s');
await app.workbench.quickopen.waitForQuickOpenElements(names => expectedNames.every(n => names.some(m => n === m)));
await app.client.keys(['Escape', 'NULL']);
});
});

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 { SpectronApplication } from '../../spectron/application';
import { Viewlet } from '../workbench/viewlet';
export class Explorer extends Viewlet {
private static EXPLORER_VIEWLET = 'div[id="workbench.view.explorer"]';
private static OPEN_EDITORS_VIEW = `${Explorer.EXPLORER_VIEWLET} .split-view-view:nth-child(1) .title`;
constructor(spectron: SpectronApplication) {
super(spectron);
}
public openExplorerView(): Promise<any> {
return this.spectron.runCommand('workbench.view.explorer');
}
public getOpenEditorsViewTitle(): Promise<string> {
return this.spectron.client.waitForText(Explorer.OPEN_EDITORS_VIEW);
}
public async openFile(fileName: string): Promise<any> {
await this.spectron.client.doubleClickAndWait(`div[class="monaco-icon-label file-icon ${fileName}-name-file-icon ${this.getExtensionSelector(fileName)} explorer-item"]`);
await this.spectron.workbench.waitForEditorFocus(fileName);
}
public getExtensionSelector(fileName: string): string {
const extension = fileName.split('.')[1];
if (extension === 'js') {
return 'js-ext-file-icon ext-file-icon javascript-lang-file-icon';
} else if (extension === 'json') {
return 'json-ext-file-icon ext-file-icon json-lang-file-icon';
} else if (extension === 'md') {
return 'md-ext-file-icon ext-file-icon markdown-lang-file-icon';
}
throw new Error('No class defined for this file extension');
}
}

View File

@@ -1,112 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SpectronApplication } from '../spectron/application';
import { 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,36 @@
/*---------------------------------------------------------------------------------------------
* 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, Quality } from '../../spectron/application';
describe('Extensions', () => {
before(function () {
this.app.suiteName = 'Extensions';
});
it(`install and activate vscode-smoketest-check extension`, async function () {
const app = this.app as SpectronApplication;
if (app.quality === Quality.Dev) {
this.skip();
return;
}
const extensionName = 'vscode-smoketest-check';
await app.workbench.extensions.openExtensionsViewlet();
const installed = await app.workbench.extensions.installExtension(extensionName);
assert.ok(installed);
await app.reload();
await app.workbench.extensions.waitForExtensionsViewlet();
await app.workbench.quickopen.runCommand('Smoke Test Check');
const statusbarText = await app.workbench.statusbar.getStatusbarTextByTitle('smoke test');
await app.screenCapturer.capture('Statusbar');
assert.equal(statusbarText, 'VS Code Smoke Test Check');
});
});

View File

@@ -0,0 +1,42 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SpectronApplication } from '../../spectron/application';
import { Viewlet } from '../workbench/viewlet';
const SEARCH_BOX = 'div.extensions-viewlet[id="workbench.view.extensions"] input.search-box';
export class Extensions extends Viewlet {
constructor(spectron: SpectronApplication) {
super(spectron);
}
async openExtensionsViewlet(): Promise<any> {
await this.spectron.runCommand('workbench.view.extensions');
await this.waitForExtensionsViewlet();
}
async waitForExtensionsViewlet(): Promise<any> {
await this.spectron.client.waitForActiveElement(SEARCH_BOX);
}
async searchForExtension(name: string): Promise<any> {
await this.spectron.client.click(SEARCH_BOX);
await this.spectron.client.waitForActiveElement(SEARCH_BOX);
await this.spectron.client.setValue(SEARCH_BOX, name);
}
async installExtension(name: string): Promise<boolean> {
await this.searchForExtension(name);
// we might want to wait for a while longer since the Marketplace can be slow
// a minute should do
await this.spectron.client.waitFor(() => this.spectron.client.click(`div.extensions-viewlet[id="workbench.view.extensions"] .monaco-list-row[aria-label="${name}"] .extension li[class='action-item'] .extension-action.install`), void 0, 'waiting for install button', 600);
await this.spectron.client.waitForElement(`div.extensions-viewlet[id="workbench.view.extensions"] .monaco-list-row[aria-label="${name}"] .extension li[class='action-item'] .extension-action.reload`);
return true;
}
}

View File

@@ -1,167 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SpectronApplication } from '../spectron/application';
import { 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,99 @@
/*---------------------------------------------------------------------------------------------
* 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 * as cp from 'child_process';
import { SpectronApplication } from '../../spectron/application';
const DIFF_EDITOR_LINE_INSERT = '.monaco-diff-editor .editor.modified .line-insert';
const SYNC_STATUSBAR = 'div[id="workbench.parts.statusbar"] .statusbar-entry a[title$="Synchronize Changes"]';
describe('Git', () => {
before(function () {
this.app.suiteName = 'Git';
});
it('reflects working tree changes', async function () {
const app = this.app as SpectronApplication;
await app.workbench.scm.openSCMViewlet();
await app.workbench.quickopen.openFile('app.js');
await app.workbench.editor.waitForTypeInEditor('app.js', '.foo{}');
await app.workbench.saveOpenedFile();
await app.workbench.quickopen.openFile('index.jade');
await app.workbench.editor.waitForTypeInEditor('index.jade', 'hello world');
await app.workbench.saveOpenedFile();
await app.workbench.scm.refreshSCMViewlet();
const appJs = await app.workbench.scm.waitForChange(c => c.name === 'app.js');
const indexJade = await app.workbench.scm.waitForChange(c => c.name === 'index.jade');
await app.screenCapturer.capture('changes');
assert.equal(appJs.name, 'app.js');
assert.equal(appJs.type, 'Modified');
assert.equal(indexJade.name, 'index.jade');
assert.equal(indexJade.type, 'Modified');
});
it('opens diff editor', async function () {
const app = this.app as SpectronApplication;
await app.workbench.scm.openSCMViewlet();
const appJs = await app.workbench.scm.waitForChange(c => c.name === 'app.js');
await app.workbench.scm.openChange(appJs);
await app.client.waitForElement(DIFF_EDITOR_LINE_INSERT);
});
it('stages correctly', async function () {
const app = this.app as SpectronApplication;
// TODO@joao get these working once joh fixes scm viewlet
if (!false) {
this.skip();
return;
}
await app.workbench.scm.openSCMViewlet();
const appJs = await app.workbench.scm.waitForChange(c => c.name === 'app.js' && c.type === 'Modified');
await app.workbench.scm.stage(appJs);
const indexAppJs = await app.workbench.scm.waitForChange(c => c.name === 'app.js' && c.type === 'Index Modified');
await app.workbench.scm.unstage(indexAppJs);
await app.workbench.scm.waitForChange(c => c.name === 'app.js' && c.type === 'Modified');
});
it(`stages, commits changes and verifies outgoing change`, async function () {
const app = this.app as SpectronApplication;
// TODO@joao get these working once joh fixes scm viewlet
if (!false) {
cp.execSync('git reset --hard origin/master', { cwd: app.workspacePath });
this.skip();
return;
}
await app.workbench.scm.openSCMViewlet();
const appJs = await app.workbench.scm.waitForChange(c => c.name === 'app.js' && c.type === 'Modified');
await app.workbench.scm.stage(appJs);
await app.workbench.scm.waitForChange(c => c.name === 'app.js' && c.type === 'Index Modified');
await app.workbench.scm.commit('first commit');
await app.client.waitForText(SYNC_STATUSBAR, ' 0↓ 1↑');
await app.workbench.quickopen.runCommand('Git: Stage All Changes');
await app.workbench.scm.waitForChange(c => c.name === 'index.jade' && c.type === 'Index Modified');
await app.workbench.scm.commit('second commit');
await app.client.waitForText(SYNC_STATUSBAR, ' 0↓ 2↑');
cp.execSync('git reset --hard origin/master', { cwd: app.workspacePath });
});
});

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 * as assert from 'assert';
import { SpectronApplication } from '../../spectron/application';
import { Viewlet } from '../workbench/viewlet';
const VIEWLET = 'div[id="workbench.view.scm"]';
const SCM_INPUT = `${VIEWLET} .scm-editor textarea`;
const SCM_RESOURCE = `${VIEWLET} .monaco-list-row > .resource`;
const SCM_RESOURCE_GROUP = `${VIEWLET} .monaco-list-row > .resource-group`;
const REFRESH_COMMAND = `div[id="workbench.parts.sidebar"] .actions-container a.action-label[title="Refresh"]`;
const COMMIT_COMMAND = `div[id="workbench.parts.sidebar"] .actions-container a.action-label[title="Commit"]`;
const SCM_RESOURCE_CLICK = name => `${SCM_RESOURCE} .monaco-icon-label[title$="${name}"]`;
const SCM_RESOURCE_GROUP_COMMAND_CLICK = name => `${SCM_RESOURCE_GROUP} .actions .action-label[title="${name}"]`;
export interface Change {
id: string;
name: string;
type: string;
actions: { id: string, title: string; }[];
}
export class SCM extends Viewlet {
constructor(spectron: SpectronApplication) {
super(spectron);
}
async openSCMViewlet(): Promise<any> {
await this.spectron.runCommand('workbench.view.scm');
await this.spectron.client.waitForElement(SCM_INPUT);
}
async waitForChange(func: (change: Change) => boolean): Promise<Change> {
return await this.spectron.client.waitFor(async () => {
const changes = await this.getChanges();
return changes.filter(func)[0];
}, void 0, 'Getting changes');
}
async refreshSCMViewlet(): Promise<any> {
await this.spectron.client.click(REFRESH_COMMAND);
}
async getChanges(): Promise<Change[]> {
const result = await this.spectron.webclient.selectorExecute(SCM_RESOURCE,
div => (Array.isArray(div) ? div : [div]).map(element => {
const name = element.querySelector('.label-name') as HTMLElement;
const icon = element.querySelector('.monaco-icon-label') as HTMLElement;
const actionElementList = element.querySelectorAll('.actions .action-label');
const actionElements: any[] = [];
for (let i = 0; i < actionElementList.length; i++) {
const element = actionElementList.item(i) as HTMLElement;
actionElements.push({ element, title: element.title });
}
return {
name: name.textContent,
type: (icon.title || '').replace(/^([^,]+),.*$/, '$1'),
element,
actionElements
};
})
);
return result.map(({ name, type, element, actionElements }) => {
// const actions = actionElements.reduce((r, { element, title }) => r[title] = element.ELEMENT, {});
const actions = actionElements.map(({ element, title }) => ({ id: element.ELEMENT, title }));
return { name, type, id: element.ELEMENT, actions };
});
}
async openChange(change: Change): Promise<void> {
await this.spectron.client.waitAndClick(SCM_RESOURCE_CLICK(change.name));
}
async stage(change: Change): Promise<void> {
const action = change.actions.filter(a => a.title === 'Stage Changes')[0];
assert(action);
await this.spectron.client.spectron.client.elementIdClick(action.id);
}
async stageAll(): Promise<void> {
await this.spectron.client.waitAndClick(SCM_RESOURCE_GROUP_COMMAND_CLICK('Stage All Changes'));
}
async unstage(change: Change): Promise<void> {
const action = change.actions.filter(a => a.title === 'Unstage Changes')[0];
assert(action);
await this.spectron.client.spectron.client.elementIdClick(action.id);
}
async commit(message: string): Promise<void> {
await this.spectron.client.waitAndClick(SCM_INPUT);
await this.spectron.client.waitForActiveElement(SCM_INPUT);
await this.spectron.client.setValue(SCM_INPUT, message);
await this.spectron.client.waitAndClick(COMMIT_COMMAND);
}
}

View File

@@ -1,54 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SpectronApplication } from '../spectron/application';
import { 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

@@ -1,53 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { 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

@@ -1,186 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SpectronApplication } from '../spectron/application';
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

@@ -1,70 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SpectronApplication } from '../spectron/application';
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,37 @@
/*---------------------------------------------------------------------------------------------
* 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 } from '../../spectron/application';
describe('Multiroot', () => {
before(async function () {
this.app.suiteName = 'Multiroot';
const app = this.app as SpectronApplication;
await app.restart([app.workspaceFilePath]);
// for some reason Code opens 2 windows at this point
// so let's select the last one
await app.client.windowByIndex(2);
});
it('shows results from all folders', async function () {
const app = this.app as SpectronApplication;
await app.workbench.quickopen.openQuickOpen('*.*');
await app.workbench.quickopen.waitForQuickOpenElements(names => names.length >= 6);
await app.workbench.quickopen.closeQuickOpen();
});
it('shows workspace name in title', async function () {
const app = this.app as SpectronApplication;
const title = await app.client.getTitle();
await app.screenCapturer.capture('window title');
assert.ok(title.indexOf('smoketest (Workspace)') >= 0);
});
});

View File

@@ -0,0 +1,28 @@
/*---------------------------------------------------------------------------------------------
* 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';
const SEARCH_INPUT = '.settings-search-input input';
export class KeybindingsEditor {
constructor(private spectron: SpectronApplication) { }
async updateKeybinding(command: string, keys: string[], ariaLabel: string): Promise<any> {
await this.spectron.runCommand('workbench.action.openGlobalKeybindings');
await this.spectron.client.waitForActiveElement(SEARCH_INPUT);
await this.spectron.client.setValue(SEARCH_INPUT, command);
await this.spectron.client.waitAndClick('div[aria-label="Keybindings"] .monaco-list-row.keybinding-item');
await this.spectron.client.waitForElement('div[aria-label="Keybindings"] .monaco-list-row.keybinding-item.focused.selected');
await this.spectron.client.waitAndClick('div[aria-label="Keybindings"] .monaco-list-row.keybinding-item .action-item .icon.add');
await this.spectron.client.waitForElement('.defineKeybindingWidget .monaco-inputbox.synthetic-focus');
await this.spectron.client.keys([...keys, 'NULL', 'Enter', 'NULL']);
await this.spectron.client.waitForElement(`div[aria-label="Keybindings"] div[aria-label="Keybinding is ${ariaLabel}."]`);
}
}

View File

@@ -0,0 +1,46 @@
/*---------------------------------------------------------------------------------------------
* 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 } from '../../spectron/application';
import { ActivityBarPosition } from '../activitybar/activityBar';
describe('Preferences', () => {
before(function () {
this.app.suiteName = 'Preferences';
});
it('turns off editor line numbers and verifies the live change', async function () {
const app = this.app as SpectronApplication;
await app.workbench.explorer.openFile('app.js');
let lineNumbers = await app.client.waitForElements('.line-numbers');
await app.screenCapturer.capture('app.js has line numbers');
assert.ok(!!lineNumbers.length, 'Line numbers are not present in the editor before disabling them.');
await app.workbench.settingsEditor.addUserSetting('editor.lineNumbers', '"off"');
await app.workbench.selectTab('app.js');
lineNumbers = await app.client.waitForElements('.line-numbers', result => !result || result.length === 0);
await app.screenCapturer.capture('line numbers hidden');
assert.ok(!lineNumbers.length, 'Line numbers are still present in the editor after disabling them.');
});
it(`changes 'workbench.action.toggleSidebarPosition' command key binding and verifies it`, async function () {
const app = this.app as SpectronApplication;
assert.ok(await app.workbench.activitybar.getActivityBar(ActivityBarPosition.LEFT), 'Activity bar should be positioned on the left.');
await app.workbench.keybindingsEditor.updateKeybinding('workbench.action.toggleSidebarPosition', ['Control', 'u'], 'Control+U');
await app.client.keys(['Control', 'u', 'NULL']);
assert.ok(await app.workbench.activitybar.getActivityBar(ActivityBarPosition.RIGHT), 'Activity bar was not moved to right after toggling its position.');
});
after(async function () {
const app = this.app as SpectronApplication;
await app.workbench.settingsEditor.clearUserSettings();
});
});

View File

@@ -0,0 +1,45 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as fs from 'fs';
import * as path from 'path';
import { SpectronApplication } from '../../spectron/application';
export enum ActivityBarPosition {
LEFT = 0,
RIGHT = 1
};
const SEARCH_INPUT = '.settings-search-input input';
const EDITOR = '.editable-preferences-editor-container .monaco-editor textarea';
export class SettingsEditor {
constructor(private spectron: SpectronApplication) { }
async addUserSetting(setting: string, value: string): Promise<void> {
await this.spectron.runCommand('workbench.action.openGlobalSettings');
await this.spectron.client.waitAndClick(SEARCH_INPUT);
await this.spectron.client.waitForActiveElement(SEARCH_INPUT);
await this.spectron.client.keys(['ArrowDown', 'NULL']);
await this.spectron.client.waitForActiveElement(EDITOR);
await this.spectron.client.keys(['ArrowRight', 'NULL']);
await this.spectron.screenCapturer.capture('user settings is open and focused');
await this.spectron.workbench.editor.waitForTypeInEditor('settings.json', `"${setting}": ${value}`, '.editable-preferences-editor-container');
await this.spectron.workbench.saveOpenedFile();
await this.spectron.screenCapturer.capture('user settings has changed');
}
async clearUserSettings(): Promise<void> {
const settingsPath = path.join(this.spectron.userDataPath, 'User', 'settings.json');
await new Promise((c, e) => fs.writeFile(settingsPath, '{}', 'utf8', err => err ? e(err) : c()));
await this.spectron.workbench.editor.waitForEditorContents('settings.json', c => c.length === 0, '.editable-preferences-editor-container');
}
}

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';
export enum ProblemSeverity {
WARNING = 0,
ERROR = 1
};
export class Problems {
static PROBLEMS_VIEW_SELECTOR = '.panel.markers-panel';
constructor(private spectron: SpectronApplication) {
// noop
}
public async showProblemsView(): Promise<any> {
if (!await this.isVisible()) {
await this.spectron.runCommand('workbench.actions.view.problems');
await this.waitForProblemsView();
}
}
public async hideProblemsView(): Promise<any> {
if (await this.isVisible()) {
await this.spectron.runCommand('workbench.actions.view.problems');
await this.spectron.client.waitForElement(Problems.PROBLEMS_VIEW_SELECTOR, el => !el);
}
}
public async isVisible(): Promise<boolean> {
const element = await this.spectron.client.element(Problems.PROBLEMS_VIEW_SELECTOR);
return !!element;
}
public async waitForProblemsView(): Promise<void> {
await this.spectron.client.waitForElement(Problems.PROBLEMS_VIEW_SELECTOR);
}
public static getSelectorInProblemsView(problemType: ProblemSeverity): string {
let selector = problemType === ProblemSeverity.WARNING ? 'warning' : 'error';
return `div[aria-label="Problems grouped by files"] .icon.${selector}`;
}
public static getSelectorInEditor(problemType: ProblemSeverity): string {
let selector = problemType === ProblemSeverity.WARNING ? 'warningsquiggly' : 'errorsquiggly';
return `.view-overlays .cdr.${selector}`;
}
}

View File

@@ -0,0 +1,91 @@
/*---------------------------------------------------------------------------------------------
* 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 QuickOpen {
static QUICK_OPEN_HIDDEN = 'div.quick-open-widget[aria-hidden="true"]';
static QUICK_OPEN = 'div.quick-open-widget[aria-hidden="false"]';
static QUICK_OPEN_INPUT = `${QuickOpen.QUICK_OPEN} .quick-open-input input`;
static QUICK_OPEN_FOCUSED_ELEMENT = `${QuickOpen.QUICK_OPEN} .quick-open-tree .monaco-tree-row.focused .monaco-highlighted-label`;
static QUICK_OPEN_ENTRY_SELECTOR = 'div[aria-label="Quick Picker"] .monaco-tree-rows.show-twisties .monaco-tree-row .quick-open-entry';
constructor(readonly spectron: SpectronApplication) { }
async openQuickOpen(value: string): Promise<void> {
await this.spectron.runCommand('workbench.action.quickOpen');
await this.waitForQuickOpenOpened();
if (value) {
await this.spectron.client.setValue(QuickOpen.QUICK_OPEN_INPUT, value);
}
}
async closeQuickOpen(): Promise<void> {
await this.spectron.runCommand('workbench.action.closeQuickOpen');
await this.waitForQuickOpenClosed();
}
async openFile(fileName: string): Promise<void> {
await this.openQuickOpen(fileName);
await this.waitForQuickOpenElements(names => names.some(n => n === fileName));
await this.spectron.client.keys(['Enter', 'NULL']);
await this.spectron.workbench.waitForActiveTab(fileName);
await this.spectron.workbench.waitForEditorFocus(fileName);
}
async runCommand(commandText: string): Promise<void> {
await this.openQuickOpen(`> ${commandText}`);
// wait for best choice to be focused
await this.spectron.client.waitForTextContent(QuickOpen.QUICK_OPEN_FOCUSED_ELEMENT, commandText);
// wait and click on best choice
await this.spectron.client.waitAndClick(QuickOpen.QUICK_OPEN_FOCUSED_ELEMENT);
}
async waitForQuickOpenOpened(): Promise<void> {
await this.spectron.client.waitForActiveElement(QuickOpen.QUICK_OPEN_INPUT);
// we gotta wait 50 milliseconds due to https://github.com/Microsoft/vscode/blob/master/src/vs/platform/list/browser/listService.ts#L59
await new Promise(c => setTimeout(c, 50));
}
private async waitForQuickOpenClosed(): Promise<void> {
await this.spectron.client.waitForElement(QuickOpen.QUICK_OPEN_HIDDEN);
}
async submit(text: string): Promise<void> {
await this.spectron.client.setValue(QuickOpen.QUICK_OPEN_INPUT, text);
await this.spectron.client.keys(['Enter', 'NULL']);
await this.waitForQuickOpenClosed();
}
async selectQuickOpenElement(index: number): Promise<void> {
await this.waitForQuickOpenOpened();
for (let from = 0; from < index; from++) {
await this.spectron.client.keys(['ArrowDown', 'NULL']);
}
await this.spectron.client.keys(['Enter', 'NULL']);
await this.waitForQuickOpenClosed();
}
async waitForQuickOpenElements(accept: (names: string[]) => boolean): Promise<void> {
await this.spectron.client.waitFor(() => this.getQuickOpenElements(), accept);
}
private async getQuickOpenElements(): Promise<string[]> {
const result = await this.spectron.webclient.selectorExecute(QuickOpen.QUICK_OPEN_ENTRY_SELECTOR,
div => (Array.isArray(div) ? div : [div]).map(element => {
const name = element.querySelector('.label-name') as HTMLElement;
return name.textContent;
})
);
return Array.isArray(result) ? result : [];
}
}

View File

@@ -1,74 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SpectronApplication } from '../spectron/application';
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,56 @@
/*---------------------------------------------------------------------------------------------
* 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';
describe('Search', () => {
before(function () {
this.app.suiteName = 'Search';
});
it('searches for body & checks for correct result number', async function () {
const app = this.app as SpectronApplication;
await app.workbench.search.openSearchViewlet();
await app.workbench.search.searchFor('body');
await app.workbench.search.waitForResultText('7 results in 4 files');
});
it('searches only for *.js files & checks for correct result number', async function () {
const app = this.app as SpectronApplication;
await app.workbench.search.searchFor('body');
await app.workbench.search.showQueryDetails();
await app.workbench.search.setFilesToIncludeText('*.js');
await app.workbench.search.submitSearch();
await app.workbench.search.waitForResultText('4 results in 1 file');
await app.workbench.search.setFilesToIncludeText('');
await app.workbench.search.hideQueryDetails();
});
it('dismisses result & checks for correct result number', async function () {
const app = this.app as SpectronApplication;
await app.workbench.search.searchFor('body');
await app.workbench.search.removeFileMatch(1);
await app.workbench.search.waitForResultText('3 results in 3 files');
});
it('replaces first search result with a replace term', async function () {
const app = this.app as SpectronApplication;
await app.workbench.search.searchFor('body');
await app.workbench.search.expandReplace();
await app.workbench.search.setReplaceText('ydob');
await app.workbench.search.replaceFileMatch(1);
await app.workbench.saveOpenedFile();
await app.workbench.search.waitForResultText('3 results in 3 files');
await app.workbench.search.searchFor('ydob');
await app.workbench.search.setReplaceText('body');
await app.workbench.search.replaceFileMatch(1);
await app.workbench.saveOpenedFile();
});
});

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 { SpectronApplication } from '../../spectron/application';
import { Viewlet } from '../workbench/viewlet';
const VIEWLET = 'div[id="workbench.view.search"] .search-viewlet';
const INPUT = `${VIEWLET} .search-widget .search-container .monaco-inputbox input`;
const INCLUDE_INPUT = `${VIEWLET} .query-details .monaco-inputbox input[aria-label="Search Include Patterns"]`;
export class Search extends Viewlet {
constructor(spectron: SpectronApplication) {
super(spectron);
}
async openSearchViewlet(): Promise<any> {
await this.spectron.runCommand('workbench.view.search');
await this.spectron.client.waitForActiveElement(INPUT);
}
async searchFor(text: string): Promise<void> {
await this.spectron.client.click(INPUT);
await this.spectron.client.waitForActiveElement(INPUT);
await this.spectron.client.setValue(INPUT, text);
await this.submitSearch();
}
async submitSearch(): Promise<void> {
await this.spectron.client.click(INPUT);
await this.spectron.client.waitForActiveElement(INPUT);
await this.spectron.client.keys(['Enter', 'NULL']);
await this.spectron.client.element(`${VIEWLET} .messages[aria-hidden="false"]`);
}
async setFilesToIncludeText(text: string): Promise<void> {
await this.spectron.client.click(INCLUDE_INPUT);
await this.spectron.client.waitForActiveElement(INCLUDE_INPUT);
await this.spectron.client.setValue(INCLUDE_INPUT, text || '');
}
async showQueryDetails(): Promise<void> {
if (!await this.areDetailsVisible()) {
await this.spectron.client.waitAndClick(`${VIEWLET} .query-details .more`);
}
}
async hideQueryDetails(): Promise<void> {
if (await this.areDetailsVisible()) {
await this.spectron.client.waitAndClick(`${VIEWLET} .query-details.more .more`);
}
}
async areDetailsVisible(): Promise<boolean> {
const element = await this.spectron.client.element(`${VIEWLET} .query-details.more`);
return !!element;
}
async removeFileMatch(index: number): Promise<void> {
await this.spectron.client.waitAndMoveToObject(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch`);
const file = await this.spectron.client.waitForText(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch a.label-name`);
await this.spectron.client.waitAndClick(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch .action-label.icon.action-remove`);
await this.spectron.client.waitForText(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch a.label-name`, void 0, result => result !== file);
}
async expandReplace(): Promise<void> {
await this.spectron.client.waitAndClick(`${VIEWLET} .search-widget .monaco-button.toggle-replace-button.collapse`);
}
async setReplaceText(text: string): Promise<void> {
await this.spectron.client.waitAndClick(`${VIEWLET} .search-widget .replace-container .monaco-inputbox input[title="Replace"]`);
await this.spectron.client.element(`${VIEWLET} .search-widget .replace-container .monaco-inputbox.synthetic-focus input[title="Replace"]`);
await this.spectron.client.setValue(`${VIEWLET} .search-widget .replace-container .monaco-inputbox.synthetic-focus input[title="Replace"]`, text);
}
async replaceFileMatch(index: number): Promise<void> {
await this.spectron.client.waitAndMoveToObject(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch`);
await this.spectron.client.click(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch .action-label.icon.action-replace-all`);
}
async waitForResultText(text: string): Promise<void> {
await this.spectron.client.waitForText(`${VIEWLET} .messages[aria-hidden="false"] .message>p`, text);
}
}

View File

@@ -1,103 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SpectronApplication } from '../spectron/application';
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,97 @@
/*---------------------------------------------------------------------------------------------
* 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, Quality } from '../../spectron/application';
import { StatusBarElement } from './statusbar';
describe('Statusbar', () => {
before(function () {
this.app.suiteName = 'Statusbar';
});
it('verifies presence of all default status bar elements', async function () {
const app = this.app as SpectronApplication;
await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.BRANCH_STATUS);
if (app.quality !== Quality.Dev) {
await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.FEEDBACK_ICON);
}
await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.SYNC_STATUS);
await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.PROBLEMS_STATUS);
await app.workbench.quickopen.openFile('app.js');
await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.ENCODING_STATUS);
await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.EOL_STATUS);
await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.INDENTATION_STATUS);
await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.LANGUAGE_STATUS);
await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.SELECTION_STATUS);
});
it(`verifies that 'quick open' opens when clicking on status bar elements`, async function () {
const app = this.app as SpectronApplication;
await app.workbench.statusbar.clickOn(StatusBarElement.BRANCH_STATUS);
await app.workbench.quickopen.waitForQuickOpenOpened();
await app.workbench.quickopen.closeQuickOpen();
await app.workbench.quickopen.openFile('app.js');
await app.workbench.statusbar.clickOn(StatusBarElement.INDENTATION_STATUS);
await app.workbench.quickopen.waitForQuickOpenOpened();
await app.workbench.quickopen.closeQuickOpen();
await app.workbench.statusbar.clickOn(StatusBarElement.ENCODING_STATUS);
await app.workbench.quickopen.waitForQuickOpenOpened();
await app.workbench.quickopen.closeQuickOpen();
await app.workbench.statusbar.clickOn(StatusBarElement.EOL_STATUS);
await app.workbench.quickopen.waitForQuickOpenOpened();
await app.workbench.quickopen.closeQuickOpen();
await app.workbench.statusbar.clickOn(StatusBarElement.LANGUAGE_STATUS);
await app.workbench.quickopen.waitForQuickOpenOpened();
await app.workbench.quickopen.closeQuickOpen();
});
it(`verifies that 'Problems View' appears when clicking on 'Problems' status element`, async function () {
const app = this.app as SpectronApplication;
await app.workbench.statusbar.clickOn(StatusBarElement.PROBLEMS_STATUS);
await app.workbench.problems.waitForProblemsView();
});
it(`verifies that 'Tweet us feedback' pop-up appears when clicking on 'Feedback' icon`, async function () {
const app = this.app as SpectronApplication;
if (app.quality === Quality.Dev) {
return this.skip();
}
await app.workbench.statusbar.clickOn(StatusBarElement.FEEDBACK_ICON);
assert.ok(!!await app.client.waitForElement('.feedback-form'));
});
it(`checks if 'Go to Line' works if called from the status bar`, async function () {
const app = this.app as SpectronApplication;
await app.workbench.quickopen.openFile('app.js');
await app.workbench.statusbar.clickOn(StatusBarElement.SELECTION_STATUS);
await app.workbench.quickopen.waitForQuickOpenOpened();
await app.workbench.quickopen.submit(':15');
await app.workbench.editor.waitForHighlightingLine(15);
});
it(`verifies if changing EOL is reflected in the status bar`, async function () {
const app = this.app as SpectronApplication;
await app.workbench.quickopen.openFile('app.js');
await app.workbench.statusbar.clickOn(StatusBarElement.EOL_STATUS);
await app.workbench.quickopen.waitForQuickOpenOpened();
await app.workbench.quickopen.selectQuickOpenElement(1);
await app.workbench.statusbar.waitForEOL('CRLF');
});
});

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 { 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 readonly mainSelector = 'div[id="workbench.parts.statusbar"]';
private readonly leftSelector = '.statusbar-item.left';
private readonly rightSelector = '.statusbar-item.right';
constructor(private spectron: SpectronApplication) {
}
public async waitForStatusbarElement(element: StatusBarElement): Promise<void> {
await this.spectron.client.waitForElement(this.getSelector(element));
}
public async clickOn(element: StatusBarElement): Promise<void> {
await this.spectron.client.waitAndClick(this.getSelector(element));
}
public async waitForEOL(eol: string): Promise<string> {
return this.spectron.client.waitForText(this.getSelector(StatusBarElement.EOL_STATUS), eol);
}
public async getStatusbarTextByTitle(title: string): Promise<string> {
return await this.spectron.client.waitForText(`${this.mainSelector} span[title="smoke test"]`);
}
private getSelector(element: StatusBarElement): string {
switch (element) {
case StatusBarElement.BRANCH_STATUS:
return `${this.mainSelector} ${this.leftSelector} .octicon.octicon-git-branch`;
case StatusBarElement.SYNC_STATUS:
return `${this.mainSelector} ${this.leftSelector} .octicon.octicon-sync`;
case StatusBarElement.PROBLEMS_STATUS:
return `${this.mainSelector} ${this.leftSelector} .task-statusbar-item[title="Problems"]`;
case StatusBarElement.SELECTION_STATUS:
return `${this.mainSelector} ${this.rightSelector} .editor-status-selection`;
case StatusBarElement.INDENTATION_STATUS:
return `${this.mainSelector} ${this.rightSelector} .editor-status-indentation`;
case StatusBarElement.ENCODING_STATUS:
return `${this.mainSelector} ${this.rightSelector} .editor-status-encoding`;
case StatusBarElement.EOL_STATUS:
return `${this.mainSelector} ${this.rightSelector} .editor-status-eol`;
case StatusBarElement.LANGUAGE_STATUS:
return `${this.mainSelector} ${this.rightSelector} .editor-status-mode`;
case StatusBarElement.FEEDBACK_ICON:
return `${this.mainSelector} ${this.rightSelector} .dropdown.send-feedback`;
default:
throw new Error(element);
}
}
}

View File

@@ -1,89 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SpectronApplication } from '../spectron/application';
import { 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,29 @@
/*---------------------------------------------------------------------------------------------
* 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';
describe('Terminal', () => {
// let app: SpectronApplication;
// before(() => { app = new SpectronApplication(); return app.start('Terminal'); });
// after(() => app.stop());
// it(`opens terminal, runs 'echo' and verifies the output`, async function () {
// const expected = new Date().getTime().toString();
// await app.workbench.terminal.showTerminal();
// await app.workbench.terminal.runCommand(`echo ${expected}`);
// await app.workbench.terminal.waitForTerminalText(terminalText => {
// // Last line will not contain the output
// for (let index = terminalText.length - 2; index >= 0; index--) {
// if (!!terminalText[index] && terminalText[index].trim() === expected) {
// return true;
// }
// }
// return false;
// });
// });
});

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 { SpectronApplication } from '../../spectron/application';
const PANEL_SELECTOR = 'div[id="workbench.panel.terminal"]';
const XTERM_SELECTOR = `${PANEL_SELECTOR} .terminal-wrapper`;
export class Terminal {
constructor(private spectron: SpectronApplication) { }
async showTerminal(): Promise<void> {
if (!await this.isVisible()) {
await this.spectron.workbench.quickopen.runCommand('View: Toggle Integrated Terminal');
await this.spectron.client.waitForElement(XTERM_SELECTOR);
await this.waitForTerminalText(text => text.length > 0, 'Waiting for Terminal to be ready');
}
}
async isVisible(): Promise<boolean> {
const element = await this.spectron.client.element(PANEL_SELECTOR);
return !!element;
}
async runCommand(commandText: string): Promise<void> {
// TODO@Tyriar fix this. we should not use type but setValue
// await this.spectron.client.type(commandText);
await this.spectron.client.keys(['Enter', 'NULL']);
}
async waitForTerminalText(fn: (text: string[]) => boolean, timeOutDescription: string = 'Getting Terminal Text'): Promise<string[]> {
return this.spectron.client.waitFor(async () => {
const terminalText = await this.getTerminalText();
if (fn(terminalText)) {
return terminalText;
}
return undefined;
}, void 0, timeOutDescription);
}
getCurrentLineNumber(): Promise<number> {
return this.getTerminalText().then(text => text.length);
}
private async getTerminalText(): Promise<string[]> {
return await this.spectron.webclient.selectorExecute(XTERM_SELECTOR,
div => {
const xterm = (<any>(Array.isArray(div) ? div[0] : div)).xterm;
const buffer = xterm.buffer;
const lines: string[] = [];
for (let i = 0; i < buffer.lines.length; i++) {
lines.push(buffer.translateBufferLineToString(i, true));
}
return lines;
}
);
}
}

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';
describe('Dataloss', () => {
before(function () {
this.app.suiteName = 'Dataloss';
});
it(`verifies that 'hot exit' works for dirty files`, async function () {
const app = this.app as SpectronApplication;
await app.workbench.newUntitledFile();
const untitled = 'Untitled-1';
const textToTypeInUntitled = 'Hello, Unitled Code';
await app.workbench.editor.waitForTypeInEditor(untitled, textToTypeInUntitled);
await app.screenCapturer.capture('Untitled file before reload');
const readmeMd = 'readme.md';
const textToType = 'Hello, Code';
await app.workbench.explorer.openFile(readmeMd);
await app.workbench.editor.waitForTypeInEditor(readmeMd, textToType);
await app.screenCapturer.capture(`${readmeMd} before reload`);
await app.reload();
await app.screenCapturer.capture('After reload');
await app.workbench.waitForActiveTab(readmeMd, true);
await app.screenCapturer.capture(`${readmeMd} after reload`);
await app.workbench.editor.waitForEditorContents(readmeMd, c => c.indexOf(textToType) > -1);
await app.workbench.waitForTab(untitled, true);
await app.workbench.selectTab(untitled, true);
await app.screenCapturer.capture('Untitled file after reload');
await app.workbench.editor.waitForEditorContents(untitled, c => c.indexOf(textToTypeInUntitled) > -1);
});
});

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, STABLE_PATH, LATEST_PATH } from '../../spectron/application';
// import { Util } from '../../helpers/utilities';
// describe('Data Migration', () => {
// if (!STABLE_PATH) {
// return;
// }
// let app: SpectronApplication;
// afterEach(() => app.stop());
// it('checks if the Untitled file is restored migrating from stable to latest', async function () {
// const textToType = 'Very dirty file';
// // Setting up stable version
// let app = new SpectronApplication(STABLE_PATH);
// await app.start('Data Migration');
// await app.workbench.newUntitledFile();
// await app.workbench.editor.waitForTypeInEditor('Untitled-1', textToType);
// await app.stop();
// await new Promise(c => setTimeout(c, 500)); // wait until all resources are released (e.g. locked local storage)
// // Checking latest version for the restored state
// app = new SpectronApplication(LATEST_PATH);
// await app.start('Data Migration');
// assert.ok(await app.workbench.waitForActiveTab('Untitled-1', true), `Untitled-1 tab is not present after migration.`);
// await app.workbench.editor.waitForEditorContents('Untitled-1', c => c.indexOf(textToType) > -1);
// await app.screenCapturer.capture('Untitled file text');
// });
// it('checks if the newly created dirty file is restored migrating from stable to latest', async function () {
// const fileName = 'test_data/plainFile',
// firstTextPart = 'This is going to be an unsaved file', secondTextPart = '_that is dirty.';
// // Setting up stable version
// let app = new SpectronApplication(STABLE_PATH, fileName);
// await Util.removeFile(`${fileName}`);
// await app.start('Data Migration');
// await app.workbench.editor.waitForTypeInEditor('plainFile', firstTextPart);
// await app.workbench.saveOpenedFile();
// await app.workbench.editor.waitForTypeInEditor('plainFile', secondTextPart);
// await app.stop();
// await new Promise(c => setTimeout(c, 1000)); // wait until all resources are released (e.g. locked local storage)
// // Checking latest version for the restored state
// app = new SpectronApplication(LATEST_PATH);
// await app.start('Data Migration');
// const filename = fileName.split('/')[1];
// assert.ok(await app.workbench.waitForActiveTab(filename), `Untitled-1 tab is not present after migration.`);
// await app.workbench.editor.waitForEditorContents(filename, c => c.indexOf(firstTextPart + secondTextPart) > -1);
// await Util.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';
// let app = new SpectronApplication(STABLE_PATH);
// await app.start('Data Migration');
// await app.workbench.quickopen.openFile(fileName1);
// await app.workbench.quickopen.openFile(fileName2);
// await app.workbench.quickopen.openFile(fileName3);
// await app.stop();
// app = new SpectronApplication(LATEST_PATH);
// await app.start('Data Migration');
// assert.ok(await app.workbench.waitForTab(fileName1), `${fileName1} tab was not restored after migration.`);
// assert.ok(await app.workbench.waitForTab(fileName2), `${fileName2} tab was not restored after migration.`);
// assert.ok(await app.workbench.waitForTab(fileName3), `${fileName3} tab was not restored after migration.`);
// });
// });

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 * as assert from 'assert';
import { SpectronApplication, Quality } from '../../spectron/application';
describe('Localization', () => {
before(async function () {
const app = this.app as SpectronApplication;
this.app.suiteName = 'Localization';
if (app.quality === Quality.Dev) {
return;
}
await app.restart(['--locale=DE']);
});
it(`starts with 'DE' locale and verifies title and viewlets text is in German`, async function () {
const app = this.app as SpectronApplication;
if (app.quality === Quality.Dev) {
this.skip();
return;
}
let text = await app.workbench.explorer.getOpenEditorsViewTitle();
await app.screenCapturer.capture('Open editors title');
assert(/geöffnete editoren/i.test(text));
await app.workbench.search.openSearchViewlet();
text = await app.workbench.search.getTitle();
await app.screenCapturer.capture('Search title');
assert(/suchen/i.test(text));
await app.workbench.scm.openSCMViewlet();
text = await app.workbench.scm.getTitle();
await app.screenCapturer.capture('Scm title');
assert(/quellcodeverwaltung/i.test(text));
await app.workbench.debug.openDebugViewlet();
text = await app.workbench.debug.getTitle();
await app.screenCapturer.capture('Debug title');
assert(/debuggen/i.test(text));
await app.workbench.extensions.openExtensionsViewlet();
text = await app.workbench.extensions.getTitle();
await app.screenCapturer.capture('Extensions title');
assert(/erweiterungen/i.test(text));
});
});

View File

@@ -3,19 +3,16 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SpectronApplication } from '../spectron/application';
import { SpectronApplication } from '../../spectron/application';
export class FirstExperience {
constructor(private spectron: SpectronApplication) {
export abstract class Viewlet {
constructor(protected 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;
public async getTitle(): Promise<string> {
return this.spectron.client.waitForText('.monaco-workbench-container .part.sidebar > .title > .title-label > span');
}
}

View File

@@ -0,0 +1,86 @@
/*---------------------------------------------------------------------------------------------
* 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 { Explorer } from '../explorer/explorer';
import { ActivityBar } from '../activitybar/activityBar';
import { QuickOpen } from '../quickopen/quickopen';
import { Extensions } from '../extensions/extensions';
import { Search } from '../search/search';
import { Editor } from '../editor/editor';
import { SCM } from '../git/scm';
import { Debug } from '../debug/debug';
import { StatusBar } from '../statusbar/statusbar';
import { Problems } from '../problems/problems';
import { SettingsEditor } from '../preferences/settings';
import { KeybindingsEditor } from '../preferences/keybindings';
import { Terminal } from '../terminal/terminal';
export class Workbench {
readonly explorer: Explorer;
readonly activitybar: ActivityBar;
readonly quickopen: QuickOpen;
readonly search: Search;
readonly extensions: Extensions;
readonly editor: Editor;
readonly scm: SCM;
readonly debug: Debug;
readonly statusbar: StatusBar;
readonly problems: Problems;
readonly settingsEditor: SettingsEditor;
readonly keybindingsEditor: KeybindingsEditor;
readonly terminal: Terminal;
constructor(private spectron: SpectronApplication) {
this.explorer = new Explorer(spectron);
this.activitybar = new ActivityBar(spectron);
this.quickopen = new QuickOpen(spectron);
this.search = new Search(spectron);
this.extensions = new Extensions(spectron);
this.editor = new Editor(spectron);
this.scm = new SCM(spectron);
this.debug = new Debug(spectron);
this.statusbar = new StatusBar(spectron);
this.problems = new Problems(spectron);
this.settingsEditor = new SettingsEditor(spectron);
this.keybindingsEditor = new KeybindingsEditor(spectron);
this.terminal = new Terminal(spectron);
}
public async saveOpenedFile(): Promise<any> {
try {
await this.spectron.client.waitForElement('.tabs-container div.tab.active.dirty');
} catch (e) {
// ignore if there is no dirty file
return Promise.resolve();
}
await this.spectron.runCommand('workbench.action.files.save');
return this.spectron.client.waitForElement('.tabs-container div.tab.active.dirty', element => !element);
}
public async selectTab(tabName: string, untitled: boolean = false): Promise<void> {
await this.spectron.client.waitAndClick(`.tabs-container div.tab[aria-label="${tabName}, tab"]`);
await this.waitForEditorFocus(tabName, untitled);
}
public async waitForEditorFocus(fileName: string, untitled: boolean = false): Promise<void> {
await this.waitForActiveTab(fileName);
await this.editor.waitForActiveEditor(fileName);
}
public async waitForActiveTab(fileName: string, isDirty: boolean = false): Promise<any> {
return this.spectron.client.waitForElement(`.tabs-container div.tab.active${isDirty ? '.dirty' : ''}[aria-selected="true"][aria-label="${fileName}, tab"]`);
}
public async waitForTab(fileName: string, isDirty: boolean = false): Promise<boolean> {
return this.spectron.client.waitForElement(`.tabs-container div.tab${isDirty ? '.dirty' : ''}[aria-label="${fileName}, tab"]`).then(() => true);
}
public async newUntitledFile(): Promise<void> {
await this.spectron.runCommand('workbench.action.files.newUntitledFile');
await this.waitForEditorFocus('Untitled-1', true);
}
}

View File

@@ -3,38 +3,35 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SpectronApplication } from '../spectron/application';
var fs = require('fs');
import * as path from 'path';
import * as fs from 'fs';
import * as mkdirp from 'mkdirp';
import { Application } from 'spectron';
import { sanitize } from './utilities';
const __testTime = new Date().toISOString();
export class ScreenCapturer {
export class Screenshot {
private index: number = 0;
private testPath: string;
private static counter = 0;
constructor(private spectron: SpectronApplication, testName: string, testRetry: number) {
const testTime = this.sanitizeFolderName(__testTime);
testName = this.sanitizeFolderName(testName);
constructor(
private application: Application,
public suiteName: string,
private screenshotsDirPath: string | undefined,
) { }
this.testPath = `test_data/screenshots/${testTime}/${testName}/${testRetry}`;
this.createFolder(this.testPath);
async capture(name: string): Promise<void> {
if (!this.screenshotsDirPath) {
return;
}
const screenshotPath = path.join(
this.screenshotsDirPath,
sanitize(this.suiteName),
`${ScreenCapturer.counter++}-${sanitize(name)}.png`
);
const image = await this.application.browserWindow.capturePage();
await new Promise((c, e) => mkdirp(path.dirname(screenshotPath), err => err ? e(err) : c()));
await new Promise((c, e) => fs.writeFile(screenshotPath, image, err => err ? e(err) : c()));
}
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

@@ -3,8 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var fs = require('fs');
var rimraf = require('rimraf');
import * as fs from 'fs';
import { dirname } from 'path';
/**
* Contains methods that are commonly used across test areas.
@@ -24,14 +24,59 @@ export class Util {
}
}
public rimraf(directory: string): Promise<any> {
return new Promise((res, rej) => {
rimraf(directory, (err) => {
if (err) {
rej(err);
}
res();
});
});
public static removeFile(filePath: string): void {
try {
fs.unlinkSync(`${filePath}`);
} catch (e) {
if (e.code !== 'ENOENT') {
throw e;
}
}
}
}
export function nfcall<R>(fn: Function, ...args): Promise<R> {
return new Promise<R>((c, e) => fn(...args, (err, r) => err ? e(err) : c(r)));
}
export async function mkdirp(path: string, mode?: number): Promise<boolean> {
const mkdir = async () => {
try {
await nfcall(fs.mkdir, path, mode);
} catch (err) {
if (err.code === 'EEXIST') {
const stat = await nfcall<fs.Stats>(fs.stat, path);
if (stat.isDirectory) {
return;
}
throw new Error(`'${path}' exists and is not a directory.`);
}
throw err;
}
};
// is root?
if (path === dirname(path)) {
return true;
}
try {
await mkdir();
} catch (err) {
if (err.code !== 'ENOENT') {
throw err;
}
await mkdirp(dirname(path), mode);
await mkdir();
}
return true;
}
export function sanitize(name: string): string {
return name.replace(/[&*:\/]/g, '');
}

View File

@@ -3,220 +3,242 @@
* 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');
import * as fs from 'fs';
import * as https from 'https';
import * as cp from 'child_process';
import * as path from 'path';
import * as minimist from 'minimist';
import * as tmp from 'tmp';
import * as rimraf from 'rimraf';
import * as mkdirp from 'mkdirp';
import { SpectronApplication, Quality } from './spectron/application';
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';
const tmpDir = tmp.dirSync({ prefix: 't' }) as { name: string; removeCallback: Function; };
const testDataPath = tmpDir.name;
process.once('exit', () => rimraf.sync(testDataPath));
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('');
const [, , ...args] = process.argv;
const opts = minimist(args, {
string: [
'build',
'stable-build',
'log',
'wait-time'
]
});
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.');
}
const artifactsPath = opts.log || '';
// 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();
}
const workspaceFilePath = path.join(testDataPath, 'smoketest.code-workspace');
const testRepoUrl = 'https://github.com/Microsoft/vscode-smoketest-express';
const workspacePath = path.join(testDataPath, 'vscode-smoketest-express');
const keybindingsPath = path.join(testDataPath, 'keybindings.json');
const extensionsPath = path.join(testDataPath, 'extensions-dir');
mkdirp.sync(extensionsPath);
function fail(errorMessage): void {
console.error(errorMessage);
process.exit(1);
}
if (parseInt(process.version.substr(1)) < 6) {
fail('Please update your Node version to greater than 6 to run the smoke test.');
}
const repoPath = path.join(__dirname, '..', '..', '..');
function getDevElectronPath(): string {
const buildPath = path.join(repoPath, '.build');
const product = require(path.join(repoPath, 'product.json'));
switch (process.platform) {
case 'darwin':
return path.join(buildPath, 'electron', `${product.nameLong}.app`, 'Contents', 'MacOS', 'Electron');
case 'linux':
return path.join(buildPath, 'electron', `${product.applicationName}`);
case 'win32':
return path.join(buildPath, 'electron', `${product.nameShort}.exe`);
default:
throw new Error('Unsupported platform.');
}
}
function getBuildElectronPath(root: string): string {
switch (process.platform) {
case 'darwin':
return path.join(root, 'Contents', 'MacOS', 'Electron');
case 'linux': {
const product = require(path.join(root, 'resources', 'app', 'product.json'));
return path.join(root, product.applicationName);
}
case 'win32': {
const product = require(path.join(root, 'resources', 'app', 'product.json'));
return path.join(root, `${product.nameShort}.exe`);
}
default:
throw new Error('Unsupported platform.');
}
}
let testCodePath = opts.build;
let stableCodePath = opts['stable-build'];
let electronPath: string;
if (testCodePath) {
electronPath = getBuildElectronPath(testCodePath);
if (stableCodePath) {
process.env.VSCODE_STABLE_PATH = getBuildElectronPath(stableCodePath);
}
} else {
testCodePath = getDevElectronPath();
electronPath = testCodePath;
process.env.VSCODE_REPOSITORY = repoPath;
process.env.VSCODE_DEV = '1';
process.env.VSCODE_CLI = '1';
}
if (!fs.existsSync(electronPath || '')) {
fail(`Can't find Code at ${electronPath}.`);
}
const userDataDir = path.join(testDataPath, 'd');
// process.env.VSCODE_WORKSPACE_PATH = workspaceFilePath;
process.env.VSCODE_KEYBINDINGS_PATH = keybindingsPath;
let quality: Quality;
if (process.env.VSCODE_DEV === '1') {
quality = Quality.Dev;
} else if ((testCodePath.indexOf('Code - Insiders') /* macOS/Windows */ || testCodePath.indexOf('code-insiders') /* Linux */) >= 0) {
quality = Quality.Insiders;
} else {
quality = Quality.Stable;
}
function getKeybindingPlatform(): string {
switch (process.platform) {
case 'darwin': return 'osx';
case 'win32': return 'win';
default: return process.platform;
}
}
function toUri(path: string): string {
if (os === 'win') {
return `file:///${path.replace(/\\/g, '/')}`;
if (process.platform === 'win32') {
return `${path.replace(/\\/g, '/')}`;
}
return `file://${path}`;
return `${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());
async function setup(): Promise<void> {
console.log('*** Test data:', testDataPath);
console.log('*** Preparing smoketest setup...');
const keybindingsUrl = `https://raw.githubusercontent.com/Microsoft/vscode-docs/master/scripts/keybindings/doc.keybindings.${getKeybindingPlatform()}.json`;
console.log('*** Fetching keybindings...');
await new Promise((c, e) => {
https.get(keybindingsUrl, res => {
const output = fs.createWriteStream(keybindingsPath);
res.on('error', e);
output.on('error', e);
output.on('close', c);
res.pipe(output);
}).on('error', e);
});
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();
if (!fs.existsSync(workspaceFilePath)) {
console.log('*** Creating workspace file...');
const workspace = {
folders: [
{
path: toUri(path.join(workspacePath, 'public'))
},
{
path: toUri(path.join(workspacePath, 'routes'))
},
{
path: toUri(path.join(workspacePath, 'views'))
}
});
});
]
};
fs.writeFileSync(workspaceFilePath, JSON.stringify(workspace, null, '\t'));
}
if (!fs.existsSync(workspacePath)) {
console.log('*** Cloning test project repository...');
cp.spawnSync('git', ['clone', testRepoUrl, workspacePath]);
} else {
console.log('*** Cleaning test project repository...');
cp.spawnSync('git', ['fetch'], { cwd: workspacePath });
cp.spawnSync('git', ['reset', '--hard', 'FETCH_HEAD'], { cwd: workspacePath });
cp.spawnSync('git', ['clean', '-xdf'], { cwd: workspacePath });
}
console.log('*** Running npm install...');
cp.execSync('npm install', { cwd: workspacePath, stdio: 'inherit' });
console.log('*** Smoketest setup done!\n');
}
/**
* WebDriverIO 4.8.0 outputs all kinds of "deprecation" warnings
* for common commands like `keys` and `moveToObject`.
* According to https://github.com/Codeception/CodeceptJS/issues/531,
* these deprecation warnings are for Firefox, and have no alternative replacements.
* Since we can't downgrade WDIO as suggested (it's Spectron's dep, not ours),
* we must suppress the warning with a classic monkey-patch.
*
* @see webdriverio/lib/helpers/depcrecationWarning.js
* @see https://github.com/webdriverio/webdriverio/issues/2076
*/
// Filter out the following messages:
const wdioDeprecationWarning = /^WARNING: the "\w+" command will be depcrecated soon./; // [sic]
// Monkey patch:
const warn = console.warn;
console.warn = function suppressWebdriverWarnings(message) {
if (wdioDeprecationWarning.test(message)) { return; }
warn.apply(console, arguments);
};
before(async function () {
// allow two minutes for setup
this.timeout(2 * 60 * 1000);
await setup();
const app = new SpectronApplication({
quality,
electronPath,
workspacePath,
userDataDir,
extensionsPath,
artifactsPath,
workspaceFilePath,
waitTime: parseInt(opts['wait-time'] || '0') || 20
});
}
function folderExists(folder: string): boolean {
try {
fs.accessSync(folder, 'rw');
return true;
} catch (e) {
return false;
}
}
await app.start();
this.app = app;
});
function binaryExists(filePath: string): boolean {
try {
fs.accessSync(filePath, 'x');
return true;
} catch (e) {
return false;
}
}
after(async function () {
await this.app.stop();
await new Promise((c, e) => rimraf(testDataPath, { maxBusyTries: 10 }, err => err ? e(err) : c()));
});
// import './areas/workbench/data-migration.test';
import './areas/workbench/data-loss.test';
import './areas/explorer/explorer.test';
import './areas/preferences/preferences.test';
import './areas/search/search.test';
import './areas/css/css.test';
import './areas/editor/editor.test';
import './areas/debug/debug.test';
import './areas/git/git.test';
// import './areas/terminal/terminal.test';
import './areas/statusbar/statusbar.test';
import './areas/extensions/extensions.test';
import './areas/multiroot/multiroot.test';
import './areas/workbench/localization.test';

View File

@@ -1,16 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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

@@ -3,142 +3,308 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Application } from 'spectron';
import { Application, SpectronClient as WebClient } from 'spectron';
import { test as testPort } from 'portastic';
import { SpectronClient } from './client';
import { Screenshot } from '../helpers/screenshot';
var fs = require('fs');
var path = require('path');
import { ScreenCapturer } from '../helpers/screenshot';
import { Workbench } from '../areas/workbench/workbench';
import * as fs from 'fs';
import * as cp from 'child_process';
import * as path from 'path';
import * as mkdirp from 'mkdirp';
import { sanitize } from '../helpers/utilities';
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';
// Just hope random helps us here, cross your fingers!
export async function findFreePort(): Promise<number> {
for (let i = 0; i < 10; i++) {
const port = 10000 + Math.round(Math.random() * 10000);
if (await testPort(port)) {
return port;
}
}
throw new Error('Could not find free port!');
}
export enum Quality {
Dev,
Insiders,
Stable
}
export interface SpectronApplicationOptions {
quality: Quality;
electronPath: string;
workspacePath: string;
userDataDir: string;
extensionsPath: string;
artifactsPath: string;
workspaceFilePath: string;
waitTime: number;
}
/**
* Wraps Spectron's Application instance with its used methods.
*/
export class SpectronApplication {
public client: SpectronClient;
private spectron: Application;
private keybindings: any[];
private screenshot: Screenshot;
private static count = 0;
private readonly sampleExtensionsDir: string = 'test_data/sample_extensions_dir';
private readonly pollTrials = 50;
private readonly pollTimeout = 1; // in secs
private _client: SpectronClient;
private _workbench: Workbench;
private _screenCapturer: ScreenCapturer;
private spectron: Application | undefined;
private keybindings: any[]; private stopLogCollection: (() => Promise<void>) | undefined;
constructor(electronPath: string, testName: string, private testRetry: number, args?: string[], chromeDriverArgs?: string[]) {
if (!args) {
args = [];
constructor(
private options: SpectronApplicationOptions
) { }
get quality(): Quality {
return this.options.quality;
}
get client(): SpectronClient {
return this._client;
}
get webclient(): WebClient {
if (!this.spectron) {
throw new Error('Application not started');
}
return this.spectron.client;
}
get screenCapturer(): ScreenCapturer {
return this._screenCapturer;
}
get workbench(): Workbench {
return this._workbench;
}
get workspacePath(): string {
return this.options.workspacePath;
}
get extensionsPath(): string {
return this.options.extensionsPath;
}
get userDataPath(): string {
return this.options.userDataDir;
}
get workspaceFilePath(): string {
return this.options.workspaceFilePath;
}
private _suiteName: string = 'Init';
set suiteName(suiteName: string) {
this._suiteName = suiteName;
this._screenCapturer.suiteName = suiteName;
}
async start(): Promise<any> {
await this._start();
await this.waitForWelcome();
}
async restart(codeArgs: string[] = []): Promise<any> {
await this.stop();
await new Promise(c => setTimeout(c, 1000));
await this._start(codeArgs);
}
private async _start(codeArgs: string[] = []): Promise<any> {
await this.retrieveKeybindings();
cp.execSync('git checkout .', { cwd: this.options.workspacePath });
await this.startApplication(codeArgs);
await this.checkWindowReady();
}
async reload(): Promise<any> {
await this.workbench.quickopen.runCommand('Reload Window');
// TODO @sandy: Find a proper condition to wait for reload
await new Promise(c => setTimeout(c, 500));
await this.checkWindowReady();
}
async stop(): Promise<any> {
if (this.stopLogCollection) {
await this.stopLogCollection();
this.stopLogCollection = undefined;
}
if (this.spectron && this.spectron.isRunning()) {
await this.spectron.stop();
this.spectron = undefined;
}
}
private async startApplication(codeArgs: string[] = []): Promise<any> {
let args: string[] = [];
let chromeDriverArgs: string[] = [];
if (process.env.VSCODE_REPOSITORY) {
args.push(process.env.VSCODE_REPOSITORY as string);
}
args.push(this.options.workspacePath);
// 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}`);
}
// Prevent Quick Open from closing when focus is stolen, this allows concurrent smoketest suite running
args.push('--sticky-quickopen');
this.spectron = new Application({
path: electronPath,
args: args,
chromeDriverArgs: chromeDriverArgs,
// Disable telemetry
args.push('--disable-telemetry');
// Disable updates
args.push('--disable-updates');
// Disable crash reporter
// This seems to be the fix for the strange hangups in which Code stays unresponsive
// and tests finish badly with timeouts, leaving Code running in the background forever
args.push('--disable-crash-reporter');
// Ensure that running over custom extensions directory, rather than picking up the one that was used by a tester previously
args.push(`--extensions-dir=${this.options.extensionsPath}`);
args.push(...codeArgs);
chromeDriverArgs.push(`--user-data-dir=${this.options.userDataDir}`);
// Spectron always uses the same port number for the chrome driver
// and it handles gracefully when two instances use the same port number
// This works, but when one of the instances quits, it takes down
// chrome driver with it, leaving the other instance in DISPAIR!!! :(
const port = await findFreePort();
// We must get a different port for debugging the smoketest express app
// otherwise concurrent test runs will clash on those ports
const env = { PORT: String(await findFreePort()), ...process.env };
const opts: any = {
path: this.options.electronPath,
port,
args,
env,
chromeDriverArgs,
startTimeout: 10000,
requireName: 'nodeRequire'
});
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;
}
const runName = String(SpectronApplication.count++);
let testsuiteRootPath: string | undefined = undefined;
let screenshotsDirPath: string | undefined = undefined;
public async start(): Promise<any> {
await this.spectron.start();
await this.focusOnWindow(1); // focuses on main renderer window
await this.checkWindowReady();
}
if (this.options.artifactsPath) {
testsuiteRootPath = path.join(this.options.artifactsPath, sanitize(runName));
mkdirp.sync(testsuiteRootPath);
public async stop(): Promise<any> {
if (this.spectron && this.spectron.isRunning()) {
return await this.spectron.stop();
// Collect screenshots
screenshotsDirPath = path.join(testsuiteRootPath, 'screenshots');
mkdirp.sync(screenshotsDirPath);
// Collect chromedriver logs
const chromedriverLogPath = path.join(testsuiteRootPath, 'chromedriver.log');
opts.chromeDriverLogPath = chromedriverLogPath;
// Collect webdriver logs
const webdriverLogsPath = path.join(testsuiteRootPath, 'webdriver');
mkdirp.sync(webdriverLogsPath);
opts.webdriverLogPath = webdriverLogsPath;
}
}
public waitFor(func: (...args: any[]) => any, args: any): Promise<any> {
return this.callClientAPI(func, args);
}
this.spectron = new Application(opts);
await this.spectron.start();
public wait(): Promise<any> {
return new Promise(resolve => setTimeout(resolve, this.testRetry * this.pollTimeout * 1000));
}
if (testsuiteRootPath) {
// Collect logs
const mainProcessLogPath = path.join(testsuiteRootPath, 'main.log');
const rendererProcessLogPath = path.join(testsuiteRootPath, 'renderer.log');
public focusOnWindow(index: number): Promise<any> {
return this.client.windowByIndex(index);
const flush = async () => {
if (!this.spectron) {
return;
}
const mainLogs = await this.spectron.client.getMainProcessLogs();
await new Promise((c, e) => fs.appendFile(mainProcessLogPath, mainLogs.join('\n'), { encoding: 'utf8' }, err => err ? e(err) : c()));
const rendererLogs = (await this.spectron.client.getRenderProcessLogs()).map(m => `${m.timestamp} - ${m.level} - ${m.message}`);
await new Promise((c, e) => fs.appendFile(rendererProcessLogPath, rendererLogs.join('\n'), { encoding: 'utf8' }, err => err ? e(err) : c()));
};
let running = true;
const loopFlush = async () => {
while (true) {
await flush();
if (!running) {
return;
}
await new Promise(c => setTimeout(c, 1000));
}
};
const loopPromise = loopFlush();
this.stopLogCollection = () => {
running = false;
return loopPromise;
};
}
this._screenCapturer = new ScreenCapturer(this.spectron, this._suiteName, screenshotsDirPath);
this._client = new SpectronClient(this.spectron, this, this.options.waitTime);
this._workbench = new Workbench(this);
}
private async checkWindowReady(): Promise<any> {
await this.waitFor(this.spectron.client.getHTML, '[id="workbench.main.container"]');
await this.webclient.waitUntilWindowLoaded();
// Spectron opens multiple terminals in Windows platform
// Workaround to focus the right window - https://github.com/electron/spectron/issues/60
await this.client.windowByIndex(1);
// await this.app.browserWindow.focus();
await this.client.waitForHTML('[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 waitForWelcome(): Promise<any> {
await this.client.waitForElement('.explorer-folders-view');
await this.client.waitForElement(`.editor-container[id="workbench.editor.walkThroughPart"] .welcomePage`);
}
private retrieveKeybindings(): Promise<void> {
return new Promise((c, e) => {
fs.readFile(process.env.VSCODE_KEYBINDINGS_PATH as string, 'utf8', (err, data) => {
if (err) {
throw err;
}
try {
this.keybindings = JSON.parse(data);
c();
} catch (e) {
throw new Error(`Error parsing keybindings JSON: ${e}`);
}
});
});
}
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> {
runCommand(command: string): Promise<any> {
const binding = this.keybindings.find(x => x['command'] === command);
if (!binding) {
return Promise.reject(`Key binding for ${command} was not found.`);
return this.workbench.quickopen.runCommand(command);
}
const keys: string = binding.key;
@@ -151,7 +317,7 @@ export class SpectronApplication {
keysToPress.push('NULL');
});
return this.client.keys(keysToPress, capture);
return this.client.keys(keysToPress);
}
/**

View File

@@ -4,124 +4,209 @@
*--------------------------------------------------------------------------------------------*/
import { Application } from 'spectron';
import { Screenshot } from '../helpers/screenshot';
import { RawResult, Element } from 'webdriverio';
import { SpectronApplication } from './application';
/**
* Abstracts the Spectron's WebdriverIO managed client property on the created Application instances.
*/
export class SpectronClient {
constructor(private spectron: Application, private shot: Screenshot) {
// noop
// waitFor calls should not take more than 200 * 100 = 20 seconds to complete, excluding
// the time it takes for the actual retry call to complete
private retryCount: number;
private readonly retryDuration = 100; // in milliseconds
constructor(
readonly spectron: Application,
private application: SpectronApplication,
waitTime: number
) {
this.retryCount = (waitTime * 1000) / this.retryDuration;
}
public windowByIndex(index: number): Promise<any> {
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);
keys(keys: string[]): Promise<void> {
this.spectron.client.keys(keys);
return Promise.resolve();
}
public async getText(selector: string, capture: boolean = true): Promise<any> {
await this.screenshot(capture);
async getText(selector: string, capture: boolean = true): Promise<any> {
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);
async waitForText(selector: string, text?: string, accept?: (result: string) => boolean): Promise<string> {
accept = accept ? accept : result => text !== void 0 ? text === result : !!result;
return this.waitFor(() => this.spectron.client.getText(selector), accept, `getText with selector ${selector}`);
}
public async click(selector: string, capture: boolean = true): Promise<any> {
await this.screenshot(capture);
async waitForTextContent(selector: string, textContent?: string, accept?: (result: string) => boolean): Promise<string> {
accept = accept ? accept : (result => textContent !== void 0 ? textContent === result : !!result);
const fn = async () => await this.spectron.client.selectorExecute(selector, div => Array.isArray(div) ? div[0].textContent : div.textContent);
return this.waitFor(fn, s => accept!(typeof s === 'string' ? s : ''), `getTextContent with selector ${selector}`);
}
async waitForValue(selector: string, value?: string, accept?: (result: string) => boolean): Promise<any> {
accept = accept ? accept : result => value !== void 0 ? value === result : !!result;
return this.waitFor(() => this.spectron.client.getValue(selector), accept, `getValue with selector ${selector}`);
}
async waitForHTML(selector: string, accept: (result: string) => boolean = (result: string) => !!result): Promise<any> {
return this.waitFor(() => this.spectron.client.getHTML(selector), accept, `getHTML with selector ${selector}`);
}
async waitAndClick(selector: string): Promise<any> {
return this.waitFor(() => this.spectron.client.click(selector), void 0, `click with selector ${selector}`);
}
async click(selector: string): Promise<any> {
return this.spectron.client.click(selector);
}
public async doubleClick(selector: string, capture: boolean = true): Promise<any> {
await this.screenshot(capture);
return this.spectron.client.doubleClick(selector);
async doubleClickAndWait(selector: string, capture: boolean = true): Promise<any> {
return this.waitFor(() => this.spectron.client.doubleClick(selector), void 0, `doubleClick with selector ${selector}`);
}
public async leftClick(selector: string, xoffset: number, yoffset: number, capture: boolean = true): Promise<any> {
await this.screenshot(capture);
async leftClick(selector: string, xoffset: number, yoffset: number, capture: boolean = true): Promise<any> {
return this.spectron.client.leftClick(selector, xoffset, yoffset);
}
public async rightClick(selector: string, capture: boolean = true): Promise<any> {
await this.screenshot(capture);
async rightClick(selector: string, capture: boolean = true): Promise<any> {
return this.spectron.client.rightClick(selector);
}
public async moveToObject(selector: string, capture: boolean = true): Promise<any> {
await this.screenshot(capture);
async moveToObject(selector: string, capture: boolean = true): Promise<any> {
return this.spectron.client.moveToObject(selector);
}
public async setValue(selector: string, text: string, capture: boolean = true): Promise<any> {
await this.screenshot(capture);
async waitAndMoveToObject(selector: string): Promise<any> {
return this.waitFor(() => this.spectron.client.moveToObject(selector), void 0, `move to object with selector ${selector}`);
}
async setValue(selector: string, text: string, capture: boolean = true): Promise<any> {
return this.spectron.client.setValue(selector, text);
}
public async elements(selector: string, capture: boolean = true): Promise<any> {
await this.screenshot(capture);
return this.spectron.client.elements(selector);
async waitForElements(selector: string, accept: (result: Element[]) => boolean = result => result.length > 0): Promise<Element[]> {
return this.waitFor<RawResult<Element[]>>(() => this.spectron.client.elements(selector), result => accept(result.value), `elements with selector ${selector}`)
.then(result => result.value);
}
public async element(selector: string, capture: boolean = true): Promise<any> {
await this.screenshot(capture);
return this.spectron.client.element(selector);
async waitForElement(selector: string, accept: (result: Element | undefined) => boolean = result => !!result): Promise<Element> {
return this.waitFor<RawResult<Element>>(() => this.spectron.client.element(selector), result => accept(result ? result.value : void 0), `element with selector ${selector}`)
.then(result => result.value);
}
public async dragAndDrop(sourceElem: string, destinationElem: string, capture: boolean = true): Promise<any> {
await this.screenshot(capture);
async waitForVisibility(selector: string, accept: (result: boolean) => boolean = result => result): Promise<any> {
return this.waitFor(() => this.spectron.client.isVisible(selector), accept, `isVisible with selector ${selector}`);
}
async element(selector: string): Promise<Element> {
return this.spectron.client.element(selector)
.then(result => result.value);
}
async waitForActiveElement(selector: string): Promise<any> {
return this.waitFor(
() => this.spectron.client.execute(s => document.activeElement.matches(s), selector),
r => r.value,
`wait for active element: ${selector}`
);
}
async waitForAttribute(selector: string, attribute: string, accept: (result: string) => boolean = result => !!result): Promise<string> {
return this.waitFor<string>(() => this.spectron.client.getAttribute(selector), accept, `attribute with selector ${selector}`);
}
async dragAndDrop(sourceElem: string, destinationElem: string, capture: boolean = true): Promise<any> {
return this.spectron.client.dragAndDrop(sourceElem, destinationElem);
}
public async selectByValue(selector: string, value: string, capture: boolean = true): Promise<any> {
await this.screenshot(capture);
async selectByValue(selector: string, value: string, capture: boolean = true): Promise<any> {
return this.spectron.client.selectByValue(selector, value);
}
public async getValue(selector: string, capture: boolean = true): Promise<any> {
await this.screenshot(capture);
async getValue(selector: string, capture: boolean = true): Promise<any> {
return this.spectron.client.getValue(selector);
}
public async getAttribute(selector: string, attribute: string, capture: boolean = true): Promise<any> {
await this.screenshot(capture);
async getAttribute(selector: string, attribute: string, capture: boolean = true): Promise<any> {
return Promise.resolve(this.spectron.client.getAttribute(selector, attribute));
}
public clearElement(selector: string): any {
return this.spectron.client.clearElement(selector);
}
public buttonDown(): any {
buttonDown(): any {
return this.spectron.client.buttonDown();
}
public buttonUp(): any {
buttonUp(): any {
return this.spectron.client.buttonUp();
}
public async isVisible(selector: string, capture: boolean = true): Promise<any> {
await this.screenshot(capture);
async isVisible(selector: string, capture: boolean = true): Promise<any> {
return this.spectron.client.isVisible(selector);
}
public getTitle(): string {
async getTitle(): Promise<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}`);
private running = false;
async waitFor<T>(func: () => T | Promise<T | undefined>, accept?: (result: T) => boolean | Promise<boolean>, timeoutMessage?: string, retryCount?: number): Promise<T>;
async waitFor<T>(func: () => T | Promise<T>, accept: (result: T) => boolean | Promise<boolean> = result => !!result, timeoutMessage?: string, retryCount?: number): Promise<T> {
if (this.running) {
throw new Error('Not allowed to run nested waitFor calls!');
}
this.running = true;
try {
let trial = 1;
retryCount = typeof retryCount === 'number' ? retryCount : this.retryCount;
while (true) {
if (trial > retryCount) {
await this.application.screenCapturer.capture('timeout');
throw new Error(`${timeoutMessage}: Timed out after ${(retryCount * this.retryDuration) / 1000} seconds.`);
}
let result;
try {
result = await func();
} catch (e) {
// console.log(e);
}
if (accept(result)) {
return result;
}
await new Promise(resolve => setTimeout(resolve, this.retryDuration));
trial++;
}
} finally {
this.running = false;
}
}
// type(text: string): Promise<any> {
// return new Promise((res) => {
// let textSplit = text.split(' ');
// const type = async (i: number) => {
// if (!textSplit[i] || textSplit[i].length <= 0) {
// return res();
// }
// const toType = textSplit[i + 1] ? `${textSplit[i]} ` : textSplit[i];
// await this.keys(toType);
// await this.keys(['NULL']);
// await type(i + 1);
// };
// return type(0);
// });
// }
}

View File

@@ -1,40 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { 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

@@ -1,57 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as 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

@@ -1,61 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as 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

@@ -1,74 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as 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

@@ -1,105 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as 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

@@ -1,43 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as 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

@@ -1,74 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as 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

@@ -1,69 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as 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

@@ -1,40 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as 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

@@ -1,44 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as 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

@@ -1,87 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as 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

@@ -1,50 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as 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

@@ -1,43 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as 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

@@ -1,73 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as 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

@@ -1,94 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as 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

@@ -1,56 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as 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.`);
});
});
}

View File

@@ -0,0 +1,3 @@
--timeout 60000
--slow 20000
out/main.js

View File

@@ -18,4 +18,4 @@
"exclude": [
"node_modules"
]
}
}