mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-09 01:32:34 -05:00
Merge VS Code 1.23.1 (#1520)
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
## Run
|
||||
|
||||
The best way to run the Code tests is from the terminal. To make development changes to unit tests you need to be running `gulp`. See [Development Workflow](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#incremental-build) for more details. From the `vscode` folder run:
|
||||
The best way to run the Code tests is from the terminal. To make development changes to unit tests you need to be running `yarn run watch`. See [Development Workflow](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#incremental-build) for more details. From the `vscode` folder run:
|
||||
|
||||
**OS X and Linux**
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ const path = require('path');
|
||||
const mocha = require('mocha');
|
||||
const events = require('events');
|
||||
|
||||
const defaultReporterName = process.platform === 'win32' ? 'list' : 'spec';
|
||||
|
||||
const optimist = require('optimist')
|
||||
.describe('grep', 'only run tests matching <pattern>').alias('grep', 'g').alias('grep', 'f').string('grep')
|
||||
.describe('run', 'only run tests from <file>').string('run')
|
||||
@@ -17,7 +19,9 @@ const optimist = require('optimist')
|
||||
.describe('build', 'run with build output (out-build)').boolean('build')
|
||||
.describe('coverage', 'generate coverage report').boolean('coverage')
|
||||
.describe('debug', 'open dev tools, keep window open, reuse app data').string('debug')
|
||||
.describe('reporter', 'the mocha reporter').string('reporter').default('reporter', process.platform === 'win32' ? 'dot' : 'spec')
|
||||
.describe('reporter', 'the mocha reporter').string('reporter').default('reporter', defaultReporterName)
|
||||
.describe('reporter-options', 'the mocha reporter options').string('reporter-options').default('reporter-options', '')
|
||||
.describe('tfs').boolean('tfs')
|
||||
.describe('help', 'show the help').alias('help', 'h');
|
||||
|
||||
const argv = optimist.argv;
|
||||
@@ -33,6 +37,9 @@ if (!argv.debug) {
|
||||
|
||||
function deserializeSuite(suite) {
|
||||
return {
|
||||
root: suite.root,
|
||||
suites: suite.suites,
|
||||
tests: suite.tests,
|
||||
title: suite.title,
|
||||
fullTitle: () => suite.fullTitle,
|
||||
timeout: () => suite.timeout,
|
||||
@@ -84,6 +91,31 @@ class IPCRunner extends events.EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
function parseReporterOption(value) {
|
||||
let r = /^([^=]+)=(.*)$/.exec(value);
|
||||
return r ? { [r[1]]: r[2] } : {};
|
||||
}
|
||||
|
||||
class TFSReporter extends mocha.reporters.Base {
|
||||
|
||||
constructor(runner) {
|
||||
super(runner);
|
||||
|
||||
runner.on('pending', test => {
|
||||
console.log('PEND', test.fullTitle());
|
||||
});
|
||||
runner.on('pass', test => {
|
||||
console.log('OK ', test.fullTitle(), `(${test.duration}ms)`);
|
||||
});
|
||||
runner.on('fail', test => {
|
||||
console.log('FAIL', test.fullTitle(), `(${test.duration}ms)`);
|
||||
});
|
||||
runner.once('end', () => {
|
||||
this.epilogue();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
app.on('ready', () => {
|
||||
|
||||
const win = new BrowserWindow({
|
||||
@@ -99,25 +131,38 @@ app.on('ready', () => {
|
||||
win.webContents.on('did-finish-load', () => {
|
||||
if (argv.debug) {
|
||||
win.show();
|
||||
win.webContents.openDevTools('right');
|
||||
win.webContents.openDevTools({ mode: 'right' });
|
||||
}
|
||||
win.webContents.send('run', argv);
|
||||
});
|
||||
|
||||
win.loadURL(`file://${__dirname}/renderer.html`);
|
||||
|
||||
const reporterPath = path.join(path.dirname(require.resolve('mocha')), 'lib', 'reporters', argv.reporter);
|
||||
let Reporter;
|
||||
|
||||
try {
|
||||
Reporter = require(reporterPath);
|
||||
} catch (err) {
|
||||
console.warn(`could not load reporter: ${argv.reporter}`);
|
||||
Reporter = process.platform === 'win32' ? mocha.reporters.Dot : mocha.reporters.Spec;
|
||||
}
|
||||
|
||||
const runner = new IPCRunner();
|
||||
new Reporter(runner);
|
||||
|
||||
if (argv.tfs) {
|
||||
new TFSReporter(runner);
|
||||
} else {
|
||||
const reporterPath = path.join(path.dirname(require.resolve('mocha')), 'lib', 'reporters', argv.reporter);
|
||||
let Reporter;
|
||||
|
||||
try {
|
||||
Reporter = require(reporterPath);
|
||||
} catch (err) {
|
||||
try {
|
||||
Reporter = require(argv.reporter);
|
||||
} catch (err) {
|
||||
Reporter = process.platform === 'win32' ? mocha.reporters.List : mocha.reporters.Spec;
|
||||
console.warn(`could not load reporter: ${argv.reporter}, using ${Reporter.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
let reporterOptions = argv['reporter-options'];
|
||||
reporterOptions = typeof reporterOptions === 'string' ? [reporterOptions] : reporterOptions;
|
||||
reporterOptions = reporterOptions.reduce((r, o) => Object.assign(r, parseReporterOption(o)), {});
|
||||
|
||||
new Reporter(runner, { reporterOptions });
|
||||
}
|
||||
|
||||
if (!argv.debug) {
|
||||
ipcMain.on('all done', () => app.exit(runner.didFail ? 1 : 0));
|
||||
|
||||
@@ -11,7 +11,10 @@
|
||||
<script src="../../node_modules/mocha/mocha.js"></script>
|
||||
|
||||
<script>
|
||||
mocha.setup('tdd');
|
||||
mocha.setup({
|
||||
ui: 'tdd',
|
||||
timeout: 5000
|
||||
});
|
||||
require('./renderer');
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -187,6 +187,9 @@ function loadTests(opts) {
|
||||
|
||||
function serializeSuite(suite) {
|
||||
return {
|
||||
root: suite.root,
|
||||
suites: suite.suites.map(serializeSuite),
|
||||
tests: suite.tests.map(serializeRunnable),
|
||||
title: suite.title,
|
||||
fullTitle: suite.fullTitle(),
|
||||
timeout: suite.timeout(),
|
||||
|
||||
3
test/smoke/.gitignore
vendored
3
test/smoke/.gitignore
vendored
@@ -4,4 +4,5 @@ Thumbs.db
|
||||
node_modules/
|
||||
out/
|
||||
keybindings.*.json
|
||||
test_data/
|
||||
test_data/
|
||||
src/vscode/driver.d.ts
|
||||
@@ -4,43 +4,26 @@
|
||||
|
||||
```
|
||||
# Dev
|
||||
npm run smoketest
|
||||
yarn smoketest
|
||||
|
||||
# Specific build
|
||||
npm run smoketest -- --build "path/to/code"
|
||||
|
||||
# Data Migration tests
|
||||
npm run smoketest -- --build "path/to/code-insiders" --stable-build "path/to/code"
|
||||
# Build
|
||||
yarn smoketest --build "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.
|
||||
The script calls mocha, so all mocha arguments should work fine. For example, use `-f Git` to filter all tests except the `Git` tests.
|
||||
|
||||
By default, screenshots are not captured. To run tests with screenshots use the argument `--screenshots`.
|
||||
A `--verbose` flag can be used to log to the console all the low level driver calls make to Code.
|
||||
|
||||
Screenshots can be captured when tests fail. In order to get them,you need to use the argument `--screenshots SCREENSHOT_DIR`.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Beware of **state**. The tests within a single suite will share the same state.
|
||||
- Beware of workbench **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 **timing**. You need to read from or write to the DOM... but is it the right time to do that? Can you 100% guarantee that that `input` box will be visible at that point in time? Or are you just hoping that it will be so? Hope is your worst enemy in UI tests. Example: just because you triggered Quick Open with `F1`, it doesn't mean that it's open and you can just start typing; you must first wait for the input element to be in the DOM as well as be the current 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.
|
||||
- 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 and wait for that instead.
|
||||
|
||||
@@ -3,8 +3,13 @@
|
||||
"version": "0.1.0",
|
||||
"main": "./src/main.js",
|
||||
"scripts": {
|
||||
"postinstall": "tsc",
|
||||
"watch": "tsc --watch",
|
||||
"postinstall": "npm run compile",
|
||||
"compile": "npm run copy-driver && npm run copy-driver-definition && tsc",
|
||||
"watch": "concurrently \"npm run watch-driver\" \"npm run watch-driver-definition\" \"tsc --watch\"",
|
||||
"copy-driver": "cpx src/vscode/driver.js out/vscode",
|
||||
"watch-driver": "cpx src/vscode/driver.js out/vscode -w",
|
||||
"copy-driver-definition": "node tools/copy-driver-definition.js",
|
||||
"watch-driver-definition": "watch \"node tools/copy-driver-definition.js\" ../../src/vs/platform/driver/common",
|
||||
"mocha": "mocha"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -15,6 +20,8 @@
|
||||
"@types/node": "8.0.33",
|
||||
"@types/rimraf": "2.0.2",
|
||||
"@types/webdriverio": "4.6.1",
|
||||
"concurrently": "^3.5.1",
|
||||
"cpx": "^1.5.0",
|
||||
"electron": "1.7.7",
|
||||
"htmlparser2": "^3.9.2",
|
||||
"mkdirp": "^0.5.1",
|
||||
@@ -22,9 +29,9 @@
|
||||
"ncp": "^2.0.0",
|
||||
"portastic": "^1.0.1",
|
||||
"rimraf": "^2.6.1",
|
||||
"spectron": "^3.7.2",
|
||||
"strip-json-comments": "^2.0.1",
|
||||
"tmp": "0.0.33",
|
||||
"typescript": "2.5.2"
|
||||
"typescript": "2.5.2",
|
||||
"watch": "^1.0.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
146
test/smoke/src/application.ts
Normal file
146
test/smoke/src/application.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Workbench } from './areas/workbench/workbench';
|
||||
import * as fs from 'fs';
|
||||
import * as cp from 'child_process';
|
||||
import { Code, spawn, SpawnOptions } from './vscode/code';
|
||||
import { Logger } from './logger';
|
||||
|
||||
export enum Quality {
|
||||
Dev,
|
||||
Insiders,
|
||||
Stable
|
||||
}
|
||||
|
||||
export interface ApplicationOptions extends SpawnOptions {
|
||||
quality: Quality;
|
||||
workspacePath: string;
|
||||
workspaceFilePath: string;
|
||||
waitTime: number;
|
||||
}
|
||||
|
||||
export class Application {
|
||||
|
||||
private _code: Code | undefined;
|
||||
private _workbench: Workbench;
|
||||
private keybindings: any[];
|
||||
|
||||
constructor(private options: ApplicationOptions) { }
|
||||
|
||||
get quality(): Quality {
|
||||
return this.options.quality;
|
||||
}
|
||||
|
||||
get code(): Code {
|
||||
return this._code!;
|
||||
}
|
||||
|
||||
get workbench(): Workbench {
|
||||
return this._workbench;
|
||||
}
|
||||
|
||||
get logger(): Logger {
|
||||
return this.options.logger;
|
||||
}
|
||||
|
||||
get workspacePath(): string {
|
||||
return this.options.workspacePath;
|
||||
}
|
||||
|
||||
get extensionsPath(): string {
|
||||
return this.options.extensionsPath;
|
||||
}
|
||||
|
||||
get userDataPath(): string {
|
||||
return this.options.userDataDir;
|
||||
}
|
||||
|
||||
get workspaceFilePath(): string {
|
||||
return this.options.workspaceFilePath;
|
||||
}
|
||||
|
||||
async start(): Promise<any> {
|
||||
await this._start();
|
||||
await this.code.waitForElement('.explorer-folders-view');
|
||||
await this.code.waitForActiveElement(`.editor-container[id="workbench.editor.walkThroughPart"] > div > div[tabIndex="0"]`);
|
||||
}
|
||||
|
||||
async restart(options: { workspaceOrFolder?: string, extraArgs?: string[] }): Promise<any> {
|
||||
await this.stop();
|
||||
await new Promise(c => setTimeout(c, 1000));
|
||||
await this._start(options.workspaceOrFolder, options.extraArgs);
|
||||
}
|
||||
|
||||
private async _start(workspaceOrFolder = this.options.workspacePath, extraArgs: string[] = []): Promise<any> {
|
||||
await this.retrieveKeybindings();
|
||||
cp.execSync('git checkout .', { cwd: this.options.workspacePath });
|
||||
await this.startApplication(workspaceOrFolder, extraArgs);
|
||||
await this.checkWindowReady();
|
||||
}
|
||||
|
||||
async reload(): Promise<any> {
|
||||
this.code.reload()
|
||||
.catch(err => null); // ignore the connection drop errors
|
||||
|
||||
// needs to be enough to propagate the 'Reload Window' command
|
||||
await new Promise(c => setTimeout(c, 1500));
|
||||
await this.checkWindowReady();
|
||||
}
|
||||
|
||||
async stop(): Promise<any> {
|
||||
if (this._code) {
|
||||
this._code.dispose();
|
||||
this._code = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async capturePage(): Promise<string> {
|
||||
return this.code.capturePage();
|
||||
}
|
||||
|
||||
private async startApplication(workspaceOrFolder: string, extraArgs: string[] = []): Promise<any> {
|
||||
this._code = await spawn({
|
||||
codePath: this.options.codePath,
|
||||
workspacePath: workspaceOrFolder,
|
||||
userDataDir: this.options.userDataDir,
|
||||
extensionsPath: this.options.extensionsPath,
|
||||
logger: this.options.logger,
|
||||
extraArgs
|
||||
});
|
||||
|
||||
this._workbench = new Workbench(this._code, this.keybindings, this.userDataPath);
|
||||
}
|
||||
|
||||
private async checkWindowReady(): Promise<any> {
|
||||
if (!this.code) {
|
||||
console.error('No code instance found');
|
||||
return;
|
||||
}
|
||||
|
||||
await this.code.waitForWindowIds(ids => ids.length > 0);
|
||||
await this.code.waitForElement('.monaco-workbench');
|
||||
|
||||
// wait a bit, since focus might be stolen off widgets
|
||||
// as soon as they open (eg quick open)
|
||||
await new Promise(c => setTimeout(c, 500));
|
||||
}
|
||||
|
||||
private retrieveKeybindings(): Promise<void> {
|
||||
return new Promise((c, e) => {
|
||||
fs.readFile(process.env.VSCODE_KEYBINDINGS_PATH as string, 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
try {
|
||||
this.keybindings = JSON.parse(data);
|
||||
c();
|
||||
} catch (e) {
|
||||
throw new Error(`Error parsing keybindings JSON: ${e}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Element } from 'webdriverio';
|
||||
import { SpectronApplication } from '../../spectron/application';
|
||||
import { Code } from '../../vscode/code';
|
||||
|
||||
export enum ActivityBarPosition {
|
||||
LEFT = 0,
|
||||
@@ -13,11 +12,9 @@ export enum ActivityBarPosition {
|
||||
|
||||
export class ActivityBar {
|
||||
|
||||
constructor(private spectron: SpectronApplication) {
|
||||
// noop
|
||||
}
|
||||
constructor(private code: Code) { }
|
||||
|
||||
public async getActivityBar(position: ActivityBarPosition): Promise<Element> {
|
||||
async waitForActivityBar(position: ActivityBarPosition): Promise<void> {
|
||||
let positionClass: string;
|
||||
|
||||
if (position === ActivityBarPosition.LEFT) {
|
||||
@@ -28,6 +25,6 @@ export class ActivityBar {
|
||||
throw new Error('No such position for activity bar defined.');
|
||||
}
|
||||
|
||||
return this.spectron.client.waitForElement(`.part.activitybar.${positionClass}`);
|
||||
await this.code.waitForElement(`.part.activitybar.${positionClass}`);
|
||||
}
|
||||
}
|
||||
@@ -3,55 +3,44 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { SpectronApplication } from '../../spectron/application';
|
||||
import { Application } from '../../application';
|
||||
import { ProblemSeverity, Problems } from '../problems/problems';
|
||||
|
||||
export function setup() {
|
||||
describe('CSS', () => {
|
||||
before(function () {
|
||||
this.app.suiteName = 'CSS';
|
||||
});
|
||||
|
||||
it('verifies quick outline', async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
await app.workbench.quickopen.openFile('style.css');
|
||||
|
||||
await app.workbench.editor.openOutline();
|
||||
await app.workbench.quickopen.openQuickOutline();
|
||||
await app.workbench.quickopen.waitForQuickOpenElements(names => names.length === 2);
|
||||
});
|
||||
|
||||
it('verifies warnings for the empty rule', async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
await app.workbench.quickopen.openFile('style.css');
|
||||
await app.workbench.editor.waitForTypeInEditor('style.css', '.foo{}');
|
||||
|
||||
let warning = await app.client.waitForElement(Problems.getSelectorInEditor(ProblemSeverity.WARNING));
|
||||
await app.screenCapturer.capture('CSS Warning in editor');
|
||||
assert.ok(warning, `Warning squiggle is not shown in 'style.css'.`);
|
||||
await app.code.waitForElement(Problems.getSelectorInEditor(ProblemSeverity.WARNING));
|
||||
|
||||
await app.workbench.problems.showProblemsView();
|
||||
warning = await app.client.waitForElement(Problems.getSelectorInProblemsView(ProblemSeverity.WARNING));
|
||||
await app.screenCapturer.capture('CSS Warning in problems view');
|
||||
assert.ok(warning, 'Warning does not appear in Problems view.');
|
||||
await app.code.waitForElement(Problems.getSelectorInProblemsView(ProblemSeverity.WARNING));
|
||||
await app.workbench.problems.hideProblemsView();
|
||||
});
|
||||
|
||||
it('verifies that warning becomes an error once setting changed', async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
// settings might take a while to update?
|
||||
this.timeout(40000);
|
||||
|
||||
const app = this.app as Application;
|
||||
await app.workbench.settingsEditor.addUserSetting('css.lint.emptyRules', '"error"');
|
||||
await app.workbench.quickopen.openFile('style.css');
|
||||
await app.workbench.editor.waitForTypeInEditor('style.css', '.foo{}');
|
||||
|
||||
let error = await app.client.waitForElement(Problems.getSelectorInEditor(ProblemSeverity.ERROR));
|
||||
await app.screenCapturer.capture('CSS Error in editor');
|
||||
assert.ok(error, `Warning squiggle is not shown in 'style.css'.`);
|
||||
await app.code.waitForElement(Problems.getSelectorInEditor(ProblemSeverity.ERROR));
|
||||
|
||||
const problems = new Problems(app);
|
||||
const problems = new Problems(app.code, app.workbench);
|
||||
await problems.showProblemsView();
|
||||
error = await app.client.waitForElement(Problems.getSelectorInProblemsView(ProblemSeverity.ERROR));
|
||||
await app.screenCapturer.capture('CSS Error in probles view');
|
||||
assert.ok(error, 'Warning does not appear in Problems view.');
|
||||
await app.code.waitForElement(Problems.getSelectorInProblemsView(ProblemSeverity.ERROR));
|
||||
await problems.hideProblemsView();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,17 +8,12 @@ import * as http from 'http';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as stripJsonComments from 'strip-json-comments';
|
||||
import { SpectronApplication } from '../../spectron/application';
|
||||
import { Application } from '../../application';
|
||||
|
||||
export function setup() {
|
||||
describe('Debug', () => {
|
||||
before(async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
app.suiteName = 'Debug';
|
||||
});
|
||||
|
||||
it('configure launch json', async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
|
||||
await app.workbench.debug.openDebugViewlet();
|
||||
await app.workbench.quickopen.openFile('app.js');
|
||||
@@ -31,7 +26,6 @@ export function setup() {
|
||||
fs.writeFileSync(launchJsonPath, JSON.stringify(config, undefined, 4), 'utf8');
|
||||
|
||||
await app.workbench.editor.waitForEditorContents('launch.json', contents => /"protocol": "inspector"/.test(contents));
|
||||
await app.screenCapturer.capture('launch.json file');
|
||||
|
||||
assert.equal(config.configurations[0].request, 'launch');
|
||||
assert.equal(config.configurations[0].type, 'node');
|
||||
@@ -43,66 +37,61 @@ export function setup() {
|
||||
});
|
||||
|
||||
it('breakpoints', async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
|
||||
await app.workbench.quickopen.openFile('index.js');
|
||||
await app.workbench.debug.setBreakpointOnLine(6);
|
||||
await app.screenCapturer.capture('breakpoints are set');
|
||||
});
|
||||
|
||||
let port: number;
|
||||
it('start debugging', async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
|
||||
// TODO@isidor
|
||||
await new Promise(c => setTimeout(c, 100));
|
||||
|
||||
port = await app.workbench.debug.startDebugging();
|
||||
await app.screenCapturer.capture('debugging has started');
|
||||
|
||||
await new Promise((c, e) => {
|
||||
const request = http.get(`http://localhost:${port}`);
|
||||
request.on('error', e);
|
||||
app.workbench.debug.waitForStackFrame(sf => sf.name === 'index.js' && sf.lineNumber === 6, 'looking for index.js and line 6').then(c, e);
|
||||
});
|
||||
|
||||
await app.screenCapturer.capture('debugging is paused');
|
||||
});
|
||||
|
||||
it('focus stack frames and variables', async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
|
||||
await app.client.waitFor(() => app.workbench.debug.getLocalVariableCount(), c => c === 4, 'there should be 4 local variables');
|
||||
await app.workbench.debug.waitForVariableCount(4);
|
||||
|
||||
await app.workbench.debug.focusStackFrame('layer.js', 'looking for layer.js');
|
||||
await app.client.waitFor(() => app.workbench.debug.getLocalVariableCount(), c => c === 5, 'there should be 5 local variables');
|
||||
await app.workbench.debug.waitForVariableCount(5);
|
||||
|
||||
await app.workbench.debug.focusStackFrame('route.js', 'looking for route.js');
|
||||
await app.client.waitFor(() => app.workbench.debug.getLocalVariableCount(), c => c === 3, 'there should be 3 local variables');
|
||||
await app.workbench.debug.waitForVariableCount(3);
|
||||
|
||||
await app.workbench.debug.focusStackFrame('index.js', 'looking for index.js');
|
||||
await app.client.waitFor(() => app.workbench.debug.getLocalVariableCount(), c => c === 4, 'there should be 4 local variables');
|
||||
await app.workbench.debug.waitForVariableCount(4);
|
||||
});
|
||||
|
||||
it('stepOver, stepIn, stepOut', async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
|
||||
await app.workbench.debug.stepIn();
|
||||
await app.screenCapturer.capture('debugging has stepped in');
|
||||
|
||||
const first = await app.workbench.debug.waitForStackFrame(sf => sf.name === 'response.js', 'looking for response.js');
|
||||
await app.workbench.debug.stepOver();
|
||||
await app.screenCapturer.capture('debugging has stepped over');
|
||||
|
||||
await app.workbench.debug.waitForStackFrame(sf => sf.name === 'response.js' && sf.lineNumber === first.lineNumber + 1, `looking for response.js and line ${first.lineNumber + 1}`);
|
||||
await app.workbench.debug.stepOut();
|
||||
await app.screenCapturer.capture('debugging has stepped out');
|
||||
|
||||
await app.workbench.debug.waitForStackFrame(sf => sf.name === 'index.js' && sf.lineNumber === 7, `looking for index.js and line 7`);
|
||||
});
|
||||
|
||||
it('continue', async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
|
||||
await app.workbench.debug.continue();
|
||||
await app.screenCapturer.capture('debugging has continued');
|
||||
|
||||
await new Promise((c, e) => {
|
||||
const request = http.get(`http://localhost:${port}`);
|
||||
@@ -110,20 +99,18 @@ export function setup() {
|
||||
app.workbench.debug.waitForStackFrame(sf => sf.name === 'index.js' && sf.lineNumber === 6, `looking for index.js and line 6`).then(c, e);
|
||||
});
|
||||
|
||||
await app.screenCapturer.capture('debugging is paused');
|
||||
});
|
||||
|
||||
it('debug console', async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
|
||||
await app.workbench.debug.waitForReplCommand('2 + 2', r => r === '4');
|
||||
});
|
||||
|
||||
it('stop debugging', async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
|
||||
await app.workbench.debug.stopDebugging();
|
||||
await app.screenCapturer.capture('debugging has stopped');
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -3,13 +3,16 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../../spectron/application';
|
||||
import { Viewlet } from '../workbench/viewlet';
|
||||
import { Commands } from '../workbench/workbench';
|
||||
import { Code, findElement } from '../../vscode/code';
|
||||
import { Editors } from '../editor/editors';
|
||||
import { Editor } from '../editor/editor';
|
||||
import { IElement } from '../../vscode/driver';
|
||||
|
||||
const VIEWLET = 'div[id="workbench.view.debug"]';
|
||||
const DEBUG_VIEW = `${VIEWLET} .debug-view-content`;
|
||||
const CONFIGURE = `div[id="workbench.parts.sidebar"] .actions-container .configure`;
|
||||
const START = `.icon[title="Start Debugging"]`;
|
||||
const STOP = `.debug-actions-widget .debug-action.stop`;
|
||||
const STEP_OVER = `.debug-actions-widget .debug-action.step-over`;
|
||||
const STEP_IN = `.debug-actions-widget .debug-action.step-into`;
|
||||
@@ -20,152 +23,120 @@ const BREAKPOINT_GLYPH = '.debug-breakpoint';
|
||||
const PAUSE = `.debug-actions-widget .debug-action.pause`;
|
||||
const DEBUG_STATUS_BAR = `.statusbar.debugging`;
|
||||
const NOT_DEBUG_STATUS_BAR = `.statusbar:not(debugging)`;
|
||||
const TOOLBAR_HIDDEN = `.debug-actions-widget.builder-hidden`;
|
||||
const TOOLBAR_HIDDEN = `.debug-actions-widget.monaco-builder-hidden`;
|
||||
const STACK_FRAME = `${VIEWLET} .monaco-tree-row .stack-frame`;
|
||||
const SPECIFIC_STACK_FRAME = filename => `${STACK_FRAME} .file[title$="${filename}"]`;
|
||||
const VARIABLE = `${VIEWLET} .debug-variables .monaco-tree-row .expression`;
|
||||
const CONSOLE_OUTPUT = `.repl .output.expression`;
|
||||
const CONSOLE_OUTPUT = `.repl .output.expression .value`;
|
||||
const CONSOLE_INPUT_OUTPUT = `.repl .input-output-pair .output.expression .value`;
|
||||
|
||||
const REPL_FOCUSED = '.repl-input-wrapper .monaco-editor textarea';
|
||||
|
||||
export interface IStackFrame {
|
||||
id: string;
|
||||
name: string;
|
||||
lineNumber: number;
|
||||
}
|
||||
|
||||
function toStackFrame(element: IElement): IStackFrame {
|
||||
const name = findElement(element, e => /\bfile-name\b/.test(e.className))!;
|
||||
const line = findElement(element, e => /\bline-number\b/.test(e.className))!;
|
||||
const lineNumber = line.textContent ? parseInt(line.textContent.split(':').shift() || '0') : 0;
|
||||
|
||||
return {
|
||||
name: name.textContent || '',
|
||||
lineNumber
|
||||
};
|
||||
}
|
||||
|
||||
export class Debug extends Viewlet {
|
||||
|
||||
constructor(spectron: SpectronApplication) {
|
||||
super(spectron);
|
||||
constructor(code: Code, private commands: Commands, private editors: Editors, private editor: Editor) {
|
||||
super(code);
|
||||
}
|
||||
|
||||
async openDebugViewlet(): Promise<any> {
|
||||
await this.spectron.runCommand('workbench.view.debug');
|
||||
await this.spectron.client.waitForElement(DEBUG_VIEW);
|
||||
await this.commands.runCommand('workbench.view.debug');
|
||||
await this.code.waitForElement(DEBUG_VIEW);
|
||||
}
|
||||
|
||||
async configure(): Promise<any> {
|
||||
await this.spectron.client.waitAndClick(CONFIGURE);
|
||||
await this.spectron.workbench.waitForEditorFocus('launch.json');
|
||||
await this.code.waitAndClick(CONFIGURE);
|
||||
await this.editors.waitForEditorFocus('launch.json');
|
||||
}
|
||||
|
||||
async setBreakpointOnLine(lineNumber: number): Promise<any> {
|
||||
await this.spectron.client.waitForElement(`${GLYPH_AREA}(${lineNumber})`);
|
||||
await this.spectron.client.leftClick(`${GLYPH_AREA}(${lineNumber})`, 5, 5);
|
||||
await this.spectron.client.waitForElement(BREAKPOINT_GLYPH);
|
||||
await this.code.waitForElement(`${GLYPH_AREA}(${lineNumber})`);
|
||||
await this.code.waitAndClick(`${GLYPH_AREA}(${lineNumber})`, 5, 5);
|
||||
await this.code.waitForElement(BREAKPOINT_GLYPH);
|
||||
}
|
||||
|
||||
async startDebugging(): Promise<number> {
|
||||
await this.spectron.client.waitAndClick(START);
|
||||
await this.spectron.client.waitForElement(PAUSE);
|
||||
await this.spectron.client.waitForElement(DEBUG_STATUS_BAR);
|
||||
await this.commands.runCommand('workbench.action.debug.start');
|
||||
await this.code.waitForElement(PAUSE);
|
||||
await this.code.waitForElement(DEBUG_STATUS_BAR);
|
||||
const portPrefix = 'Port: ';
|
||||
await this.spectron.client.waitFor(async () => {
|
||||
const output = await this.getConsoleOutput();
|
||||
return output.join('');
|
||||
}, text => !!text && text.indexOf(portPrefix) >= 0);
|
||||
const output = await this.getConsoleOutput();
|
||||
const lastOutput = output.pop();
|
||||
|
||||
const output = await this.waitForOutput(output => output.some(line => line.indexOf(portPrefix) >= 0));
|
||||
const lastOutput = output.filter(line => line.indexOf(portPrefix) >= 0)[0];
|
||||
|
||||
return lastOutput ? parseInt(lastOutput.substr(portPrefix.length)) : 3000;
|
||||
}
|
||||
|
||||
async stepOver(): Promise<any> {
|
||||
await this.spectron.client.waitAndClick(STEP_OVER);
|
||||
await this.code.waitAndClick(STEP_OVER);
|
||||
}
|
||||
|
||||
async stepIn(): Promise<any> {
|
||||
await this.spectron.client.waitAndClick(STEP_IN);
|
||||
await this.code.waitAndClick(STEP_IN);
|
||||
}
|
||||
|
||||
async stepOut(): Promise<any> {
|
||||
await this.spectron.client.waitAndClick(STEP_OUT);
|
||||
await this.code.waitAndClick(STEP_OUT);
|
||||
}
|
||||
|
||||
async continue(): Promise<any> {
|
||||
await this.spectron.client.waitAndClick(CONTINUE);
|
||||
await this.code.waitAndClick(CONTINUE);
|
||||
await this.waitForStackFrameLength(0);
|
||||
}
|
||||
|
||||
async stopDebugging(): Promise<any> {
|
||||
await this.spectron.client.waitAndClick(STOP);
|
||||
await this.spectron.client.waitForElement(TOOLBAR_HIDDEN);
|
||||
await this.spectron.client.waitForElement(NOT_DEBUG_STATUS_BAR);
|
||||
await this.code.waitAndClick(STOP);
|
||||
await this.code.waitForElement(TOOLBAR_HIDDEN);
|
||||
await this.code.waitForElement(NOT_DEBUG_STATUS_BAR);
|
||||
}
|
||||
|
||||
async waitForStackFrame(func: (stackFrame: IStackFrame) => boolean, message: string): Promise<IStackFrame> {
|
||||
return await this.spectron.client.waitFor(async () => {
|
||||
const stackFrames = await this.getStackFrames();
|
||||
return stackFrames.filter(func)[0];
|
||||
}, void 0, `Waiting for Stack Frame: ${message}`);
|
||||
const elements = await this.code.waitForElements(STACK_FRAME, true, elements => elements.some(e => func(toStackFrame(e))));
|
||||
return elements.map(toStackFrame).filter(s => func(s))[0];
|
||||
}
|
||||
|
||||
async waitForStackFrameLength(length: number): Promise<any> {
|
||||
return await this.spectron.client.waitFor(() => this.getStackFrames(), stackFrames => stackFrames.length === length);
|
||||
await this.code.waitForElements(STACK_FRAME, false, result => result.length === length);
|
||||
}
|
||||
|
||||
async focusStackFrame(name: string, message: string): Promise<any> {
|
||||
const stackFrame = await this.waitForStackFrame(sf => sf.name === name, message);
|
||||
await this.spectron.client.spectron.client.elementIdClick(stackFrame.id);
|
||||
await this.spectron.workbench.waitForTab(name);
|
||||
await this.code.waitAndClick(SPECIFIC_STACK_FRAME(name));
|
||||
await this.editors.waitForTab(name);
|
||||
}
|
||||
|
||||
async waitForReplCommand(text: string, accept: (result: string) => boolean): Promise<void> {
|
||||
await this.spectron.workbench.quickopen.runCommand('Debug: Focus Debug Console');
|
||||
await this.spectron.client.waitForActiveElement(REPL_FOCUSED);
|
||||
await this.spectron.client.setValue(REPL_FOCUSED, text);
|
||||
await this.commands.runCommand('Debug: Focus Debug Console');
|
||||
await this.code.waitForActiveElement(REPL_FOCUSED);
|
||||
await this.code.waitForSetValue(REPL_FOCUSED, text);
|
||||
|
||||
// Wait for the keys to be picked up by the editor model such that repl evalutes what just got typed
|
||||
await this.spectron.workbench.editor.waitForEditorContents('debug:input', s => s.indexOf(text) >= 0);
|
||||
await this.spectron.client.keys(['Enter', 'NULL']);
|
||||
await this.spectron.client.waitForElement(CONSOLE_INPUT_OUTPUT);
|
||||
await this.spectron.client.waitFor(async () => {
|
||||
const result = await this.getConsoleOutput();
|
||||
return result[result.length - 1] || '';
|
||||
}, accept);
|
||||
await this.editor.waitForEditorContents('debug:input', s => s.indexOf(text) >= 0);
|
||||
await this.code.dispatchKeybinding('enter');
|
||||
await this.code.waitForElement(CONSOLE_INPUT_OUTPUT);
|
||||
await this.waitForOutput(output => accept(output[output.length - 1] || ''));
|
||||
}
|
||||
|
||||
async getLocalVariableCount(): Promise<number> {
|
||||
return await this.spectron.webclient.selectorExecute(VARIABLE, div => (Array.isArray(div) ? div : [div]).length);
|
||||
async waitForVariableCount(count: number): Promise<void> {
|
||||
await this.code.waitForElements(VARIABLE, false, els => els.length === count);
|
||||
}
|
||||
|
||||
async getStackFramesLength(): Promise<number> {
|
||||
const stackFrames = await this.getStackFrames();
|
||||
return stackFrames.length;
|
||||
}
|
||||
|
||||
private async getStackFrames(): Promise<IStackFrame[]> {
|
||||
const result = await this.spectron.webclient.selectorExecute(STACK_FRAME,
|
||||
div => (Array.isArray(div) ? div : [div]).map(element => {
|
||||
const name = element.querySelector('.file-name') as HTMLElement;
|
||||
const line = element.querySelector('.line-number') as HTMLElement;
|
||||
const lineNumber = line.textContent ? parseInt(line.textContent.split(':').shift() || '0') : 0;
|
||||
|
||||
return {
|
||||
name: name.textContent,
|
||||
lineNumber,
|
||||
element
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
if (!Array.isArray(result)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return result
|
||||
.map(({ name, lineNumber, element }) => ({ name, lineNumber, id: element.ELEMENT }));
|
||||
}
|
||||
|
||||
private async getConsoleOutput(): Promise<string[]> {
|
||||
const result = await this.spectron.webclient.selectorExecute(CONSOLE_OUTPUT,
|
||||
div => (Array.isArray(div) ? div : [div]).map(element => {
|
||||
const value = element.querySelector('.value') as HTMLElement;
|
||||
return value && value.textContent;
|
||||
}).filter(line => !!line)
|
||||
);
|
||||
|
||||
return result;
|
||||
private async waitForOutput(fn: (output: string[]) => boolean): Promise<string[]> {
|
||||
const elements = await this.code.waitForElements(CONSOLE_OUTPUT, false, elements => fn(elements.map(e => e.textContent)));
|
||||
return elements.map(e => e.textContent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,27 +3,23 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../../spectron/application';
|
||||
import { Application } from '../../application';
|
||||
|
||||
export function setup() {
|
||||
describe('Editor', () => {
|
||||
before(function () {
|
||||
this.app.suiteName = 'Editor';
|
||||
});
|
||||
|
||||
it('shows correct quick outline', async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
await app.workbench.quickopen.openFile('www');
|
||||
|
||||
await app.workbench.editor.openOutline();
|
||||
await app.workbench.quickopen.openQuickOutline();
|
||||
await app.workbench.quickopen.waitForQuickOpenElements(names => names.length >= 6);
|
||||
});
|
||||
|
||||
it(`finds 'All References' to 'app'`, async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
await app.workbench.quickopen.openFile('www');
|
||||
|
||||
const references = await app.workbench.editor.findReferences('app', 7);
|
||||
const references = await app.workbench.editor.findReferences('www', 'app', 7);
|
||||
|
||||
await references.waitForReferencesCountInTitle(3);
|
||||
await references.waitForReferencesCount(3);
|
||||
@@ -31,11 +27,10 @@ export function setup() {
|
||||
});
|
||||
|
||||
it(`renames local 'app' variable`, async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
await app.workbench.quickopen.openFile('www');
|
||||
await app.workbench.editor.rename('www', 7, 'app', 'newApp');
|
||||
await app.workbench.editor.waitForEditorContents('www', contents => contents.indexOf('newApp') > -1);
|
||||
await app.screenCapturer.capture('Rename result');
|
||||
});
|
||||
|
||||
// it('folds/unfolds the code correctly', async function () {
|
||||
@@ -55,19 +50,19 @@ export function setup() {
|
||||
// });
|
||||
|
||||
it(`verifies that 'Go To Definition' works`, async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
await app.workbench.quickopen.openFile('app.js');
|
||||
|
||||
await app.workbench.editor.gotoDefinition('express', 11);
|
||||
await app.workbench.editor.gotoDefinition('app.js', 'express', 11);
|
||||
|
||||
await app.workbench.waitForActiveTab('index.d.ts');
|
||||
await app.workbench.editors.waitForActiveTab('index.d.ts');
|
||||
});
|
||||
|
||||
it(`verifies that 'Peek Definition' works`, async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
await app.workbench.quickopen.openFile('app.js');
|
||||
|
||||
const peek = await app.workbench.editor.peekDefinition('express', 11);
|
||||
const peek = await app.workbench.editor.peekDefinition('app.js', 'express', 11);
|
||||
|
||||
await peek.waitForFile('index.d.ts');
|
||||
});
|
||||
|
||||
@@ -3,192 +3,131 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../../spectron/application';
|
||||
import { QuickOutline } from './quickoutline';
|
||||
import { References } from './peek';
|
||||
import { Commands } from '../workbench/workbench';
|
||||
import { Code } from '../../vscode/code';
|
||||
|
||||
const RENAME_BOX = '.monaco-editor .monaco-editor.rename-box';
|
||||
const RENAME_INPUT = `${RENAME_BOX} .rename-input`;
|
||||
const EDITOR = filename => `.monaco-editor[data-uri$="${filename}"]`;
|
||||
const VIEW_LINES = filename => `${EDITOR(filename)} .view-lines`;
|
||||
const LINE_NUMBERS = filename => `${EDITOR(filename)} .margin .margin-view-overlays .line-numbers`;
|
||||
|
||||
export class Editor {
|
||||
|
||||
private static readonly VIEW_LINES = '.monaco-editor .view-lines';
|
||||
private static readonly LINE_NUMBERS = '.monaco-editor .margin .margin-view-overlays .line-numbers';
|
||||
private static readonly FOLDING_EXPANDED = '.monaco-editor .margin .margin-view-overlays>:nth-child(${INDEX}) .folding';
|
||||
private static readonly FOLDING_COLLAPSED = `${Editor.FOLDING_EXPANDED}.collapsed`;
|
||||
|
||||
constructor(private spectron: SpectronApplication) {
|
||||
}
|
||||
constructor(private code: Code, private commands: Commands) { }
|
||||
|
||||
async openOutline(): Promise<QuickOutline> {
|
||||
const outline = new QuickOutline(this.spectron);
|
||||
await outline.open();
|
||||
return outline;
|
||||
}
|
||||
|
||||
async findReferences(term: string, line: number): Promise<References> {
|
||||
await this.clickOnTerm(term, line);
|
||||
await this.spectron.workbench.quickopen.runCommand('Find All References');
|
||||
const references = new References(this.spectron);
|
||||
async findReferences(filename: string, term: string, line: number): Promise<References> {
|
||||
await this.clickOnTerm(filename, term, line);
|
||||
await this.commands.runCommand('Find All References');
|
||||
const references = new References(this.code);
|
||||
await references.waitUntilOpen();
|
||||
return references;
|
||||
}
|
||||
|
||||
async rename(filename: string, line: number, from: string, to: string): Promise<void> {
|
||||
await this.clickOnTerm(from, line);
|
||||
await this.spectron.workbench.quickopen.runCommand('Rename Symbol');
|
||||
await this.clickOnTerm(filename, from, line);
|
||||
await this.commands.runCommand('Rename Symbol');
|
||||
|
||||
await this.spectron.client.waitForActiveElement(RENAME_INPUT);
|
||||
await this.spectron.client.setValue(RENAME_INPUT, to);
|
||||
await this.code.waitForActiveElement(RENAME_INPUT);
|
||||
await this.code.waitForSetValue(RENAME_INPUT, to);
|
||||
|
||||
await this.spectron.client.keys(['Enter', 'NULL']);
|
||||
await this.code.dispatchKeybinding('enter');
|
||||
}
|
||||
|
||||
async gotoDefinition(term: string, line: number): Promise<void> {
|
||||
await this.clickOnTerm(term, line);
|
||||
await this.spectron.workbench.quickopen.runCommand('Go to Definition');
|
||||
async gotoDefinition(filename: string, term: string, line: number): Promise<void> {
|
||||
await this.clickOnTerm(filename, term, line);
|
||||
await this.commands.runCommand('Go to Definition');
|
||||
}
|
||||
|
||||
async peekDefinition(term: string, line: number): Promise<References> {
|
||||
await this.clickOnTerm(term, line);
|
||||
await this.spectron.workbench.quickopen.runCommand('Peek Definition');
|
||||
const peek = new References(this.spectron);
|
||||
async peekDefinition(filename: string, term: string, line: number): Promise<References> {
|
||||
await this.clickOnTerm(filename, term, line);
|
||||
await this.commands.runCommand('Peek Definition');
|
||||
const peek = new References(this.code);
|
||||
await peek.waitUntilOpen();
|
||||
return peek;
|
||||
}
|
||||
|
||||
async waitForHighlightingLine(line: number): Promise<void> {
|
||||
const currentLineIndex = await this.getViewLineIndex(line);
|
||||
async waitForHighlightingLine(filename: string, line: number): Promise<void> {
|
||||
const currentLineIndex = await this.getViewLineIndex(filename, line);
|
||||
if (currentLineIndex) {
|
||||
await this.spectron.client.waitForElement(`.monaco-editor .view-overlays>:nth-child(${currentLineIndex}) .current-line`);
|
||||
await this.code.waitForElement(`.monaco-editor .view-overlays>:nth-child(${currentLineIndex}) .current-line`);
|
||||
return;
|
||||
}
|
||||
throw new Error('Cannot find line ' + line);
|
||||
}
|
||||
|
||||
async getSelector(term: string, line: number): Promise<string> {
|
||||
const lineIndex = await this.getViewLineIndex(line);
|
||||
const classNames = await this.spectron.client.waitFor(() => this.getClassSelectors(term, lineIndex), classNames => classNames && !!classNames.length, 'Getting class names for editor lines');
|
||||
return `${Editor.VIEW_LINES}>:nth-child(${lineIndex}) span span.${classNames[0]}`;
|
||||
private async getSelector(filename: string, term: string, line: number): Promise<string> {
|
||||
const lineIndex = await this.getViewLineIndex(filename, line);
|
||||
const classNames = await this.getClassSelectors(filename, term, lineIndex);
|
||||
|
||||
return `${VIEW_LINES(filename)}>:nth-child(${lineIndex}) span span.${classNames[0]}`;
|
||||
}
|
||||
|
||||
async foldAtLine(line: number): Promise<any> {
|
||||
const lineIndex = await this.getViewLineIndex(line);
|
||||
await this.spectron.client.waitAndClick(Editor.FOLDING_EXPANDED.replace('${INDEX}', '' + lineIndex));
|
||||
await this.spectron.client.waitForElement(Editor.FOLDING_COLLAPSED.replace('${INDEX}', '' + lineIndex));
|
||||
async foldAtLine(filename: string, line: number): Promise<any> {
|
||||
const lineIndex = await this.getViewLineIndex(filename, line);
|
||||
await this.code.waitAndClick(Editor.FOLDING_EXPANDED.replace('${INDEX}', '' + lineIndex));
|
||||
await this.code.waitForElement(Editor.FOLDING_COLLAPSED.replace('${INDEX}', '' + lineIndex));
|
||||
}
|
||||
|
||||
async unfoldAtLine(line: number): Promise<any> {
|
||||
const lineIndex = await this.getViewLineIndex(line);
|
||||
await this.spectron.client.waitAndClick(Editor.FOLDING_COLLAPSED.replace('${INDEX}', '' + lineIndex));
|
||||
await this.spectron.client.waitForElement(Editor.FOLDING_EXPANDED.replace('${INDEX}', '' + lineIndex));
|
||||
async unfoldAtLine(filename: string, line: number): Promise<any> {
|
||||
const lineIndex = await this.getViewLineIndex(filename, line);
|
||||
await this.code.waitAndClick(Editor.FOLDING_COLLAPSED.replace('${INDEX}', '' + lineIndex));
|
||||
await this.code.waitForElement(Editor.FOLDING_EXPANDED.replace('${INDEX}', '' + lineIndex));
|
||||
}
|
||||
|
||||
async waitUntilHidden(line: number): Promise<void> {
|
||||
await this.spectron.client.waitFor<number>(() => this.getViewLineIndexWithoutWait(line), lineNumber => lineNumber === undefined, 'Waiting until line number is hidden');
|
||||
private async clickOnTerm(filename: string, term: string, line: number): Promise<void> {
|
||||
const selector = await this.getSelector(filename, term, line);
|
||||
await this.code.waitAndClick(selector);
|
||||
}
|
||||
|
||||
async waitUntilShown(line: number): Promise<void> {
|
||||
await this.getViewLineIndex(line);
|
||||
}
|
||||
async waitForEditorFocus(filename: string, lineNumber: number, selectorPrefix = ''): Promise<void> {
|
||||
const editor = [selectorPrefix || '', EDITOR(filename)].join(' ');
|
||||
const line = `${editor} .view-lines > .view-line:nth-child(${lineNumber})`;
|
||||
const textarea = `${editor} textarea`;
|
||||
|
||||
async clickOnTerm(term: string, line: number): Promise<void> {
|
||||
const selector = await this.getSelector(term, line);
|
||||
await this.spectron.client.waitAndClick(selector);
|
||||
await this.code.waitAndClick(line, 0, 0);
|
||||
await this.code.waitForActiveElement(textarea);
|
||||
}
|
||||
|
||||
async waitForTypeInEditor(filename: string, text: string, selectorPrefix = ''): Promise<any> {
|
||||
const editor = [
|
||||
selectorPrefix || '',
|
||||
`.monaco-editor[data-uri$="${filename}"]`
|
||||
].join(' ');
|
||||
const editor = [selectorPrefix || '', EDITOR(filename)].join(' ');
|
||||
|
||||
await this.spectron.client.element(editor);
|
||||
await this.code.waitForElement(editor);
|
||||
|
||||
const textarea = `${editor} textarea`;
|
||||
await this.spectron.client.waitForActiveElement(textarea);
|
||||
await this.code.waitForActiveElement(textarea);
|
||||
|
||||
// https://github.com/Microsoft/vscode/issues/34203#issuecomment-334441786
|
||||
await this.spectron.client.spectron.client.selectorExecute(textarea, (elements, text) => {
|
||||
const textarea = (Array.isArray(elements) ? elements : [elements])[0] as HTMLTextAreaElement;
|
||||
const start = textarea.selectionStart;
|
||||
const newStart = start + text.length;
|
||||
const value = textarea.value;
|
||||
const newValue = value.substr(0, start) + text + value.substr(start);
|
||||
|
||||
textarea.value = newValue;
|
||||
textarea.setSelectionRange(newStart, newStart);
|
||||
|
||||
const event = new Event('input', { 'bubbles': true, 'cancelable': true });
|
||||
textarea.dispatchEvent(event);
|
||||
}, text);
|
||||
await this.code.waitForTypeInEditor(textarea, text);
|
||||
|
||||
await this.waitForEditorContents(filename, c => c.indexOf(text) > -1, selectorPrefix);
|
||||
}
|
||||
|
||||
async waitForEditorContents(filename: string, accept: (contents: string) => boolean, selectorPrefix = ''): Promise<any> {
|
||||
const selector = [
|
||||
selectorPrefix || '',
|
||||
`.monaco-editor[data-uri$="${filename}"] .view-lines`
|
||||
].join(' ');
|
||||
|
||||
return this.spectron.client.waitForTextContent(selector, undefined, c => accept(c.replace(/\u00a0/g, ' ')));
|
||||
const selector = [selectorPrefix || '', `${EDITOR(filename)} .view-lines`].join(' ');
|
||||
return this.code.waitForTextContent(selector, undefined, c => accept(c.replace(/\u00a0/g, ' ')));
|
||||
}
|
||||
|
||||
async waitForActiveEditor(filename: string): Promise<any> {
|
||||
const selector = `.editor-container .monaco-editor[data-uri$="${filename}"] textarea`;
|
||||
return this.spectron.client.waitForActiveElement(selector);
|
||||
private async getClassSelectors(filename: string, term: string, viewline: number): Promise<string[]> {
|
||||
const elements = await this.code.waitForElements(`${VIEW_LINES(filename)}>:nth-child(${viewline}) span span`, false, els => els.some(el => el.textContent === term));
|
||||
const { className } = elements.filter(r => r.textContent === term)[0];
|
||||
return className.split(/\s/g);
|
||||
}
|
||||
|
||||
// async waitForActiveEditorFirstLineText(filename: string): Promise<string> {
|
||||
// const selector = `.editor-container .monaco-editor[data-uri$="${filename}"] textarea`;
|
||||
// const result = await this.spectron.client.waitFor(
|
||||
// () => this.spectron.client.spectron.client.execute(s => {
|
||||
// if (!document.activeElement.matches(s)) {
|
||||
// return undefined;
|
||||
// }
|
||||
private async getViewLineIndex(filename: string, line: number): Promise<number> {
|
||||
const elements = await this.code.waitForElements(LINE_NUMBERS(filename), false, els => {
|
||||
return els.some(el => el.textContent === `${line}`);
|
||||
});
|
||||
|
||||
// let element: Element | null = document.activeElement;
|
||||
// while (element && !/monaco-editor/.test(element.className) && element !== document.body) {
|
||||
// element = element.parentElement;
|
||||
// }
|
||||
|
||||
// if (element && /monaco-editor/.test(element.className)) {
|
||||
// const firstLine = element.querySelector('.view-lines span span:nth-child(1)');
|
||||
|
||||
// if (firstLine) {
|
||||
// return (firstLine.textContent || '').replace(/\u00a0/g, ' '); // DAMN
|
||||
// }
|
||||
// }
|
||||
|
||||
// return undefined;
|
||||
// }, selector),
|
||||
// r => typeof r.value === 'string',
|
||||
// `wait for active editor first line: ${selector}`
|
||||
// );
|
||||
|
||||
// return result.value;
|
||||
// }
|
||||
|
||||
private async getClassSelectors(term: string, viewline: number): Promise<string[]> {
|
||||
const result: { text: string, className: string }[] = await this.spectron.webclient.selectorExecute(`${Editor.VIEW_LINES}>:nth-child(${viewline}) span span`,
|
||||
elements => (Array.isArray(elements) ? elements : [elements])
|
||||
.map(element => ({ text: element.textContent, className: element.className })));
|
||||
return result.filter(r => r.text === term).map(({ className }) => className);
|
||||
}
|
||||
|
||||
private async getViewLineIndex(line: number): Promise<number> {
|
||||
return await this.spectron.client.waitFor<number>(() => this.getViewLineIndexWithoutWait(line), void 0, 'Getting line index');
|
||||
}
|
||||
|
||||
private async getViewLineIndexWithoutWait(line: number): Promise<number | undefined> {
|
||||
const lineNumbers = await this.spectron.webclient.selectorExecute(Editor.LINE_NUMBERS,
|
||||
elements => (Array.isArray(elements) ? elements : [elements]).map(element => element.textContent));
|
||||
for (let index = 0; index < lineNumbers.length; index++) {
|
||||
if (lineNumbers[index] === `${line}`) {
|
||||
for (let index = 0; index < elements.length; index++) {
|
||||
if (elements[index].textContent === `${line}`) {
|
||||
return index + 1;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
|
||||
throw new Error('Line not found');
|
||||
}
|
||||
}
|
||||
44
test/smoke/src/areas/editor/editors.ts
Normal file
44
test/smoke/src/areas/editor/editors.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Commands } from '../workbench/workbench';
|
||||
import { Code } from '../../vscode/code';
|
||||
|
||||
export class Editors {
|
||||
|
||||
constructor(private code: Code, private commands: Commands) { }
|
||||
|
||||
async saveOpenedFile(): Promise<any> {
|
||||
await this.commands.runCommand('workbench.action.files.save');
|
||||
}
|
||||
|
||||
async selectTab(tabName: string, untitled: boolean = false): Promise<void> {
|
||||
await this.code.waitAndClick(`.tabs-container div.tab[aria-label="${tabName}, tab"]`);
|
||||
await this.waitForEditorFocus(tabName, untitled);
|
||||
}
|
||||
|
||||
async waitForActiveEditor(filename: string): Promise<any> {
|
||||
const selector = `.editor-container .monaco-editor[data-uri$="${filename}"] textarea`;
|
||||
return this.code.waitForActiveElement(selector);
|
||||
}
|
||||
|
||||
async waitForEditorFocus(fileName: string, untitled: boolean = false): Promise<void> {
|
||||
await this.waitForActiveTab(fileName);
|
||||
await this.waitForActiveEditor(fileName);
|
||||
}
|
||||
|
||||
async waitForActiveTab(fileName: string, isDirty: boolean = false): Promise<void> {
|
||||
await this.code.waitForElement(`.tabs-container div.tab.active${isDirty ? '.dirty' : ''}[aria-selected="true"][aria-label="${fileName}, tab"]`);
|
||||
}
|
||||
|
||||
async waitForTab(fileName: string, isDirty: boolean = false): Promise<void> {
|
||||
await this.code.waitForElement(`.tabs-container div.tab${isDirty ? '.dirty' : ''}[aria-label="${fileName}, tab"]`);
|
||||
}
|
||||
|
||||
async newUntitledFile(): Promise<void> {
|
||||
await this.commands.runCommand('workbench.action.files.newUntitledFile');
|
||||
await this.waitForEditorFocus('Untitled-1', true);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../../spectron/application';
|
||||
import { Code } from '../../vscode/code';
|
||||
|
||||
export class References {
|
||||
|
||||
@@ -12,30 +12,41 @@ export class References {
|
||||
private static readonly REFERENCES_TITLE_COUNT = `${References.REFERENCES_WIDGET} .head .peekview-title .meta`;
|
||||
private static readonly REFERENCES = `${References.REFERENCES_WIDGET} .body .ref-tree.inline .monaco-tree-row .reference`;
|
||||
|
||||
constructor(private spectron: SpectronApplication) {
|
||||
constructor(private code: Code) { }
|
||||
|
||||
async waitUntilOpen(): Promise<void> {
|
||||
await this.code.waitForElement(References.REFERENCES_WIDGET);
|
||||
}
|
||||
|
||||
public async waitUntilOpen(): Promise<void> {
|
||||
await this.spectron.client.waitForElement(References.REFERENCES_WIDGET);
|
||||
}
|
||||
|
||||
public async waitForReferencesCountInTitle(count: number): Promise<void> {
|
||||
await this.spectron.client.waitForText(References.REFERENCES_TITLE_COUNT, void 0, titleCount => {
|
||||
async waitForReferencesCountInTitle(count: number): Promise<void> {
|
||||
await this.code.waitForTextContent(References.REFERENCES_TITLE_COUNT, void 0, titleCount => {
|
||||
const matches = titleCount.match(/\d+/);
|
||||
return matches ? parseInt(matches[0]) === count : false;
|
||||
});
|
||||
}
|
||||
|
||||
public async waitForReferencesCount(count: number): Promise<void> {
|
||||
await this.spectron.client.waitForElements(References.REFERENCES, result => result && result.length === count);
|
||||
async waitForReferencesCount(count: number): Promise<void> {
|
||||
await this.code.waitForElements(References.REFERENCES, false, result => result && result.length === count);
|
||||
}
|
||||
|
||||
public async waitForFile(file: string): Promise<void> {
|
||||
await this.spectron.client.waitForText(References.REFERENCES_TITLE_FILE_NAME, file);
|
||||
async waitForFile(file: string): Promise<void> {
|
||||
await this.code.waitForTextContent(References.REFERENCES_TITLE_FILE_NAME, file);
|
||||
}
|
||||
|
||||
public async close(): Promise<void> {
|
||||
await this.spectron.client.keys(['Escape', 'NULL']);
|
||||
await this.spectron.client.waitForElement(References.REFERENCES_WIDGET, element => !element);
|
||||
async close(): Promise<void> {
|
||||
// Sometimes someone else eats up the `Escape` key
|
||||
let count = 0;
|
||||
while (true) {
|
||||
await this.code.dispatchKeybinding('escape');
|
||||
|
||||
try {
|
||||
await this.code.waitForElement(References.REFERENCES_WIDGET, el => !el, 10);
|
||||
return;
|
||||
} catch (err) {
|
||||
if (++count > 5) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../../spectron/application';
|
||||
import { QuickOpen } from '../quickopen/quickopen';
|
||||
|
||||
export class QuickOutline extends QuickOpen {
|
||||
|
||||
constructor(spectron: SpectronApplication) {
|
||||
super(spectron);
|
||||
}
|
||||
|
||||
public async open(): Promise<void> {
|
||||
await this.spectron.client.waitFor(async () => {
|
||||
await this.spectron.runCommand('workbench.action.gotoSymbol');
|
||||
const entry = await this.spectron.client.element('div[aria-label="Quick Picker"] .monaco-tree-rows.show-twisties div.monaco-tree-row .quick-open-entry');
|
||||
if (entry) {
|
||||
const text = await this.spectron.client.getText('div[aria-label="Quick Picker"] .monaco-tree-rows.show-twisties div.monaco-tree-row .quick-open-entry .monaco-icon-label .label-name .monaco-highlighted-label span');
|
||||
if (text !== 'No symbol information for the file') {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
await this.closeQuickOpen();
|
||||
}, undefined, 'Opening Outline');
|
||||
}
|
||||
}
|
||||
@@ -3,16 +3,12 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../../spectron/application';
|
||||
import { Application } from '../../application';
|
||||
|
||||
export function setup() {
|
||||
describe('Explorer', () => {
|
||||
before(function () {
|
||||
this.app.suiteName = 'Explorer';
|
||||
});
|
||||
|
||||
it('quick open search produces correct result', async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
const expectedNames = [
|
||||
'.eslintrc.json',
|
||||
'tasks.json',
|
||||
@@ -25,11 +21,11 @@ export function setup() {
|
||||
|
||||
await app.workbench.quickopen.openQuickOpen('.js');
|
||||
await app.workbench.quickopen.waitForQuickOpenElements(names => expectedNames.every(n => names.some(m => n === m)));
|
||||
await app.client.keys(['Escape', 'NULL']);
|
||||
await app.code.dispatchKeybinding('escape');
|
||||
});
|
||||
|
||||
it('quick open respects fuzzy matching', async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
const expectedNames = [
|
||||
'tasks.json',
|
||||
'app.js',
|
||||
@@ -38,7 +34,7 @@ export function setup() {
|
||||
|
||||
await app.workbench.quickopen.openQuickOpen('a.s');
|
||||
await app.workbench.quickopen.waitForQuickOpenElements(names => expectedNames.every(n => names.some(m => n === m)));
|
||||
await app.client.keys(['Escape', 'NULL']);
|
||||
await app.code.dispatchKeybinding('escape');
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -3,33 +3,34 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../../spectron/application';
|
||||
import { Viewlet } from '../workbench/viewlet';
|
||||
|
||||
import { Editors } from '../editor/editors';
|
||||
import { Commands } from '../workbench/workbench';
|
||||
import { Code } from '../../vscode/code';
|
||||
|
||||
export class Explorer extends Viewlet {
|
||||
|
||||
private static readonly EXPLORER_VIEWLET = 'div[id="workbench.view.explorer"]';
|
||||
private static readonly OPEN_EDITORS_VIEW = `${Explorer.EXPLORER_VIEWLET} .split-view-view:nth-child(1) .title`;
|
||||
|
||||
constructor(spectron: SpectronApplication) {
|
||||
super(spectron);
|
||||
constructor(code: Code, private commands: Commands, private editors: Editors) {
|
||||
super(code);
|
||||
}
|
||||
|
||||
public openExplorerView(): Promise<any> {
|
||||
return this.spectron.runCommand('workbench.view.explorer');
|
||||
openExplorerView(): Promise<any> {
|
||||
return this.commands.runCommand('workbench.view.explorer');
|
||||
}
|
||||
|
||||
public getOpenEditorsViewTitle(): Promise<string> {
|
||||
return this.spectron.client.waitForText(Explorer.OPEN_EDITORS_VIEW);
|
||||
async waitForOpenEditorsViewTitle(fn: (title: string) => boolean): Promise<void> {
|
||||
await this.code.waitForTextContent(Explorer.OPEN_EDITORS_VIEW, undefined, fn);
|
||||
}
|
||||
|
||||
public async openFile(fileName: string): Promise<any> {
|
||||
await this.spectron.client.doubleClickAndWait(`div[class="monaco-icon-label file-icon ${fileName}-name-file-icon ${this.getExtensionSelector(fileName)} explorer-item"]`);
|
||||
await this.spectron.workbench.waitForEditorFocus(fileName);
|
||||
async openFile(fileName: string): Promise<any> {
|
||||
await this.code.waitAndDoubleClick(`div[class="monaco-icon-label file-icon ${fileName}-name-file-icon ${this.getExtensionSelector(fileName)} explorer-item"]`);
|
||||
await this.editors.waitForEditorFocus(fileName);
|
||||
}
|
||||
|
||||
public getExtensionSelector(fileName: string): string {
|
||||
getExtensionSelector(fileName: string): string {
|
||||
const extension = fileName.split('.')[1];
|
||||
if (extension === 'js') {
|
||||
return 'js-ext-file-icon ext-file-icon javascript-lang-file-icon';
|
||||
|
||||
@@ -3,17 +3,12 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { SpectronApplication, Quality } from '../../spectron/application';
|
||||
import { Application, Quality } from '../../application';
|
||||
|
||||
export function setup() {
|
||||
describe('Extensions', () => {
|
||||
before(function () {
|
||||
this.app.suiteName = 'Extensions';
|
||||
});
|
||||
|
||||
it(`install and activate vscode-smoketest-check extension`, async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
|
||||
if (app.quality === Quality.Dev) {
|
||||
this.skip();
|
||||
@@ -23,16 +18,12 @@ export function setup() {
|
||||
const extensionName = 'vscode-smoketest-check';
|
||||
await app.workbench.extensions.openExtensionsViewlet();
|
||||
|
||||
const installed = await app.workbench.extensions.installExtension(extensionName);
|
||||
assert.ok(installed);
|
||||
await app.workbench.extensions.installExtension(extensionName);
|
||||
|
||||
await app.reload();
|
||||
await app.workbench.extensions.waitForExtensionsViewlet();
|
||||
await app.workbench.quickopen.runCommand('Smoke Test Check');
|
||||
|
||||
const statusbarText = await app.workbench.statusbar.getStatusbarTextByTitle('smoke test');
|
||||
await app.screenCapturer.capture('Statusbar');
|
||||
assert.equal(statusbarText, 'VS Code Smoke Test Check');
|
||||
await app.workbench.extensions.openExtensionsViewlet();
|
||||
await app.workbench.runCommand('Smoke Test Check');
|
||||
await app.workbench.statusbar.waitForStatusbarText('smoke test', 'VS Code Smoke Test Check');
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -3,40 +3,32 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../../spectron/application';
|
||||
import { Viewlet } from '../workbench/viewlet';
|
||||
import { Commands } from '../workbench/workbench';
|
||||
import { Code } from '../../vscode/code';
|
||||
|
||||
const SEARCH_BOX = 'div.extensions-viewlet[id="workbench.view.extensions"] input.search-box';
|
||||
|
||||
export class Extensions extends Viewlet {
|
||||
|
||||
constructor(spectron: SpectronApplication) {
|
||||
super(spectron);
|
||||
constructor(code: Code, private commands: Commands) {
|
||||
super(code);
|
||||
}
|
||||
|
||||
async openExtensionsViewlet(): Promise<any> {
|
||||
await this.spectron.runCommand('workbench.view.extensions');
|
||||
await this.waitForExtensionsViewlet();
|
||||
}
|
||||
|
||||
async waitForExtensionsViewlet(): Promise<any> {
|
||||
await this.spectron.client.waitForActiveElement(SEARCH_BOX);
|
||||
await this.commands.runCommand('workbench.view.extensions');
|
||||
await this.code.waitForActiveElement(SEARCH_BOX);
|
||||
}
|
||||
|
||||
async searchForExtension(name: string): Promise<any> {
|
||||
await this.spectron.client.click(SEARCH_BOX);
|
||||
await this.spectron.client.waitForActiveElement(SEARCH_BOX);
|
||||
await this.spectron.client.setValue(SEARCH_BOX, name);
|
||||
await this.code.waitAndClick(SEARCH_BOX);
|
||||
await this.code.waitForActiveElement(SEARCH_BOX);
|
||||
await this.code.waitForSetValue(SEARCH_BOX, `name:"${name}"`);
|
||||
}
|
||||
|
||||
async installExtension(name: string): Promise<boolean> {
|
||||
async installExtension(name: string): Promise<void> {
|
||||
await this.searchForExtension(name);
|
||||
|
||||
// we might want to wait for a while longer since the Marketplace can be slow
|
||||
// a minute should do
|
||||
await this.spectron.client.waitFor(() => this.spectron.client.click(`div.extensions-viewlet[id="workbench.view.extensions"] .monaco-list-row[aria-label="${name}"] .extension li[class='action-item'] .extension-action.install`), void 0, 'waiting for install button', 600);
|
||||
|
||||
await this.spectron.client.waitForElement(`div.extensions-viewlet[id="workbench.view.extensions"] .monaco-list-row[aria-label="${name}"] .extension li[class='action-item'] .extension-action.reload`);
|
||||
return true;
|
||||
await this.code.waitAndClick(`div.extensions-viewlet[id="workbench.view.extensions"] .monaco-list-row[aria-label="${name}"] .extension li[class='action-item'] .extension-action.install`);
|
||||
await this.code.waitForElement(`div.extensions-viewlet[id="workbench.view.extensions"] .monaco-list-row[aria-label="${name}"] .extension li[class='action-item'] .extension-action.reload`);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as cp from 'child_process';
|
||||
import { SpectronApplication } from '../../spectron/application';
|
||||
import { Application } from '../../application';
|
||||
|
||||
const DIFF_EDITOR_LINE_INSERT = '.monaco-diff-editor .editor.modified .line-insert';
|
||||
const SYNC_STATUSBAR = 'div[id="workbench.parts.statusbar"] .statusbar-entry a[title$="Synchronize Changes"]';
|
||||
@@ -12,39 +12,40 @@ const SYNC_STATUSBAR = 'div[id="workbench.parts.statusbar"] .statusbar-entry a[t
|
||||
export function setup() {
|
||||
describe('Git', () => {
|
||||
before(async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
app.suiteName = 'Git';
|
||||
const app = this.app as Application;
|
||||
|
||||
cp.execSync('git config user.name testuser', { cwd: app.workspacePath });
|
||||
cp.execSync('git config user.email monacotools@microsoft.com', { cwd: app.workspacePath });
|
||||
});
|
||||
|
||||
it('reflects working tree changes', async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
|
||||
await app.workbench.scm.openSCMViewlet();
|
||||
|
||||
await app.workbench.quickopen.openFile('app.js');
|
||||
await app.workbench.editor.waitForTypeInEditor('app.js', '.foo{}');
|
||||
await app.workbench.saveOpenedFile();
|
||||
await app.workbench.editors.saveOpenedFile();
|
||||
|
||||
await app.workbench.quickopen.openFile('index.jade');
|
||||
await app.workbench.editor.waitForTypeInEditor('index.jade', 'hello world');
|
||||
await app.workbench.saveOpenedFile();
|
||||
await app.workbench.editors.saveOpenedFile();
|
||||
|
||||
await app.workbench.scm.refreshSCMViewlet();
|
||||
await app.workbench.scm.waitForChange('app.js', 'Modified');
|
||||
await app.workbench.scm.waitForChange('index.jade', 'Modified');
|
||||
await app.screenCapturer.capture('changes');
|
||||
});
|
||||
|
||||
it('opens diff editor', async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
|
||||
await app.workbench.scm.openSCMViewlet();
|
||||
await app.workbench.scm.openChange('app.js');
|
||||
await app.client.waitForElement(DIFF_EDITOR_LINE_INSERT);
|
||||
await app.code.waitForElement(DIFF_EDITOR_LINE_INSERT);
|
||||
});
|
||||
|
||||
it('stages correctly', async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
|
||||
await app.workbench.scm.openSCMViewlet();
|
||||
|
||||
@@ -58,7 +59,7 @@ export function setup() {
|
||||
});
|
||||
|
||||
it(`stages, commits changes and verifies outgoing change`, async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
|
||||
await app.workbench.scm.openSCMViewlet();
|
||||
|
||||
@@ -67,13 +68,13 @@ export function setup() {
|
||||
await app.workbench.scm.waitForChange('app.js', 'Index Modified');
|
||||
|
||||
await app.workbench.scm.commit('first commit');
|
||||
await app.client.waitForText(SYNC_STATUSBAR, ' 0↓ 1↑');
|
||||
await app.code.waitForTextContent(SYNC_STATUSBAR, ' 0↓ 1↑');
|
||||
|
||||
await app.workbench.quickopen.runCommand('Git: Stage All Changes');
|
||||
await app.workbench.runCommand('Git: Stage All Changes');
|
||||
await app.workbench.scm.waitForChange('index.jade', 'Index Modified');
|
||||
|
||||
await app.workbench.scm.commit('second commit');
|
||||
await app.client.waitForText(SYNC_STATUSBAR, ' 0↓ 2↑');
|
||||
await app.code.waitForTextContent(SYNC_STATUSBAR, ' 0↓ 2↑');
|
||||
|
||||
cp.execSync('git reset --hard origin/master', { cwd: app.workspacePath });
|
||||
});
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../../spectron/application';
|
||||
import { Viewlet } from '../workbench/viewlet';
|
||||
import { Commands } from '../workbench/workbench';
|
||||
import { IElement } from '../../vscode/driver';
|
||||
import { findElement, findElements, Code } from '../../vscode/code';
|
||||
|
||||
const VIEWLET = 'div[id="workbench.view.scm"]';
|
||||
const SCM_INPUT = `${VIEWLET} .scm-editor textarea`;
|
||||
@@ -22,84 +24,61 @@ interface Change {
|
||||
actions: string[];
|
||||
}
|
||||
|
||||
function toChange(element: IElement): Change {
|
||||
const name = findElement(element, e => /\blabel-name\b/.test(e.className))!;
|
||||
const type = element.attributes['data-tooltip'] || '';
|
||||
|
||||
const actionElementList = findElements(element, e => /\baction-label\b/.test(e.className));
|
||||
const actions = actionElementList.map(e => e.attributes['title']);
|
||||
|
||||
return {
|
||||
name: name.textContent || '',
|
||||
type,
|
||||
actions
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export class SCM extends Viewlet {
|
||||
|
||||
constructor(spectron: SpectronApplication) {
|
||||
super(spectron);
|
||||
constructor(code: Code, private commands: Commands) {
|
||||
super(code);
|
||||
}
|
||||
|
||||
async openSCMViewlet(): Promise<any> {
|
||||
await this.spectron.runCommand('workbench.view.scm');
|
||||
await this.spectron.client.waitForElement(SCM_INPUT);
|
||||
await this.commands.runCommand('workbench.view.scm');
|
||||
await this.code.waitForElement(SCM_INPUT);
|
||||
}
|
||||
|
||||
waitForChange(name: string, type?: string): Promise<void> {
|
||||
return this.spectron.client.waitFor(async () => {
|
||||
const changes = await this.queryChanges(name, type);
|
||||
return changes.length;
|
||||
}, l => l > 0, 'Getting SCM changes') as Promise<any> as Promise<void>;
|
||||
async waitForChange(name: string, type?: string): Promise<void> {
|
||||
const func = (change: Change) => change.name === name && (!type || change.type === type);
|
||||
await this.code.waitForElements(SCM_RESOURCE, true, elements => elements.some(e => func(toChange(e))));
|
||||
}
|
||||
|
||||
async refreshSCMViewlet(): Promise<any> {
|
||||
await this.spectron.client.click(REFRESH_COMMAND);
|
||||
}
|
||||
|
||||
private async queryChanges(name: string, type?: string): Promise<Change[]> {
|
||||
const result = await this.spectron.webclient.selectorExecute(SCM_RESOURCE, (div, name, type) => {
|
||||
return (Array.isArray(div) ? div : [div])
|
||||
.map(element => {
|
||||
const name = element.querySelector('.label-name') as HTMLElement;
|
||||
const type = element.getAttribute('data-tooltip') || '';
|
||||
const actionElementList = element.querySelectorAll('.actions .action-label');
|
||||
const actions: string[] = [];
|
||||
|
||||
for (let i = 0; i < actionElementList.length; i++) {
|
||||
const element = actionElementList.item(i) as HTMLElement;
|
||||
actions.push(element.title);
|
||||
}
|
||||
|
||||
return {
|
||||
name: name.textContent,
|
||||
type,
|
||||
actions
|
||||
};
|
||||
})
|
||||
.filter(change => {
|
||||
if (change.name !== name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type && (change.type !== type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}, name, type);
|
||||
|
||||
return result;
|
||||
await this.code.waitAndClick(REFRESH_COMMAND);
|
||||
}
|
||||
|
||||
async openChange(name: string): Promise<void> {
|
||||
await this.spectron.client.waitAndClick(SCM_RESOURCE_CLICK(name));
|
||||
await this.code.waitAndClick(SCM_RESOURCE_CLICK(name));
|
||||
}
|
||||
|
||||
async stage(name: string): Promise<void> {
|
||||
await this.spectron.client.waitAndClick(SCM_RESOURCE_ACTION_CLICK(name, 'Stage Changes'));
|
||||
await this.code.waitAndClick(SCM_RESOURCE_ACTION_CLICK(name, 'Stage Changes'));
|
||||
}
|
||||
|
||||
async stageAll(): Promise<void> {
|
||||
await this.spectron.client.waitAndClick(SCM_RESOURCE_GROUP_COMMAND_CLICK('Stage All Changes'));
|
||||
await this.code.waitAndClick(SCM_RESOURCE_GROUP_COMMAND_CLICK('Stage All Changes'));
|
||||
}
|
||||
|
||||
async unstage(name: string): Promise<void> {
|
||||
await this.spectron.client.waitAndClick(SCM_RESOURCE_ACTION_CLICK(name, 'Unstage Changes'));
|
||||
await this.code.waitAndClick(SCM_RESOURCE_ACTION_CLICK(name, 'Unstage Changes'));
|
||||
}
|
||||
|
||||
async commit(message: string): Promise<void> {
|
||||
await this.spectron.client.waitAndClick(SCM_INPUT);
|
||||
await this.spectron.client.waitForActiveElement(SCM_INPUT);
|
||||
await this.spectron.client.setValue(SCM_INPUT, message);
|
||||
await this.spectron.client.waitAndClick(COMMIT_COMMAND);
|
||||
await this.code.waitAndClick(SCM_INPUT);
|
||||
await this.code.waitForActiveElement(SCM_INPUT);
|
||||
await this.code.waitForSetValue(SCM_INPUT, message);
|
||||
await this.code.waitAndClick(COMMIT_COMMAND);
|
||||
}
|
||||
}
|
||||
@@ -3,16 +3,13 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { SpectronApplication } from '../../spectron/application';
|
||||
import { Application } from '../../application';
|
||||
|
||||
export function setup() {
|
||||
describe('Multiroot', () => {
|
||||
|
||||
before(async function () {
|
||||
this.app.suiteName = 'Multiroot';
|
||||
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
|
||||
// restart with preventing additional windows from restoring
|
||||
// to ensure the window after restart is the multi-root workspace
|
||||
@@ -20,7 +17,7 @@ export function setup() {
|
||||
});
|
||||
|
||||
it('shows results from all folders', async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
await app.workbench.quickopen.openQuickOpen('*.*');
|
||||
|
||||
await app.workbench.quickopen.waitForQuickOpenElements(names => names.length === 6);
|
||||
@@ -28,10 +25,8 @@ export function setup() {
|
||||
});
|
||||
|
||||
it('shows workspace name in title', async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const title = await app.client.getTitle();
|
||||
await app.screenCapturer.capture('window title');
|
||||
assert.ok(title.indexOf('smoketest (Workspace)') >= 0);
|
||||
const app = this.app as Application;
|
||||
await app.code.waitForTitle(title => /smoketest \(Workspace\)/i.test(title));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -3,26 +3,28 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../../spectron/application';
|
||||
import { Commands } from '../workbench/workbench';
|
||||
import { Code } from '../../vscode/code';
|
||||
|
||||
const SEARCH_INPUT = '.settings-search-input input';
|
||||
|
||||
export class KeybindingsEditor {
|
||||
|
||||
constructor(private spectron: SpectronApplication) { }
|
||||
constructor(private code: Code, private commands: Commands) { }
|
||||
|
||||
async updateKeybinding(command: string, keys: string[], ariaLabel: string): Promise<any> {
|
||||
await this.spectron.runCommand('workbench.action.openGlobalKeybindings');
|
||||
await this.spectron.client.waitForActiveElement(SEARCH_INPUT);
|
||||
await this.spectron.client.setValue(SEARCH_INPUT, command);
|
||||
async updateKeybinding(command: string, keybinding: string, ariaLabel: string): Promise<any> {
|
||||
await this.commands.runCommand('workbench.action.openGlobalKeybindings');
|
||||
await this.code.waitForActiveElement(SEARCH_INPUT);
|
||||
await this.code.waitForSetValue(SEARCH_INPUT, command);
|
||||
|
||||
await this.spectron.client.waitAndClick('div[aria-label="Keybindings"] .monaco-list-row.keybinding-item');
|
||||
await this.spectron.client.waitForElement('div[aria-label="Keybindings"] .monaco-list-row.keybinding-item.focused.selected');
|
||||
await this.code.waitAndClick('div[aria-label="Keybindings"] .monaco-list-row.keybinding-item');
|
||||
await this.code.waitForElement('div[aria-label="Keybindings"] .monaco-list-row.keybinding-item.focused.selected');
|
||||
|
||||
await this.spectron.client.waitAndClick('div[aria-label="Keybindings"] .monaco-list-row.keybinding-item .action-item .icon.add');
|
||||
await this.spectron.client.waitForElement('.defineKeybindingWidget .monaco-inputbox.synthetic-focus');
|
||||
await this.code.waitAndClick('div[aria-label="Keybindings"] .monaco-list-row.keybinding-item .action-item .icon.add');
|
||||
await this.code.waitForElement('.defineKeybindingWidget .monaco-inputbox.synthetic-focus');
|
||||
|
||||
await this.spectron.client.keys([...keys, 'NULL', 'Enter', 'NULL']);
|
||||
await this.spectron.client.waitForElement(`div[aria-label="Keybindings"] div[aria-label="Keybinding is ${ariaLabel}."]`);
|
||||
await this.code.dispatchKeybinding(keybinding);
|
||||
await this.code.dispatchKeybinding('enter');
|
||||
await this.code.waitForElement(`div[aria-label="Keybindings"] div[aria-label="Keybinding is ${ariaLabel}."]`);
|
||||
}
|
||||
}
|
||||
@@ -3,45 +3,34 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
|
||||
import { SpectronApplication } from '../../spectron/application';
|
||||
import { Application } from '../../application';
|
||||
import { ActivityBarPosition } from '../activitybar/activityBar';
|
||||
|
||||
export function setup() {
|
||||
describe('Preferences', () => {
|
||||
before(function () {
|
||||
this.app.suiteName = 'Preferences';
|
||||
});
|
||||
|
||||
it('turns off editor line numbers and verifies the live change', async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
|
||||
await app.workbench.explorer.openFile('app.js');
|
||||
let lineNumbers = await app.client.waitForElements('.line-numbers');
|
||||
await app.screenCapturer.capture('app.js has line numbers');
|
||||
assert.ok(!!lineNumbers.length, 'Line numbers are not present in the editor before disabling them.');
|
||||
await app.code.waitForElements('.line-numbers', false, elements => !!elements.length);
|
||||
|
||||
await app.workbench.settingsEditor.addUserSetting('editor.lineNumbers', '"off"');
|
||||
await app.workbench.selectTab('app.js');
|
||||
lineNumbers = await app.client.waitForElements('.line-numbers', result => !result || result.length === 0);
|
||||
|
||||
await app.screenCapturer.capture('line numbers hidden');
|
||||
assert.ok(!lineNumbers.length, 'Line numbers are still present in the editor after disabling them.');
|
||||
await app.workbench.editors.selectTab('app.js');
|
||||
await app.code.waitForElements('.line-numbers', false, result => !result || result.length === 0);
|
||||
});
|
||||
|
||||
it(`changes 'workbench.action.toggleSidebarPosition' command key binding and verifies it`, async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
assert.ok(await app.workbench.activitybar.getActivityBar(ActivityBarPosition.LEFT), 'Activity bar should be positioned on the left.');
|
||||
const app = this.app as Application;
|
||||
await app.workbench.activitybar.waitForActivityBar(ActivityBarPosition.LEFT);
|
||||
|
||||
await app.workbench.keybindingsEditor.updateKeybinding('workbench.action.toggleSidebarPosition', ['Control', 'u'], 'Control+U');
|
||||
await app.workbench.keybindingsEditor.updateKeybinding('workbench.action.toggleSidebarPosition', 'ctrl+u', 'Control+U');
|
||||
|
||||
await app.client.keys(['Control', 'u', 'NULL']);
|
||||
assert.ok(await app.workbench.activitybar.getActivityBar(ActivityBarPosition.RIGHT), 'Activity bar was not moved to right after toggling its position.');
|
||||
await app.code.dispatchKeybinding('ctrl+u');
|
||||
await app.workbench.activitybar.waitForActivityBar(ActivityBarPosition.RIGHT);
|
||||
});
|
||||
|
||||
after(async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
await app.workbench.settingsEditor.clearUserSettings();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { SpectronApplication } from '../../spectron/application';
|
||||
import { Commands } from '../workbench/workbench';
|
||||
import { Editor } from '../editor/editor';
|
||||
import { Editors } from '../editor/editors';
|
||||
import { Code } from '../../vscode/code';
|
||||
|
||||
export enum ActivityBarPosition {
|
||||
LEFT = 0,
|
||||
@@ -13,33 +16,28 @@ export enum ActivityBarPosition {
|
||||
}
|
||||
|
||||
const SEARCH_INPUT = '.settings-search-input input';
|
||||
const EDITOR = '.editable-preferences-editor-container .monaco-editor textarea';
|
||||
|
||||
export class SettingsEditor {
|
||||
|
||||
constructor(private spectron: SpectronApplication) { }
|
||||
constructor(private code: Code, private userDataPath: string, private commands: Commands, private editors: Editors, private editor: Editor) { }
|
||||
|
||||
async addUserSetting(setting: string, value: string): Promise<void> {
|
||||
await this.spectron.runCommand('workbench.action.openGlobalSettings');
|
||||
await this.spectron.client.waitAndClick(SEARCH_INPUT);
|
||||
await this.spectron.client.waitForActiveElement(SEARCH_INPUT);
|
||||
await this.commands.runCommand('workbench.action.openGlobalSettings');
|
||||
await this.code.waitAndClick(SEARCH_INPUT);
|
||||
await this.code.waitForActiveElement(SEARCH_INPUT);
|
||||
|
||||
await this.spectron.client.keys(['ArrowDown', 'NULL']);
|
||||
await this.spectron.client.waitForActiveElement(EDITOR);
|
||||
await this.editor.waitForEditorFocus('settings.json', 1, '.editable-preferences-editor-container');
|
||||
|
||||
await this.spectron.client.keys(['ArrowRight', 'NULL']);
|
||||
await this.spectron.screenCapturer.capture('user settings is open and focused');
|
||||
|
||||
await this.spectron.workbench.editor.waitForTypeInEditor('settings.json', `"${setting}": ${value}`, '.editable-preferences-editor-container');
|
||||
await this.spectron.workbench.saveOpenedFile();
|
||||
|
||||
await this.spectron.screenCapturer.capture('user settings has changed');
|
||||
await this.code.dispatchKeybinding('right');
|
||||
await this.editor.waitForTypeInEditor('settings.json', `"${setting}": ${value}`, '.editable-preferences-editor-container');
|
||||
await this.editors.saveOpenedFile();
|
||||
}
|
||||
|
||||
async clearUserSettings(): Promise<void> {
|
||||
const settingsPath = path.join(this.spectron.userDataPath, 'User', 'settings.json');
|
||||
const settingsPath = path.join(this.userDataPath, 'User', 'settings.json');
|
||||
await new Promise((c, e) => fs.writeFile(settingsPath, '{}', 'utf8', err => err ? e(err) : c()));
|
||||
|
||||
await this.spectron.workbench.editor.waitForEditorContents('settings.json', c => c.length === 0, '.editable-preferences-editor-container');
|
||||
await this.commands.runCommand('workbench.action.openGlobalSettings');
|
||||
await this.editor.waitForEditorContents('settings.json', c => c === '{}', '.editable-preferences-editor-container');
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,8 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../../spectron/application';
|
||||
import { Commands } from '../workbench/workbench';
|
||||
import { Code } from '../../vscode/code';
|
||||
|
||||
export enum ProblemSeverity {
|
||||
WARNING = 0,
|
||||
@@ -14,31 +15,20 @@ export class Problems {
|
||||
|
||||
static PROBLEMS_VIEW_SELECTOR = '.panel.markers-panel';
|
||||
|
||||
constructor(private spectron: SpectronApplication) {
|
||||
// noop
|
||||
}
|
||||
constructor(private code: Code, private commands: Commands) { }
|
||||
|
||||
public async showProblemsView(): Promise<any> {
|
||||
if (!await this.isVisible()) {
|
||||
await this.spectron.runCommand('workbench.actions.view.problems');
|
||||
await this.waitForProblemsView();
|
||||
}
|
||||
await this.commands.runCommand('workbench.actions.view.problems');
|
||||
await this.waitForProblemsView();
|
||||
}
|
||||
|
||||
public async hideProblemsView(): Promise<any> {
|
||||
if (await this.isVisible()) {
|
||||
await this.spectron.runCommand('workbench.actions.view.problems');
|
||||
await this.spectron.client.waitForElement(Problems.PROBLEMS_VIEW_SELECTOR, el => !el);
|
||||
}
|
||||
}
|
||||
|
||||
public async isVisible(): Promise<boolean> {
|
||||
const element = await this.spectron.client.element(Problems.PROBLEMS_VIEW_SELECTOR);
|
||||
return !!element;
|
||||
await this.commands.runCommand('workbench.actions.view.problems');
|
||||
await this.code.waitForElement(Problems.PROBLEMS_VIEW_SELECTOR, el => !el);
|
||||
}
|
||||
|
||||
public async waitForProblemsView(): Promise<void> {
|
||||
await this.spectron.client.waitForElement(Problems.PROBLEMS_VIEW_SELECTOR);
|
||||
await this.code.waitForElement(Problems.PROBLEMS_VIEW_SELECTOR);
|
||||
}
|
||||
|
||||
public static getSelectorInProblemsView(problemType: ProblemSeverity): string {
|
||||
@@ -47,7 +37,7 @@ export class Problems {
|
||||
}
|
||||
|
||||
public static getSelectorInEditor(problemType: ProblemSeverity): string {
|
||||
let selector = problemType === ProblemSeverity.WARNING ? 'squiggly-b-warning' : 'squiggly-c-error';
|
||||
let selector = problemType === ProblemSeverity.WARNING ? 'squiggly-warning' : 'squiggly-error';
|
||||
return `.view-overlays .cdr.${selector}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,86 +3,110 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../../spectron/application';
|
||||
import { Editors } from '../editor/editors';
|
||||
import { Commands } from '../workbench/workbench';
|
||||
import { Code } from '../../vscode/code';
|
||||
|
||||
export class QuickOpen {
|
||||
|
||||
static QUICK_OPEN_HIDDEN = 'div.quick-open-widget[aria-hidden="true"]';
|
||||
static QUICK_OPEN = 'div.quick-open-widget[aria-hidden="false"]';
|
||||
static QUICK_OPEN_HIDDEN = 'div.monaco-quick-open-widget[aria-hidden="true"]';
|
||||
static QUICK_OPEN = 'div.monaco-quick-open-widget[aria-hidden="false"]';
|
||||
static QUICK_OPEN_INPUT = `${QuickOpen.QUICK_OPEN} .quick-open-input input`;
|
||||
static QUICK_OPEN_FOCUSED_ELEMENT = `${QuickOpen.QUICK_OPEN} .quick-open-tree .monaco-tree-row.focused .monaco-highlighted-label`;
|
||||
static QUICK_OPEN_ENTRY_SELECTOR = 'div[aria-label="Quick Picker"] .monaco-tree-rows.show-twisties .monaco-tree-row .quick-open-entry';
|
||||
static QUICK_OPEN_ENTRY_LABEL_SELECTOR = 'div[aria-label="Quick Picker"] .monaco-tree-rows.show-twisties .monaco-tree-row .quick-open-entry .label-name';
|
||||
|
||||
constructor(readonly spectron: SpectronApplication) { }
|
||||
constructor(private code: Code, private commands: Commands, private editors: Editors) { }
|
||||
|
||||
async openQuickOpen(value: string): Promise<void> {
|
||||
await this.spectron.runCommand('workbench.action.quickOpen');
|
||||
await this.waitForQuickOpenOpened();
|
||||
let retries = 0;
|
||||
|
||||
// other parts of code might steal focus away from quickopen :(
|
||||
while (retries < 5) {
|
||||
await this.commands.runCommand('workbench.action.quickOpen');
|
||||
|
||||
try {
|
||||
await this.waitForQuickOpenOpened(10);
|
||||
break;
|
||||
} catch (err) {
|
||||
if (++retries > 5) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
await this.code.dispatchKeybinding('escape');
|
||||
}
|
||||
}
|
||||
|
||||
if (value) {
|
||||
await this.spectron.client.setValue(QuickOpen.QUICK_OPEN_INPUT, value);
|
||||
await this.code.waitForSetValue(QuickOpen.QUICK_OPEN_INPUT, value);
|
||||
}
|
||||
}
|
||||
|
||||
async closeQuickOpen(): Promise<void> {
|
||||
await this.spectron.runCommand('workbench.action.closeQuickOpen');
|
||||
await this.commands.runCommand('workbench.action.closeQuickOpen');
|
||||
await this.waitForQuickOpenClosed();
|
||||
}
|
||||
|
||||
async openFile(fileName: string): Promise<void> {
|
||||
await this.openQuickOpen(fileName);
|
||||
|
||||
await this.waitForQuickOpenElements(names => names.some(n => n === fileName));
|
||||
await this.spectron.client.keys(['Enter', 'NULL']);
|
||||
await this.spectron.workbench.waitForActiveTab(fileName);
|
||||
await this.spectron.workbench.waitForEditorFocus(fileName);
|
||||
await this.waitForQuickOpenElements(names => names[0] === fileName);
|
||||
await this.code.dispatchKeybinding('enter');
|
||||
await this.editors.waitForActiveTab(fileName);
|
||||
await this.editors.waitForEditorFocus(fileName);
|
||||
}
|
||||
|
||||
async runCommand(commandText: string): Promise<void> {
|
||||
await this.openQuickOpen(`> ${commandText}`);
|
||||
|
||||
// wait for best choice to be focused
|
||||
await this.spectron.client.waitForTextContent(QuickOpen.QUICK_OPEN_FOCUSED_ELEMENT, commandText);
|
||||
|
||||
// wait and click on best choice
|
||||
await this.spectron.client.waitAndClick(QuickOpen.QUICK_OPEN_FOCUSED_ELEMENT);
|
||||
}
|
||||
|
||||
async waitForQuickOpenOpened(): Promise<void> {
|
||||
await this.spectron.client.waitForActiveElement(QuickOpen.QUICK_OPEN_INPUT);
|
||||
async waitForQuickOpenOpened(retryCount?: number): Promise<void> {
|
||||
await this.code.waitForActiveElement(QuickOpen.QUICK_OPEN_INPUT, retryCount);
|
||||
}
|
||||
|
||||
private async waitForQuickOpenClosed(): Promise<void> {
|
||||
await this.spectron.client.waitForElement(QuickOpen.QUICK_OPEN_HIDDEN);
|
||||
await this.code.waitForElement(QuickOpen.QUICK_OPEN_HIDDEN);
|
||||
}
|
||||
|
||||
async submit(text: string): Promise<void> {
|
||||
await this.spectron.client.setValue(QuickOpen.QUICK_OPEN_INPUT, text);
|
||||
await this.spectron.client.keys(['Enter', 'NULL']);
|
||||
await this.code.waitForSetValue(QuickOpen.QUICK_OPEN_INPUT, text);
|
||||
await this.code.dispatchKeybinding('enter');
|
||||
await this.waitForQuickOpenClosed();
|
||||
}
|
||||
|
||||
async selectQuickOpenElement(index: number): Promise<void> {
|
||||
await this.waitForQuickOpenOpened();
|
||||
for (let from = 0; from < index; from++) {
|
||||
await this.spectron.client.keys(['ArrowDown', 'NULL']);
|
||||
await this.code.dispatchKeybinding('down');
|
||||
}
|
||||
await this.spectron.client.keys(['Enter', 'NULL']);
|
||||
await this.code.dispatchKeybinding('enter');
|
||||
await this.waitForQuickOpenClosed();
|
||||
}
|
||||
|
||||
async waitForQuickOpenElements(accept: (names: string[]) => boolean): Promise<void> {
|
||||
await this.spectron.client.waitFor(() => this.getQuickOpenElements(), accept);
|
||||
await this.code.waitForElements(QuickOpen.QUICK_OPEN_ENTRY_LABEL_SELECTOR, false, els => accept(els.map(e => e.textContent)));
|
||||
}
|
||||
|
||||
private async getQuickOpenElements(): Promise<string[]> {
|
||||
const result = await this.spectron.webclient.selectorExecute(QuickOpen.QUICK_OPEN_ENTRY_SELECTOR,
|
||||
div => (Array.isArray(div) ? div : [div]).map(element => {
|
||||
const name = element.querySelector('.label-name') as HTMLElement;
|
||||
return name.textContent;
|
||||
})
|
||||
);
|
||||
async runCommand(command: string): Promise<void> {
|
||||
await this.openQuickOpen(`> ${command}`);
|
||||
|
||||
return Array.isArray(result) ? result : [];
|
||||
// wait for best choice to be focused
|
||||
await this.code.waitForTextContent(QuickOpen.QUICK_OPEN_FOCUSED_ELEMENT, command);
|
||||
|
||||
// wait and click on best choice
|
||||
await this.code.waitAndClick(QuickOpen.QUICK_OPEN_FOCUSED_ELEMENT);
|
||||
}
|
||||
|
||||
async openQuickOutline(): Promise<void> {
|
||||
let retries = 0;
|
||||
|
||||
while (++retries < 10) {
|
||||
await this.commands.runCommand('workbench.action.gotoSymbol');
|
||||
|
||||
const text = await this.code.waitForTextContent('div[aria-label="Quick Picker"] .monaco-tree-rows.show-twisties div.monaco-tree-row .quick-open-entry .monaco-icon-label .label-name .monaco-highlighted-label span');
|
||||
|
||||
if (text !== 'No symbol information for the file') {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.closeQuickOpen();
|
||||
await new Promise(c => setTimeout(c, 250));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,16 +3,12 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../../spectron/application';
|
||||
import { Application } from '../../application';
|
||||
|
||||
export function setup() {
|
||||
describe('Search', () => {
|
||||
before(function () {
|
||||
this.app.suiteName = 'Search';
|
||||
});
|
||||
|
||||
it('searches for body & checks for correct result number', async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
await app.workbench.search.openSearchViewlet();
|
||||
await app.workbench.search.searchFor('body');
|
||||
|
||||
@@ -20,7 +16,7 @@ export function setup() {
|
||||
});
|
||||
|
||||
it('searches only for *.js files & checks for correct result number', async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
await app.workbench.search.searchFor('body');
|
||||
await app.workbench.search.showQueryDetails();
|
||||
await app.workbench.search.setFilesToIncludeText('*.js');
|
||||
@@ -32,27 +28,25 @@ export function setup() {
|
||||
});
|
||||
|
||||
it('dismisses result & checks for correct result number', async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
await app.workbench.search.searchFor('body');
|
||||
await app.workbench.search.removeFileMatch(1);
|
||||
await app.workbench.search.waitForResultText('10 results in 4 files');
|
||||
});
|
||||
|
||||
it('replaces first search result with a replace term', async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
|
||||
await app.workbench.search.searchFor('body');
|
||||
await app.workbench.search.expandReplace();
|
||||
await app.workbench.search.setReplaceText('ydob');
|
||||
await app.workbench.search.replaceFileMatch(1);
|
||||
await app.workbench.saveOpenedFile();
|
||||
|
||||
await app.workbench.search.waitForResultText('10 results in 4 files');
|
||||
|
||||
await app.workbench.search.searchFor('ydob');
|
||||
await app.workbench.search.setReplaceText('body');
|
||||
await app.workbench.search.replaceFileMatch(1);
|
||||
await app.workbench.saveOpenedFile();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -3,85 +3,77 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../../spectron/application';
|
||||
import { Viewlet } from '../workbench/viewlet';
|
||||
import { Commands } from '../workbench/workbench';
|
||||
import { Code } from '../../vscode/code';
|
||||
|
||||
const VIEWLET = 'div[id="workbench.view.search"] .search-view';
|
||||
const INPUT = `${VIEWLET} .search-widget .search-container .monaco-inputbox input`;
|
||||
const INCLUDE_INPUT = `${VIEWLET} .query-details .monaco-inputbox input[aria-label="Search Include Patterns"]`;
|
||||
const INCLUDE_INPUT = `${VIEWLET} .query-details .file-types.includes .monaco-inputbox input`;
|
||||
|
||||
export class Search extends Viewlet {
|
||||
|
||||
constructor(spectron: SpectronApplication) {
|
||||
super(spectron);
|
||||
constructor(code: Code, private commands: Commands) {
|
||||
super(code);
|
||||
}
|
||||
|
||||
async openSearchViewlet(): Promise<any> {
|
||||
await this.spectron.runCommand('workbench.view.search');
|
||||
await this.spectron.client.waitForActiveElement(INPUT);
|
||||
await this.commands.runCommand('workbench.view.search');
|
||||
await this.code.waitForActiveElement(INPUT);
|
||||
}
|
||||
|
||||
async searchFor(text: string): Promise<void> {
|
||||
await this.spectron.client.click(INPUT);
|
||||
await this.spectron.client.waitForActiveElement(INPUT);
|
||||
await this.spectron.client.setValue(INPUT, text);
|
||||
await this.code.waitAndClick(INPUT);
|
||||
await this.code.waitForActiveElement(INPUT);
|
||||
await this.code.waitForSetValue(INPUT, text);
|
||||
await this.submitSearch();
|
||||
}
|
||||
|
||||
async submitSearch(): Promise<void> {
|
||||
await this.spectron.client.click(INPUT);
|
||||
await this.spectron.client.waitForActiveElement(INPUT);
|
||||
await this.code.waitAndClick(INPUT);
|
||||
await this.code.waitForActiveElement(INPUT);
|
||||
|
||||
await this.spectron.client.keys(['Enter', 'NULL']);
|
||||
await this.spectron.client.element(`${VIEWLET} .messages[aria-hidden="false"]`);
|
||||
await this.code.dispatchKeybinding('enter');
|
||||
await this.code.waitForElement(`${VIEWLET} .messages[aria-hidden="false"]`);
|
||||
}
|
||||
|
||||
async setFilesToIncludeText(text: string): Promise<void> {
|
||||
await this.spectron.client.click(INCLUDE_INPUT);
|
||||
await this.spectron.client.waitForActiveElement(INCLUDE_INPUT);
|
||||
await this.spectron.client.setValue(INCLUDE_INPUT, text || '');
|
||||
await this.code.waitAndClick(INCLUDE_INPUT);
|
||||
await this.code.waitForActiveElement(INCLUDE_INPUT);
|
||||
await this.code.waitForSetValue(INCLUDE_INPUT, text || '');
|
||||
}
|
||||
|
||||
async showQueryDetails(): Promise<void> {
|
||||
if (!await this.areDetailsVisible()) {
|
||||
await this.spectron.client.waitAndClick(`${VIEWLET} .query-details .more`);
|
||||
}
|
||||
await this.code.waitAndClick(`${VIEWLET} .query-details .more`);
|
||||
}
|
||||
|
||||
async hideQueryDetails(): Promise<void> {
|
||||
if (await this.areDetailsVisible()) {
|
||||
await this.spectron.client.waitAndClick(`${VIEWLET} .query-details.more .more`);
|
||||
}
|
||||
}
|
||||
|
||||
async areDetailsVisible(): Promise<boolean> {
|
||||
const element = await this.spectron.client.element(`${VIEWLET} .query-details.more`);
|
||||
return !!element;
|
||||
await this.code.waitAndClick(`${VIEWLET} .query-details.more .more`);
|
||||
}
|
||||
|
||||
async removeFileMatch(index: number): Promise<void> {
|
||||
await this.spectron.client.waitAndMoveToObject(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch`);
|
||||
const file = await this.spectron.client.waitForText(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch a.label-name`);
|
||||
await this.spectron.client.waitAndClick(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch .action-label.icon.action-remove`);
|
||||
await this.spectron.client.waitForText(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch a.label-name`, void 0, result => result !== file);
|
||||
await this.code.waitAndMove(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch`);
|
||||
const file = await this.code.waitForTextContent(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch a.label-name`);
|
||||
await this.code.waitAndClick(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch .action-label.icon.action-remove`);
|
||||
await this.code.waitForTextContent(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch a.label-name`, void 0, result => result !== file);
|
||||
}
|
||||
|
||||
async expandReplace(): Promise<void> {
|
||||
await this.spectron.client.waitAndClick(`${VIEWLET} .search-widget .monaco-button.toggle-replace-button.collapse`);
|
||||
await this.code.waitAndClick(`${VIEWLET} .search-widget .monaco-button.toggle-replace-button.collapse`);
|
||||
}
|
||||
|
||||
async setReplaceText(text: string): Promise<void> {
|
||||
await this.spectron.client.waitAndClick(`${VIEWLET} .search-widget .replace-container .monaco-inputbox input[title="Replace"]`);
|
||||
await this.spectron.client.element(`${VIEWLET} .search-widget .replace-container .monaco-inputbox.synthetic-focus input[title="Replace"]`);
|
||||
await this.spectron.client.setValue(`${VIEWLET} .search-widget .replace-container .monaco-inputbox.synthetic-focus input[title="Replace"]`, text);
|
||||
await this.code.waitAndClick(`${VIEWLET} .search-widget .replace-container .monaco-inputbox input[title="Replace"]`);
|
||||
await this.code.waitForElement(`${VIEWLET} .search-widget .replace-container .monaco-inputbox.synthetic-focus input[title="Replace"]`);
|
||||
await this.code.waitForSetValue(`${VIEWLET} .search-widget .replace-container .monaco-inputbox.synthetic-focus input[title="Replace"]`, text);
|
||||
}
|
||||
|
||||
async replaceFileMatch(index: number): Promise<void> {
|
||||
await this.spectron.client.waitAndMoveToObject(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch`);
|
||||
await this.spectron.client.click(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch .action-label.icon.action-replace-all`);
|
||||
await this.code.waitAndMove(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch`);
|
||||
await this.code.waitAndClick(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch .action-label.icon.action-replace-all`);
|
||||
}
|
||||
|
||||
async waitForResultText(text: string): Promise<void> {
|
||||
await this.spectron.client.waitForText(`${VIEWLET} .messages[aria-hidden="false"] .message>p`, text);
|
||||
await this.code.waitForTextContent(`${VIEWLET} .messages[aria-hidden="false"] .message>p`, text);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,19 +3,13 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
|
||||
import { SpectronApplication, Quality } from '../../spectron/application';
|
||||
import { Application, Quality } from '../../application';
|
||||
import { StatusBarElement } from './statusbar';
|
||||
|
||||
export function setup() {
|
||||
describe('Statusbar', () => {
|
||||
before(function () {
|
||||
this.app.suiteName = 'Statusbar';
|
||||
});
|
||||
|
||||
it('verifies presence of all default status bar elements', async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
|
||||
await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.BRANCH_STATUS);
|
||||
if (app.quality !== Quality.Dev) {
|
||||
@@ -33,7 +27,7 @@ export function setup() {
|
||||
});
|
||||
|
||||
it(`verifies that 'quick open' opens when clicking on status bar elements`, async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
|
||||
await app.workbench.statusbar.clickOn(StatusBarElement.BRANCH_STATUS);
|
||||
await app.workbench.quickopen.waitForQuickOpenOpened();
|
||||
@@ -55,25 +49,25 @@ export function setup() {
|
||||
});
|
||||
|
||||
it(`verifies that 'Problems View' appears when clicking on 'Problems' status element`, async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
|
||||
await app.workbench.statusbar.clickOn(StatusBarElement.PROBLEMS_STATUS);
|
||||
await app.workbench.problems.waitForProblemsView();
|
||||
});
|
||||
|
||||
it(`verifies that 'Tweet us feedback' pop-up appears when clicking on 'Feedback' icon`, async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
|
||||
if (app.quality === Quality.Dev) {
|
||||
return this.skip();
|
||||
}
|
||||
|
||||
await app.workbench.statusbar.clickOn(StatusBarElement.FEEDBACK_ICON);
|
||||
assert.ok(!!await app.client.waitForElement('.feedback-form'));
|
||||
await app.code.waitForElement('.feedback-form');
|
||||
});
|
||||
|
||||
it(`checks if 'Go to Line' works if called from the status bar`, async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
|
||||
await app.workbench.quickopen.openFile('app.js');
|
||||
await app.workbench.statusbar.clickOn(StatusBarElement.SELECTION_STATUS);
|
||||
@@ -81,11 +75,11 @@ export function setup() {
|
||||
await app.workbench.quickopen.waitForQuickOpenOpened();
|
||||
|
||||
await app.workbench.quickopen.submit(':15');
|
||||
await app.workbench.editor.waitForHighlightingLine(15);
|
||||
await app.workbench.editor.waitForHighlightingLine('app.js', 15);
|
||||
});
|
||||
|
||||
it(`verifies if changing EOL is reflected in the status bar`, async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
|
||||
await app.workbench.quickopen.openFile('app.js');
|
||||
await app.workbench.statusbar.clickOn(StatusBarElement.EOL_STATUS);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../../spectron/application';
|
||||
import { Code } from '../../vscode/code';
|
||||
|
||||
export enum StatusBarElement {
|
||||
BRANCH_STATUS = 0,
|
||||
@@ -23,23 +23,22 @@ export class StatusBar {
|
||||
private readonly leftSelector = '.statusbar-item.left';
|
||||
private readonly rightSelector = '.statusbar-item.right';
|
||||
|
||||
constructor(private spectron: SpectronApplication) {
|
||||
constructor(private code: Code) { }
|
||||
|
||||
async waitForStatusbarElement(element: StatusBarElement): Promise<void> {
|
||||
await this.code.waitForElement(this.getSelector(element));
|
||||
}
|
||||
|
||||
public async waitForStatusbarElement(element: StatusBarElement): Promise<void> {
|
||||
await this.spectron.client.waitForElement(this.getSelector(element));
|
||||
async clickOn(element: StatusBarElement): Promise<void> {
|
||||
await this.code.waitAndClick(this.getSelector(element));
|
||||
}
|
||||
|
||||
public async clickOn(element: StatusBarElement): Promise<void> {
|
||||
await this.spectron.client.waitAndClick(this.getSelector(element));
|
||||
async waitForEOL(eol: string): Promise<string> {
|
||||
return this.code.waitForTextContent(this.getSelector(StatusBarElement.EOL_STATUS), eol);
|
||||
}
|
||||
|
||||
public async waitForEOL(eol: string): Promise<string> {
|
||||
return this.spectron.client.waitForText(this.getSelector(StatusBarElement.EOL_STATUS), eol);
|
||||
}
|
||||
|
||||
public async getStatusbarTextByTitle(title: string): Promise<string> {
|
||||
return await this.spectron.client.waitForText(`${this.mainSelector} span[title="smoke test"]`);
|
||||
async waitForStatusbarText(title: string, text: string): Promise<void> {
|
||||
await this.code.waitForTextContent(`${this.mainSelector} span[title="${title}"]`, text);
|
||||
}
|
||||
|
||||
private getSelector(element: StatusBarElement): string {
|
||||
@@ -61,7 +60,7 @@ export class StatusBar {
|
||||
case StatusBarElement.LANGUAGE_STATUS:
|
||||
return `${this.mainSelector} ${this.rightSelector} .editor-status-mode`;
|
||||
case StatusBarElement.FEEDBACK_ICON:
|
||||
return `${this.mainSelector} ${this.rightSelector} .dropdown.send-feedback`;
|
||||
return `${this.mainSelector} ${this.rightSelector} .monaco-dropdown.send-feedback`;
|
||||
default:
|
||||
throw new Error(element);
|
||||
}
|
||||
|
||||
@@ -3,27 +3,24 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// import { SpectronApplication } from '../../spectron/application';
|
||||
// import { Application } from '../../application';
|
||||
|
||||
describe('Terminal', () => {
|
||||
// let app: SpectronApplication;
|
||||
// before(() => { app = new SpectronApplication(); return app.start('Terminal'); });
|
||||
// after(() => app.stop());
|
||||
export function setup() {
|
||||
describe('Terminal', () => {
|
||||
// it(`opens terminal, runs 'echo' and verifies the output`, async function () {
|
||||
// const app = this.app as Application;
|
||||
|
||||
// it(`opens terminal, runs 'echo' and verifies the output`, async function () {
|
||||
// const expected = new Date().getTime().toString();
|
||||
// await app.workbench.terminal.showTerminal();
|
||||
|
||||
// await app.workbench.terminal.runCommand(`echo ${expected}`);
|
||||
|
||||
// await app.workbench.terminal.waitForTerminalText(terminalText => {
|
||||
// // Last line will not contain the output
|
||||
// for (let index = terminalText.length - 2; index >= 0; index--) {
|
||||
// if (!!terminalText[index] && terminalText[index].trim() === expected) {
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// });
|
||||
// });
|
||||
});
|
||||
// const expected = new Date().getTime().toString();
|
||||
// await app.workbench.terminal.showTerminal();
|
||||
// await app.workbench.terminal.runCommand(`echo ${expected}`);
|
||||
// await app.workbench.terminal.waitForTerminalText(terminalText => {
|
||||
// for (let index = terminalText.length - 2; index >= 0; index--) {
|
||||
// if (!!terminalText[index] && terminalText[index].trim() === expected) {
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// });
|
||||
// });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,59 +3,29 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../../spectron/application';
|
||||
import { Code } from '../../vscode/code';
|
||||
import { Commands } from '../workbench/workbench';
|
||||
|
||||
const PANEL_SELECTOR = 'div[id="workbench.panel.terminal"]';
|
||||
const XTERM_SELECTOR = `${PANEL_SELECTOR} .terminal-wrapper`;
|
||||
const XTERM_TEXTAREA = `${XTERM_SELECTOR} textarea.xterm-helper-textarea`;
|
||||
|
||||
export class Terminal {
|
||||
|
||||
constructor(private spectron: SpectronApplication) { }
|
||||
constructor(private code: Code, private commands: Commands) { }
|
||||
|
||||
async showTerminal(): Promise<void> {
|
||||
if (!await this.isVisible()) {
|
||||
await this.spectron.workbench.quickopen.runCommand('View: Toggle Integrated Terminal');
|
||||
await this.spectron.client.waitForElement(XTERM_SELECTOR);
|
||||
await this.waitForTerminalText(text => text.length > 0, 'Waiting for Terminal to be ready');
|
||||
}
|
||||
}
|
||||
|
||||
async isVisible(): Promise<boolean> {
|
||||
const element = await this.spectron.client.element(PANEL_SELECTOR);
|
||||
return !!element;
|
||||
await this.commands.runCommand('workbench.action.terminal.toggleTerminal');
|
||||
await this.code.waitForActiveElement(XTERM_TEXTAREA);
|
||||
await this.code.waitForTerminalBuffer(XTERM_SELECTOR, lines => lines.some(line => line.length > 0));
|
||||
}
|
||||
|
||||
async runCommand(commandText: string): Promise<void> {
|
||||
// TODO@Tyriar fix this. we should not use type but setValue
|
||||
// await this.spectron.client.type(commandText);
|
||||
await this.spectron.client.keys(['Enter', 'NULL']);
|
||||
await this.code.waitForPaste(XTERM_TEXTAREA, commandText);
|
||||
await this.code.dispatchKeybinding('enter');
|
||||
}
|
||||
|
||||
async waitForTerminalText(fn: (text: string[]) => boolean, timeOutDescription: string = 'Getting Terminal Text'): Promise<string[]> {
|
||||
return this.spectron.client.waitFor(async () => {
|
||||
const terminalText = await this.getTerminalText();
|
||||
if (fn(terminalText)) {
|
||||
return terminalText;
|
||||
}
|
||||
return undefined;
|
||||
}, void 0, timeOutDescription);
|
||||
}
|
||||
|
||||
getCurrentLineNumber(): Promise<number> {
|
||||
return this.getTerminalText().then(text => text.length);
|
||||
}
|
||||
|
||||
private async getTerminalText(): Promise<string[]> {
|
||||
return await this.spectron.webclient.selectorExecute(XTERM_SELECTOR,
|
||||
div => {
|
||||
const xterm = (<any>(Array.isArray(div) ? div[0] : div)).xterm;
|
||||
const buffer = xterm.buffer;
|
||||
const lines: string[] = [];
|
||||
for (let i = 0; i < buffer.lines.length; i++) {
|
||||
lines.push(buffer.translateBufferLineToString(i, true));
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
);
|
||||
async waitForTerminalText(accept: (buffer: string[]) => boolean): Promise<void> {
|
||||
await this.code.waitForTerminalBuffer(XTERM_SELECTOR, accept);
|
||||
}
|
||||
}
|
||||
@@ -3,39 +3,30 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../../spectron/application';
|
||||
import { Application } from '../../application';
|
||||
|
||||
export function setup() {
|
||||
describe('Dataloss', () => {
|
||||
before(function () {
|
||||
this.app.suiteName = 'Dataloss';
|
||||
});
|
||||
|
||||
it(`verifies that 'hot exit' works for dirty files`, async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
await app.workbench.newUntitledFile();
|
||||
const app = this.app as Application;
|
||||
await app.workbench.editors.newUntitledFile();
|
||||
|
||||
const untitled = 'Untitled-1';
|
||||
const textToTypeInUntitled = 'Hello, Unitled Code';
|
||||
await app.workbench.editor.waitForTypeInEditor(untitled, textToTypeInUntitled);
|
||||
await app.screenCapturer.capture('Untitled file before reload');
|
||||
|
||||
const readmeMd = 'readme.md';
|
||||
const textToType = 'Hello, Code';
|
||||
await app.workbench.explorer.openFile(readmeMd);
|
||||
await app.workbench.editor.waitForTypeInEditor(readmeMd, textToType);
|
||||
await app.screenCapturer.capture(`${readmeMd} before reload`);
|
||||
|
||||
await app.reload();
|
||||
await app.screenCapturer.capture('After reload');
|
||||
|
||||
await app.workbench.waitForActiveTab(readmeMd, true);
|
||||
await app.screenCapturer.capture(`${readmeMd} after reload`);
|
||||
await app.workbench.editors.waitForActiveTab(readmeMd, true);
|
||||
await app.workbench.editor.waitForEditorContents(readmeMd, c => c.indexOf(textToType) > -1);
|
||||
|
||||
await app.workbench.waitForTab(untitled, true);
|
||||
await app.workbench.selectTab(untitled, true);
|
||||
await app.screenCapturer.capture('Untitled file after reload');
|
||||
await app.workbench.editors.waitForTab(untitled, true);
|
||||
await app.workbench.editors.selectTab(untitled, true);
|
||||
await app.workbench.editor.waitForEditorContents(untitled, c => c.indexOf(textToTypeInUntitled) > -1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,130 +3,109 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
|
||||
import { SpectronApplication, Quality } from '../../spectron/application';
|
||||
import { Application, Quality } from '../../application';
|
||||
import * as rimraf from 'rimraf';
|
||||
|
||||
export interface ICreateAppFn {
|
||||
(quality: Quality): SpectronApplication | null;
|
||||
(quality: Quality): Application;
|
||||
}
|
||||
|
||||
export function setup(userDataDir: string, createApp: ICreateAppFn) {
|
||||
|
||||
describe('Data Migration', () => {
|
||||
|
||||
afterEach(async function () {
|
||||
await new Promise((c, e) => rimraf(userDataDir, { maxBusyTries: 10 }, err => err ? e(err) : c()));
|
||||
});
|
||||
|
||||
it('checks if the Untitled file is restored migrating from stable to latest', async function () {
|
||||
const stableApp = createApp(Quality.Stable);
|
||||
// it('checks if the Untitled file is restored migrating from stable to latest', async function () {
|
||||
// const stableApp = createApp(Quality.Stable);
|
||||
|
||||
if (!stableApp) {
|
||||
this.skip();
|
||||
return;
|
||||
}
|
||||
// if (!stableApp) {
|
||||
// this.skip();
|
||||
// return;
|
||||
// }
|
||||
|
||||
await stableApp.start();
|
||||
stableApp.suiteName = 'Data Migration';
|
||||
// await stableApp.start();
|
||||
|
||||
const textToType = 'Very dirty file';
|
||||
// const textToType = 'Very dirty file';
|
||||
|
||||
await stableApp.workbench.newUntitledFile();
|
||||
await stableApp.workbench.editor.waitForTypeInEditor('Untitled-1', textToType);
|
||||
// await stableApp.workbench.editors.newUntitledFile();
|
||||
// await stableApp.workbench.editor.waitForTypeInEditor('Untitled-1', textToType);
|
||||
|
||||
await stableApp.stop();
|
||||
await new Promise(c => setTimeout(c, 500)); // wait until all resources are released (e.g. locked local storage)
|
||||
// await stableApp.stop();
|
||||
// await new Promise(c => setTimeout(c, 500)); // wait until all resources are released (e.g. locked local storage)
|
||||
|
||||
// Checking latest version for the restored state
|
||||
const app = createApp(Quality.Insiders);
|
||||
// // Checking latest version for the restored state
|
||||
// const app = createApp(Quality.Insiders);
|
||||
|
||||
if (!app) {
|
||||
return assert(false);
|
||||
}
|
||||
// await app.start(false);
|
||||
|
||||
await app.start(false);
|
||||
app.suiteName = 'Data Migration';
|
||||
// await app.workbench.editors.waitForActiveTab('Untitled-1', true);
|
||||
// await app.workbench.editor.waitForEditorContents('Untitled-1', c => c.indexOf(textToType) > -1);
|
||||
|
||||
assert.ok(await app.workbench.waitForActiveTab('Untitled-1', true), `Untitled-1 tab is not present after migration.`);
|
||||
// await app.stop();
|
||||
// });
|
||||
|
||||
await app.workbench.editor.waitForEditorContents('Untitled-1', c => c.indexOf(textToType) > -1);
|
||||
await app.screenCapturer.capture('Untitled file text');
|
||||
// it('checks if the newly created dirty file is restored migrating from stable to latest', async function () {
|
||||
// const stableApp = createApp(Quality.Stable);
|
||||
|
||||
await app.stop();
|
||||
});
|
||||
// if (!stableApp) {
|
||||
// this.skip();
|
||||
// return;
|
||||
// }
|
||||
|
||||
it('checks if the newly created dirty file is restored migrating from stable to latest', async function () {
|
||||
const stableApp = createApp(Quality.Stable);
|
||||
// await stableApp.start();
|
||||
|
||||
if (!stableApp) {
|
||||
this.skip();
|
||||
return;
|
||||
}
|
||||
// const fileName = 'app.js';
|
||||
// const textPart = 'This is going to be an unsaved file';
|
||||
|
||||
await stableApp.start();
|
||||
stableApp.suiteName = 'Data Migration';
|
||||
// await stableApp.workbench.quickopen.openFile(fileName);
|
||||
|
||||
const fileName = 'app.js';
|
||||
const textPart = 'This is going to be an unsaved file';
|
||||
// await stableApp.workbench.editor.waitForTypeInEditor(fileName, textPart);
|
||||
|
||||
await stableApp.workbench.quickopen.openFile(fileName);
|
||||
// await stableApp.stop();
|
||||
// await new Promise(c => setTimeout(c, 500)); // wait until all resources are released (e.g. locked local storage)
|
||||
|
||||
await stableApp.workbench.editor.waitForTypeInEditor(fileName, textPart);
|
||||
// // Checking latest version for the restored state
|
||||
// const app = createApp(Quality.Insiders);
|
||||
|
||||
await stableApp.stop();
|
||||
await new Promise(c => setTimeout(c, 500)); // wait until all resources are released (e.g. locked local storage)
|
||||
// await app.start(false);
|
||||
|
||||
// Checking latest version for the restored state
|
||||
const app = createApp(Quality.Insiders);
|
||||
// await app.workbench.editors.waitForActiveTab(fileName);
|
||||
// await app.workbench.editor.waitForEditorContents(fileName, c => c.indexOf(textPart) > -1);
|
||||
|
||||
if (!app) {
|
||||
return assert(false);
|
||||
}
|
||||
// await app.stop();
|
||||
// });
|
||||
|
||||
await app.start(false);
|
||||
app.suiteName = 'Data Migration';
|
||||
// it('checks if opened tabs are restored migrating from stable to latest', async function () {
|
||||
// const stableApp = createApp(Quality.Stable);
|
||||
|
||||
assert.ok(await app.workbench.waitForActiveTab(fileName), `dirty file tab is not present after migration.`);
|
||||
await app.workbench.editor.waitForEditorContents(fileName, c => c.indexOf(textPart) > -1);
|
||||
// if (!stableApp) {
|
||||
// this.skip();
|
||||
// return;
|
||||
// }
|
||||
|
||||
await app.stop();
|
||||
});
|
||||
// await stableApp.start();
|
||||
|
||||
it('checks if opened tabs are restored migrating from stable to latest', async function () {
|
||||
const stableApp = createApp(Quality.Stable);
|
||||
// const fileName1 = 'app.js', fileName2 = 'jsconfig.json', fileName3 = 'readme.md';
|
||||
|
||||
if (!stableApp) {
|
||||
this.skip();
|
||||
return;
|
||||
}
|
||||
// await stableApp.workbench.quickopen.openFile(fileName1);
|
||||
// await stableApp.workbench.runCommand('View: Keep Editor');
|
||||
// await stableApp.workbench.quickopen.openFile(fileName2);
|
||||
// await stableApp.workbench.runCommand('View: Keep Editor');
|
||||
// await stableApp.workbench.quickopen.openFile(fileName3);
|
||||
// await stableApp.stop();
|
||||
|
||||
await stableApp.start();
|
||||
stableApp.suiteName = 'Data Migration';
|
||||
// const app = createApp(Quality.Insiders);
|
||||
|
||||
const fileName1 = 'app.js', fileName2 = 'jsconfig.json', fileName3 = 'readme.md';
|
||||
// await app.start(false);
|
||||
|
||||
await stableApp.workbench.quickopen.openFile(fileName1);
|
||||
await stableApp.workbench.quickopen.runCommand('View: Keep Editor');
|
||||
await stableApp.workbench.quickopen.openFile(fileName2);
|
||||
await stableApp.workbench.quickopen.runCommand('View: Keep Editor');
|
||||
await stableApp.workbench.quickopen.openFile(fileName3);
|
||||
await stableApp.stop();
|
||||
// await app.workbench.editors.waitForTab(fileName1);
|
||||
// await app.workbench.editors.waitForTab(fileName2);
|
||||
// await app.workbench.editors.waitForTab(fileName3);
|
||||
|
||||
const app = createApp(Quality.Insiders);
|
||||
|
||||
if (!app) {
|
||||
return assert(false);
|
||||
}
|
||||
|
||||
await app.start(false);
|
||||
app.suiteName = 'Data Migration';
|
||||
|
||||
assert.ok(await app.workbench.waitForTab(fileName1), `${fileName1} tab was not restored after migration.`);
|
||||
assert.ok(await app.workbench.waitForTab(fileName2), `${fileName2} tab was not restored after migration.`);
|
||||
assert.ok(await app.workbench.waitForTab(fileName3), `${fileName3} tab was not restored after migration.`);
|
||||
|
||||
await app.stop();
|
||||
});
|
||||
// await app.stop();
|
||||
// });
|
||||
});
|
||||
}
|
||||
@@ -3,15 +3,12 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
|
||||
import { SpectronApplication, Quality } from '../../spectron/application';
|
||||
import { Application, Quality } from '../../application';
|
||||
|
||||
export function setup() {
|
||||
describe('Localization', () => {
|
||||
before(async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
this.app.suiteName = 'Localization';
|
||||
const app = this.app as Application;
|
||||
|
||||
if (app.quality === Quality.Dev) {
|
||||
return;
|
||||
@@ -21,36 +18,26 @@ export function setup() {
|
||||
});
|
||||
|
||||
it(`starts with 'DE' locale and verifies title and viewlets text is in German`, async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
const app = this.app as Application;
|
||||
|
||||
if (app.quality === Quality.Dev) {
|
||||
this.skip();
|
||||
return;
|
||||
}
|
||||
|
||||
let text = await app.workbench.explorer.getOpenEditorsViewTitle();
|
||||
await app.screenCapturer.capture('Open editors title');
|
||||
assert(/geöffnete editoren/i.test(text));
|
||||
await app.workbench.explorer.waitForOpenEditorsViewTitle(title => /geöffnete editoren/i.test(title));
|
||||
|
||||
await app.workbench.search.openSearchViewlet();
|
||||
text = await app.workbench.search.getTitle();
|
||||
await app.screenCapturer.capture('Search title');
|
||||
assert(/suchen/i.test(text));
|
||||
await app.workbench.search.waitForTitle(title => /suchen/i.test(title));
|
||||
|
||||
await app.workbench.scm.openSCMViewlet();
|
||||
text = await app.workbench.scm.getTitle();
|
||||
await app.screenCapturer.capture('Scm title');
|
||||
assert(/quellcodeverwaltung/i.test(text));
|
||||
await app.workbench.scm.waitForTitle(title => /quellcodeverwaltung/i.test(title));
|
||||
|
||||
await app.workbench.debug.openDebugViewlet();
|
||||
text = await app.workbench.debug.getTitle();
|
||||
await app.screenCapturer.capture('Debug title');
|
||||
assert(/debuggen/i.test(text));
|
||||
await app.workbench.debug.waitForTitle(title => /debuggen/i.test(title));
|
||||
|
||||
await app.workbench.extensions.openExtensionsViewlet();
|
||||
text = await app.workbench.extensions.getTitle();
|
||||
await app.screenCapturer.capture('Extensions title');
|
||||
assert(/erweiterungen/i.test(text));
|
||||
await app.workbench.extensions.waitForTitle(title => /erweiterungen/i.test(title));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -3,16 +3,15 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../../spectron/application';
|
||||
'use strict';
|
||||
|
||||
import { Code } from '../../vscode/code';
|
||||
|
||||
export abstract class Viewlet {
|
||||
|
||||
constructor(protected spectron: SpectronApplication) {
|
||||
// noop
|
||||
}
|
||||
constructor(protected code: Code) { }
|
||||
|
||||
public async getTitle(): Promise<string> {
|
||||
return this.spectron.client.waitForText('.monaco-workbench-container .part.sidebar > .title > .title-label > span');
|
||||
async waitForTitle(fn: (title: string) => boolean): Promise<void> {
|
||||
await this.code.waitForTextContent('.monaco-workbench-container .part.sidebar > .title > .title-label > span', undefined, fn);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SpectronApplication } from '../../spectron/application';
|
||||
import { Explorer } from '../explorer/explorer';
|
||||
import { ActivityBar } from '../activitybar/activityBar';
|
||||
import { QuickOpen } from '../quickopen/quickopen';
|
||||
@@ -16,13 +15,20 @@ import { StatusBar } from '../statusbar/statusbar';
|
||||
import { Problems } from '../problems/problems';
|
||||
import { SettingsEditor } from '../preferences/settings';
|
||||
import { KeybindingsEditor } from '../preferences/keybindings';
|
||||
import { Editors } from '../editor/editors';
|
||||
import { Code } from '../../vscode/code';
|
||||
import { Terminal } from '../terminal/terminal';
|
||||
|
||||
export class Workbench {
|
||||
export interface Commands {
|
||||
runCommand(command: string): Promise<any>;
|
||||
}
|
||||
|
||||
export class Workbench implements Commands {
|
||||
|
||||
readonly quickopen: QuickOpen;
|
||||
readonly editors: Editors;
|
||||
readonly explorer: Explorer;
|
||||
readonly activitybar: ActivityBar;
|
||||
readonly quickopen: QuickOpen;
|
||||
readonly search: Search;
|
||||
readonly extensions: Extensions;
|
||||
readonly editor: Editor;
|
||||
@@ -34,47 +40,35 @@ export class Workbench {
|
||||
readonly keybindingsEditor: KeybindingsEditor;
|
||||
readonly terminal: Terminal;
|
||||
|
||||
constructor(private spectron: SpectronApplication) {
|
||||
this.explorer = new Explorer(spectron);
|
||||
this.activitybar = new ActivityBar(spectron);
|
||||
this.quickopen = new QuickOpen(spectron);
|
||||
this.search = new Search(spectron);
|
||||
this.extensions = new Extensions(spectron);
|
||||
this.editor = new Editor(spectron);
|
||||
this.scm = new SCM(spectron);
|
||||
this.debug = new Debug(spectron);
|
||||
this.statusbar = new StatusBar(spectron);
|
||||
this.problems = new Problems(spectron);
|
||||
this.settingsEditor = new SettingsEditor(spectron);
|
||||
this.keybindingsEditor = new KeybindingsEditor(spectron);
|
||||
this.terminal = new Terminal(spectron);
|
||||
constructor(private code: Code, private keybindings: any[], userDataPath: string) {
|
||||
this.editors = new Editors(code, this);
|
||||
this.quickopen = new QuickOpen(code, this, this.editors);
|
||||
this.explorer = new Explorer(code, this.quickopen, this.editors);
|
||||
this.activitybar = new ActivityBar(code);
|
||||
this.search = new Search(code, this);
|
||||
this.extensions = new Extensions(code, this);
|
||||
this.editor = new Editor(code, this);
|
||||
this.scm = new SCM(code, this);
|
||||
this.debug = new Debug(code, this, this.editors, this.editor);
|
||||
this.statusbar = new StatusBar(code);
|
||||
this.problems = new Problems(code, this);
|
||||
this.settingsEditor = new SettingsEditor(code, userDataPath, this, this.editors, this.editor);
|
||||
this.keybindingsEditor = new KeybindingsEditor(code, this);
|
||||
this.terminal = new Terminal(code, this);
|
||||
}
|
||||
|
||||
public async saveOpenedFile(): Promise<any> {
|
||||
await this.spectron.client.waitForElement('.tabs-container div.tab.active.dirty');
|
||||
await this.spectron.workbench.quickopen.runCommand('File: Save');
|
||||
}
|
||||
/**
|
||||
* Retrieves the command from keybindings file and executes it with WebdriverIO client API
|
||||
* @param command command (e.g. 'workbench.action.files.newUntitledFile')
|
||||
*/
|
||||
async runCommand(command: string): Promise<void> {
|
||||
const binding = this.keybindings.find(x => x['command'] === command);
|
||||
|
||||
public async selectTab(tabName: string, untitled: boolean = false): Promise<void> {
|
||||
await this.spectron.client.waitAndClick(`.tabs-container div.tab[aria-label="${tabName}, tab"]`);
|
||||
await this.waitForEditorFocus(tabName, untitled);
|
||||
}
|
||||
|
||||
public async waitForEditorFocus(fileName: string, untitled: boolean = false): Promise<void> {
|
||||
await this.waitForActiveTab(fileName);
|
||||
await this.editor.waitForActiveEditor(fileName);
|
||||
}
|
||||
|
||||
public async waitForActiveTab(fileName: string, isDirty: boolean = false): Promise<any> {
|
||||
return this.spectron.client.waitForElement(`.tabs-container div.tab.active${isDirty ? '.dirty' : ''}[aria-selected="true"][aria-label="${fileName}, tab"]`);
|
||||
}
|
||||
|
||||
public async waitForTab(fileName: string, isDirty: boolean = false): Promise<boolean> {
|
||||
return this.spectron.client.waitForElement(`.tabs-container div.tab${isDirty ? '.dirty' : ''}[aria-label="${fileName}, tab"]`).then(() => true);
|
||||
}
|
||||
|
||||
public async newUntitledFile(): Promise<void> {
|
||||
await this.spectron.runCommand('workbench.action.files.newUntitledFile');
|
||||
await this.waitForEditorFocus('Untitled-1', true);
|
||||
if (binding) {
|
||||
await this.code.dispatchKeybinding(binding.key);
|
||||
} else {
|
||||
await this.quickopen.runCommand(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as mkdirp from 'mkdirp';
|
||||
import { Application } from 'spectron';
|
||||
import { sanitize } from './utilities';
|
||||
|
||||
export class ScreenCapturer {
|
||||
|
||||
private static counter = 0;
|
||||
|
||||
constructor(
|
||||
private application: Application,
|
||||
public suiteName: string,
|
||||
private screenshotsDirPath: string | undefined,
|
||||
) { }
|
||||
|
||||
async capture(name: string): Promise<void> {
|
||||
if (!this.screenshotsDirPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
const screenshotPath = path.join(
|
||||
this.screenshotsDirPath,
|
||||
sanitize(this.suiteName),
|
||||
`${ScreenCapturer.counter++}-${sanitize(name)}.png`
|
||||
);
|
||||
|
||||
const image = await this.application.browserWindow.capturePage();
|
||||
await new Promise((c, e) => mkdirp(path.dirname(screenshotPath), err => err ? e(err) : c()));
|
||||
await new Promise((c, e) => fs.writeFile(screenshotPath, image, err => err ? e(err) : c()));
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import { dirname } from 'path';
|
||||
|
||||
export function nfcall<R>(fn: Function, ...args): Promise<R> {
|
||||
return new Promise<R>((c, e) => fn(...args, (err, r) => err ? e(err) : c(r)));
|
||||
}
|
||||
|
||||
export async function mkdirp(path: string, mode?: number): Promise<boolean> {
|
||||
const mkdir = async () => {
|
||||
try {
|
||||
await nfcall(fs.mkdir, path, mode);
|
||||
} catch (err) {
|
||||
if (err.code === 'EEXIST') {
|
||||
const stat = await nfcall<fs.Stats>(fs.stat, path);
|
||||
|
||||
if (stat.isDirectory) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(`'${path}' exists and is not a directory.`);
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
// is root?
|
||||
if (path === dirname(path)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
await mkdir();
|
||||
} catch (err) {
|
||||
if (err.code !== 'ENOENT') {
|
||||
throw err;
|
||||
}
|
||||
|
||||
await mkdirp(dirname(path), mode);
|
||||
await mkdir();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function sanitize(name: string): string {
|
||||
return name.replace(/[&*:\/]/g, '');
|
||||
}
|
||||
42
test/smoke/src/logger.ts
Normal file
42
test/smoke/src/logger.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { appendFileSync, writeFileSync } from 'fs';
|
||||
import { format } from 'util';
|
||||
import { EOL } from 'os';
|
||||
|
||||
export interface Logger {
|
||||
log(message: string, ...args: any[]): void;
|
||||
}
|
||||
|
||||
export class ConsoleLogger implements Logger {
|
||||
|
||||
log(message: string, ...args: any[]): void {
|
||||
console.log('**', message, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
export class FileLogger implements Logger {
|
||||
|
||||
constructor(private path: string) {
|
||||
writeFileSync(path, '');
|
||||
}
|
||||
|
||||
log(message: string, ...args: any[]): void {
|
||||
const date = new Date().toISOString();
|
||||
appendFileSync(this.path, `[${date}] ${format(message, ...args)}${EOL}`);
|
||||
}
|
||||
}
|
||||
|
||||
export class MultiLogger implements Logger {
|
||||
|
||||
constructor(private loggers: Logger[]) { }
|
||||
|
||||
log(message: string, ...args: any[]): void {
|
||||
for (const logger of this.loggers) {
|
||||
logger.log(message, ...args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,9 +11,9 @@ import * as minimist from 'minimist';
|
||||
import * as tmp from 'tmp';
|
||||
import * as rimraf from 'rimraf';
|
||||
import * as mkdirp from 'mkdirp';
|
||||
import { SpectronApplication, Quality } from './spectron/application';
|
||||
import { setup as setupDataMigrationTests } from './areas/workbench/data-migration.test';
|
||||
import { Application, Quality } from './application';
|
||||
|
||||
import { setup as setupDataMigrationTests } from './areas/workbench/data-migration.test';
|
||||
import { setup as setupDataLossTests } from './areas/workbench/data-loss.test';
|
||||
import { setup as setupDataExplorerTests } from './areas/explorer/explorer.test';
|
||||
import { setup as setupDataPreferencesTests } from './areas/preferences/preferences.test';
|
||||
@@ -24,9 +24,10 @@ import { setup as setupDataDebugTests } from './areas/debug/debug.test';
|
||||
import { setup as setupDataGitTests } from './areas/git/git.test';
|
||||
import { setup as setupDataStatusbarTests } from './areas/statusbar/statusbar.test';
|
||||
import { setup as setupDataExtensionTests } from './areas/extensions/extensions.test';
|
||||
import { setup as setupTerminalTests } from './areas/terminal/terminal.test';
|
||||
import { setup as setupDataMultirootTests } from './areas/multiroot/multiroot.test';
|
||||
import { setup as setupDataLocalizationTests } from './areas/workbench/localization.test';
|
||||
// import './areas/terminal/terminal.test';
|
||||
import { MultiLogger, Logger, ConsoleLogger, FileLogger } from './logger';
|
||||
|
||||
const tmpDir = tmp.dirSync({ prefix: 't' }) as { name: string; removeCallback: Function; };
|
||||
const testDataPath = tmpDir.name;
|
||||
@@ -37,13 +38,20 @@ const opts = minimist(args, {
|
||||
string: [
|
||||
'build',
|
||||
'stable-build',
|
||||
'log',
|
||||
'wait-time'
|
||||
]
|
||||
'wait-time',
|
||||
'test-repo',
|
||||
'keybindings',
|
||||
'screenshots',
|
||||
'log'
|
||||
],
|
||||
boolean: [
|
||||
'verbose'
|
||||
],
|
||||
default: {
|
||||
verbose: false
|
||||
}
|
||||
});
|
||||
|
||||
const artifactsPath = opts.log || '';
|
||||
|
||||
const workspaceFilePath = path.join(testDataPath, 'smoketest.code-workspace');
|
||||
const testRepoUrl = 'https://github.com/Microsoft/vscode-smoketest-express';
|
||||
const workspacePath = path.join(testDataPath, 'vscode-smoketest-express');
|
||||
@@ -51,6 +59,12 @@ const keybindingsPath = path.join(testDataPath, 'keybindings.json');
|
||||
const extensionsPath = path.join(testDataPath, 'extensions-dir');
|
||||
mkdirp.sync(extensionsPath);
|
||||
|
||||
const screenshotsPath = opts.screenshots ? path.resolve(opts.screenshots) : null;
|
||||
|
||||
if (screenshotsPath) {
|
||||
mkdirp.sync(screenshotsPath);
|
||||
}
|
||||
|
||||
function fail(errorMessage): void {
|
||||
console.error(errorMessage);
|
||||
process.exit(1);
|
||||
@@ -96,16 +110,16 @@ function getBuildElectronPath(root: string): string {
|
||||
}
|
||||
|
||||
let testCodePath = opts.build;
|
||||
let stableCodePath = opts['stable-build'];
|
||||
// let stableCodePath = opts['stable-build'];
|
||||
let electronPath: string;
|
||||
let stablePath: string;
|
||||
// let stablePath: string;
|
||||
|
||||
if (testCodePath) {
|
||||
electronPath = getBuildElectronPath(testCodePath);
|
||||
|
||||
if (stableCodePath) {
|
||||
stablePath = getBuildElectronPath(stableCodePath);
|
||||
}
|
||||
// if (stableCodePath) {
|
||||
// stablePath = getBuildElectronPath(stableCodePath);
|
||||
// }
|
||||
} else {
|
||||
testCodePath = getDevElectronPath();
|
||||
electronPath = testCodePath;
|
||||
@@ -147,96 +161,106 @@ function toUri(path: string): string {
|
||||
return `${path}`;
|
||||
}
|
||||
|
||||
async function getKeybindings(): Promise<void> {
|
||||
if (opts.keybindings) {
|
||||
console.log('*** Using keybindings: ', opts.keybindings);
|
||||
const rawKeybindings = fs.readFileSync(opts.keybindings);
|
||||
fs.writeFileSync(keybindingsPath, rawKeybindings);
|
||||
} else {
|
||||
const keybindingsUrl = `https://raw.githubusercontent.com/Microsoft/vscode-docs/master/build/keybindings/doc.keybindings.${getKeybindingPlatform()}.json`;
|
||||
console.log('*** Fetching keybindings...');
|
||||
|
||||
await new Promise((c, e) => {
|
||||
https.get(keybindingsUrl, res => {
|
||||
const output = fs.createWriteStream(keybindingsPath);
|
||||
res.on('error', e);
|
||||
output.on('error', e);
|
||||
output.on('close', c);
|
||||
res.pipe(output);
|
||||
}).on('error', e);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function createWorkspaceFile(): Promise<void> {
|
||||
if (fs.existsSync(workspaceFilePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('*** Creating workspace file...');
|
||||
const workspace = {
|
||||
folders: [
|
||||
{
|
||||
path: toUri(path.join(workspacePath, 'public'))
|
||||
},
|
||||
{
|
||||
path: toUri(path.join(workspacePath, 'routes'))
|
||||
},
|
||||
{
|
||||
path: toUri(path.join(workspacePath, 'views'))
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
fs.writeFileSync(workspaceFilePath, JSON.stringify(workspace, null, '\t'));
|
||||
}
|
||||
|
||||
async function setupRepository(): Promise<void> {
|
||||
if (opts['test-repo']) {
|
||||
console.log('*** Copying test project repository:', opts['test-repo']);
|
||||
rimraf.sync(workspacePath);
|
||||
// not platform friendly
|
||||
cp.execSync(`cp -R "${opts['test-repo']}" "${workspacePath}"`);
|
||||
} else {
|
||||
if (!fs.existsSync(workspacePath)) {
|
||||
console.log('*** Cloning test project repository...');
|
||||
cp.spawnSync('git', ['clone', testRepoUrl, workspacePath]);
|
||||
} else {
|
||||
console.log('*** Cleaning test project repository...');
|
||||
cp.spawnSync('git', ['fetch'], { cwd: workspacePath });
|
||||
cp.spawnSync('git', ['reset', '--hard', 'FETCH_HEAD'], { cwd: workspacePath });
|
||||
cp.spawnSync('git', ['clean', '-xdf'], { cwd: workspacePath });
|
||||
}
|
||||
|
||||
console.log('*** Running npm install...');
|
||||
cp.execSync('npm install', { cwd: workspacePath, stdio: 'inherit' });
|
||||
}
|
||||
}
|
||||
|
||||
async function setup(): Promise<void> {
|
||||
console.log('*** Test data:', testDataPath);
|
||||
console.log('*** Preparing smoketest setup...');
|
||||
|
||||
const keybindingsUrl = `https://raw.githubusercontent.com/Microsoft/vscode-docs/master/build/keybindings/doc.keybindings.${getKeybindingPlatform()}.json`;
|
||||
console.log('*** Fetching keybindings...');
|
||||
|
||||
await new Promise((c, e) => {
|
||||
https.get(keybindingsUrl, res => {
|
||||
const output = fs.createWriteStream(keybindingsPath);
|
||||
res.on('error', e);
|
||||
output.on('error', e);
|
||||
output.on('close', c);
|
||||
res.pipe(output);
|
||||
}).on('error', e);
|
||||
});
|
||||
|
||||
if (!fs.existsSync(workspaceFilePath)) {
|
||||
console.log('*** Creating workspace file...');
|
||||
const workspace = {
|
||||
folders: [
|
||||
{
|
||||
path: toUri(path.join(workspacePath, 'public'))
|
||||
},
|
||||
{
|
||||
path: toUri(path.join(workspacePath, 'routes'))
|
||||
},
|
||||
{
|
||||
path: toUri(path.join(workspacePath, 'views'))
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
fs.writeFileSync(workspaceFilePath, JSON.stringify(workspace, null, '\t'));
|
||||
}
|
||||
|
||||
if (!fs.existsSync(workspacePath)) {
|
||||
console.log('*** Cloning test project repository...');
|
||||
cp.spawnSync('git', ['clone', testRepoUrl, workspacePath]);
|
||||
} else {
|
||||
console.log('*** Cleaning test project repository...');
|
||||
cp.spawnSync('git', ['fetch'], { cwd: workspacePath });
|
||||
cp.spawnSync('git', ['reset', '--hard', 'FETCH_HEAD'], { cwd: workspacePath });
|
||||
cp.spawnSync('git', ['clean', '-xdf'], { cwd: workspacePath });
|
||||
}
|
||||
|
||||
console.log('*** Running npm install...');
|
||||
cp.execSync('npm install', { cwd: workspacePath, stdio: 'inherit' });
|
||||
await getKeybindings();
|
||||
await createWorkspaceFile();
|
||||
await setupRepository();
|
||||
|
||||
console.log('*** Smoketest setup done!\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* WebDriverIO 4.8.0 outputs all kinds of "deprecation" warnings
|
||||
* for common commands like `keys` and `moveToObject`.
|
||||
* According to https://github.com/Codeception/CodeceptJS/issues/531,
|
||||
* these deprecation warnings are for Firefox, and have no alternative replacements.
|
||||
* Since we can't downgrade WDIO as suggested (it's Spectron's dep, not ours),
|
||||
* we must suppress the warning with a classic monkey-patch.
|
||||
*
|
||||
* @see webdriverio/lib/helpers/depcrecationWarning.js
|
||||
* @see https://github.com/webdriverio/webdriverio/issues/2076
|
||||
*/
|
||||
// Filter out the following messages:
|
||||
const wdioDeprecationWarning = /^WARNING: the "\w+" command will be deprecated soon../; // [sic]
|
||||
// Monkey patch:
|
||||
const warn = console.warn;
|
||||
console.warn = function suppressWebdriverWarnings(message) {
|
||||
if (wdioDeprecationWarning.test(message)) { return; }
|
||||
warn.apply(console, arguments);
|
||||
};
|
||||
function createApp(quality: Quality): Application {
|
||||
const loggers: Logger[] = [];
|
||||
|
||||
function createApp(quality: Quality): SpectronApplication | null {
|
||||
const path = quality === Quality.Stable ? stablePath : electronPath;
|
||||
|
||||
if (!path) {
|
||||
return null;
|
||||
if (opts.verbose) {
|
||||
loggers.push(new ConsoleLogger());
|
||||
}
|
||||
|
||||
return new SpectronApplication({
|
||||
if (opts.log) {
|
||||
loggers.push(new FileLogger(opts.log));
|
||||
}
|
||||
|
||||
return new Application({
|
||||
quality,
|
||||
electronPath: path,
|
||||
codePath: opts.build,
|
||||
workspacePath,
|
||||
userDataDir,
|
||||
extensionsPath,
|
||||
artifactsPath,
|
||||
workspaceFilePath,
|
||||
waitTime: parseInt(opts['wait-time'] || '0') || 20
|
||||
waitTime: parseInt(opts['wait-time'] || '0') || 20,
|
||||
logger: new MultiLogger(loggers)
|
||||
});
|
||||
}
|
||||
|
||||
before(async function () {
|
||||
// allow two minutes for setup
|
||||
this.timeout(2 * 60 * 1000);
|
||||
@@ -244,6 +268,7 @@ before(async function () {
|
||||
});
|
||||
|
||||
after(async function () {
|
||||
await new Promise(c => setTimeout(c, 500)); // wait for shutdown
|
||||
await new Promise((c, e) => rimraf(testDataPath, { maxBusyTries: 10 }, err => err ? e(err) : c()));
|
||||
});
|
||||
|
||||
@@ -251,7 +276,7 @@ describe('Data Migration', () => {
|
||||
setupDataMigrationTests(userDataDir, createApp);
|
||||
});
|
||||
|
||||
describe('Everything Else', () => {
|
||||
describe('Test', () => {
|
||||
before(async function () {
|
||||
const app = createApp(quality);
|
||||
await app!.start();
|
||||
@@ -262,6 +287,36 @@ describe('Everything Else', () => {
|
||||
await this.app.stop();
|
||||
});
|
||||
|
||||
if (screenshotsPath) {
|
||||
afterEach(async function () {
|
||||
if (this.currentTest.state !== 'failed') {
|
||||
return;
|
||||
}
|
||||
|
||||
const app = this.app as Application;
|
||||
const raw = await app.capturePage();
|
||||
const buffer = new Buffer(raw, 'base64');
|
||||
|
||||
const name = this.currentTest.fullTitle().replace(/[^a-z0-9\-]/ig, '_');
|
||||
const screenshotPath = path.join(screenshotsPath, `${name}.png`);
|
||||
|
||||
if (opts.log) {
|
||||
app.logger.log('*** Screenshot recorded:', screenshotPath);
|
||||
}
|
||||
|
||||
fs.writeFileSync(screenshotPath, buffer);
|
||||
});
|
||||
}
|
||||
|
||||
if (opts.log) {
|
||||
beforeEach(async function () {
|
||||
const app = this.app as Application;
|
||||
const title = this.currentTest.fullTitle();
|
||||
|
||||
app.logger.log('*** Test start:', title);
|
||||
});
|
||||
}
|
||||
|
||||
setupDataLossTests();
|
||||
setupDataExplorerTests();
|
||||
setupDataPreferencesTests();
|
||||
@@ -272,6 +327,7 @@ describe('Everything Else', () => {
|
||||
setupDataGitTests();
|
||||
setupDataStatusbarTests();
|
||||
setupDataExtensionTests();
|
||||
setupTerminalTests();
|
||||
setupDataMultirootTests();
|
||||
setupDataLocalizationTests();
|
||||
});
|
||||
|
||||
@@ -1,351 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Application, SpectronClient as WebClient } from 'spectron';
|
||||
import { test as testPort } from 'portastic';
|
||||
import { SpectronClient } from './client';
|
||||
import { ScreenCapturer } from '../helpers/screenshot';
|
||||
import { Workbench } from '../areas/workbench/workbench';
|
||||
import * as fs from 'fs';
|
||||
import * as cp from 'child_process';
|
||||
import * as path from 'path';
|
||||
import * as mkdirp from 'mkdirp';
|
||||
import { sanitize } from '../helpers/utilities';
|
||||
|
||||
// Just hope random helps us here, cross your fingers!
|
||||
export async function findFreePort(): Promise<number> {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const port = 10000 + Math.round(Math.random() * 10000);
|
||||
|
||||
if (await testPort(port)) {
|
||||
return port;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Could not find free port!');
|
||||
}
|
||||
|
||||
export enum Quality {
|
||||
Dev,
|
||||
Insiders,
|
||||
Stable
|
||||
}
|
||||
|
||||
export interface SpectronApplicationOptions {
|
||||
quality: Quality;
|
||||
electronPath: string;
|
||||
workspacePath: string;
|
||||
userDataDir: string;
|
||||
extensionsPath: string;
|
||||
artifactsPath: string;
|
||||
workspaceFilePath: string;
|
||||
waitTime: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps Spectron's Application instance with its used methods.
|
||||
*/
|
||||
export class SpectronApplication {
|
||||
|
||||
private static count = 0;
|
||||
|
||||
private _client: SpectronClient;
|
||||
private _workbench: Workbench;
|
||||
private _screenCapturer: ScreenCapturer;
|
||||
private spectron: Application | undefined;
|
||||
private keybindings: any[]; private stopLogCollection: (() => Promise<void>) | undefined;
|
||||
|
||||
constructor(
|
||||
private options: SpectronApplicationOptions
|
||||
) { }
|
||||
|
||||
get quality(): Quality {
|
||||
return this.options.quality;
|
||||
}
|
||||
|
||||
get client(): SpectronClient {
|
||||
return this._client;
|
||||
}
|
||||
|
||||
get webclient(): WebClient {
|
||||
if (!this.spectron) {
|
||||
throw new Error('Application not started');
|
||||
}
|
||||
|
||||
return this.spectron.client;
|
||||
}
|
||||
|
||||
get screenCapturer(): ScreenCapturer {
|
||||
return this._screenCapturer;
|
||||
}
|
||||
|
||||
get workbench(): Workbench {
|
||||
return this._workbench;
|
||||
}
|
||||
|
||||
get workspacePath(): string {
|
||||
return this.options.workspacePath;
|
||||
}
|
||||
|
||||
get extensionsPath(): string {
|
||||
return this.options.extensionsPath;
|
||||
}
|
||||
|
||||
get userDataPath(): string {
|
||||
return this.options.userDataDir;
|
||||
}
|
||||
|
||||
get workspaceFilePath(): string {
|
||||
return this.options.workspaceFilePath;
|
||||
}
|
||||
|
||||
private _suiteName: string = 'Init';
|
||||
|
||||
set suiteName(suiteName: string) {
|
||||
this._suiteName = suiteName;
|
||||
this._screenCapturer.suiteName = suiteName;
|
||||
}
|
||||
|
||||
async start(waitForWelcome: boolean = true): Promise<any> {
|
||||
await this._start();
|
||||
|
||||
if (waitForWelcome) {
|
||||
await this.waitForWelcome();
|
||||
}
|
||||
}
|
||||
|
||||
async restart(options: { workspaceOrFolder?: string, extraArgs?: string[] }): Promise<any> {
|
||||
await this.stop();
|
||||
await new Promise(c => setTimeout(c, 1000));
|
||||
await this._start(options.workspaceOrFolder, options.extraArgs);
|
||||
}
|
||||
|
||||
private async _start(workspaceOrFolder = this.options.workspacePath, extraArgs: string[] = []): Promise<any> {
|
||||
await this.retrieveKeybindings();
|
||||
cp.execSync('git checkout .', { cwd: this.options.workspacePath });
|
||||
await this.startApplication(workspaceOrFolder, extraArgs);
|
||||
await this.checkWindowReady();
|
||||
}
|
||||
|
||||
async reload(): Promise<any> {
|
||||
await this.workbench.quickopen.runCommand('Reload Window');
|
||||
// TODO @sandy: Find a proper condition to wait for reload
|
||||
await new Promise(c => setTimeout(c, 1500));
|
||||
await this.checkWindowReady();
|
||||
}
|
||||
|
||||
async stop(): Promise<any> {
|
||||
if (this.stopLogCollection) {
|
||||
await this.stopLogCollection();
|
||||
this.stopLogCollection = undefined;
|
||||
}
|
||||
|
||||
if (this.spectron && this.spectron.isRunning()) {
|
||||
await this.spectron.stop();
|
||||
this.spectron = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private async startApplication(workspaceOrFolder: string, extraArgs: string[] = []): Promise<any> {
|
||||
|
||||
let args: string[] = [];
|
||||
let chromeDriverArgs: string[] = [];
|
||||
|
||||
if (process.env.VSCODE_REPOSITORY) {
|
||||
args.push(process.env.VSCODE_REPOSITORY as string);
|
||||
}
|
||||
|
||||
args.push(workspaceOrFolder);
|
||||
|
||||
// Prevent 'Getting Started' web page from opening on clean user-data-dir
|
||||
args.push('--skip-getting-started');
|
||||
|
||||
// Prevent 'Getting Started' web page from opening on clean user-data-dir
|
||||
args.push('--skip-release-notes');
|
||||
|
||||
// Prevent Quick Open from closing when focus is stolen, this allows concurrent smoketest suite running
|
||||
args.push('--sticky-quickopen');
|
||||
|
||||
// Disable telemetry
|
||||
args.push('--disable-telemetry');
|
||||
|
||||
// Disable updates
|
||||
args.push('--disable-updates');
|
||||
|
||||
// Disable crash reporter
|
||||
// This seems to be the fix for the strange hangups in which Code stays unresponsive
|
||||
// and tests finish badly with timeouts, leaving Code running in the background forever
|
||||
args.push('--disable-crash-reporter');
|
||||
|
||||
// Ensure that running over custom extensions directory, rather than picking up the one that was used by a tester previously
|
||||
args.push(`--extensions-dir=${this.options.extensionsPath}`);
|
||||
|
||||
args.push(...extraArgs);
|
||||
|
||||
chromeDriverArgs.push(`--user-data-dir=${this.options.userDataDir}`);
|
||||
|
||||
// Spectron always uses the same port number for the chrome driver
|
||||
// and it handles gracefully when two instances use the same port number
|
||||
// This works, but when one of the instances quits, it takes down
|
||||
// chrome driver with it, leaving the other instance in DISPAIR!!! :(
|
||||
const port = await findFreePort();
|
||||
|
||||
// We must get a different port for debugging the smoketest express app
|
||||
// otherwise concurrent test runs will clash on those ports
|
||||
const env = { PORT: String(await findFreePort()), ...process.env };
|
||||
|
||||
const opts: any = {
|
||||
path: this.options.electronPath,
|
||||
port,
|
||||
args,
|
||||
env,
|
||||
chromeDriverArgs,
|
||||
startTimeout: 10000,
|
||||
requireName: 'nodeRequire'
|
||||
};
|
||||
|
||||
const runName = String(SpectronApplication.count++);
|
||||
let testsuiteRootPath: string | undefined = undefined;
|
||||
let screenshotsDirPath: string | undefined = undefined;
|
||||
|
||||
if (this.options.artifactsPath) {
|
||||
testsuiteRootPath = path.join(this.options.artifactsPath, sanitize(runName));
|
||||
mkdirp.sync(testsuiteRootPath);
|
||||
|
||||
// Collect screenshots
|
||||
screenshotsDirPath = path.join(testsuiteRootPath, 'screenshots');
|
||||
mkdirp.sync(screenshotsDirPath);
|
||||
|
||||
// Collect chromedriver logs
|
||||
const chromedriverLogPath = path.join(testsuiteRootPath, 'chromedriver.log');
|
||||
opts.chromeDriverLogPath = chromedriverLogPath;
|
||||
|
||||
// Collect webdriver logs
|
||||
const webdriverLogsPath = path.join(testsuiteRootPath, 'webdriver');
|
||||
mkdirp.sync(webdriverLogsPath);
|
||||
opts.webdriverLogPath = webdriverLogsPath;
|
||||
}
|
||||
|
||||
this.spectron = new Application(opts);
|
||||
await this.spectron.start();
|
||||
|
||||
if (testsuiteRootPath) {
|
||||
// Collect logs
|
||||
const mainProcessLogPath = path.join(testsuiteRootPath, 'main.log');
|
||||
const rendererProcessLogPath = path.join(testsuiteRootPath, 'renderer.log');
|
||||
|
||||
const flush = async () => {
|
||||
if (!this.spectron) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mainLogs = await this.spectron.client.getMainProcessLogs();
|
||||
await new Promise((c, e) => fs.appendFile(mainProcessLogPath, mainLogs.join('\n'), { encoding: 'utf8' }, err => err ? e(err) : c()));
|
||||
|
||||
const rendererLogs = (await this.spectron.client.getRenderProcessLogs()).map(m => `${m.timestamp} - ${m.level} - ${m.message}`);
|
||||
await new Promise((c, e) => fs.appendFile(rendererProcessLogPath, rendererLogs.join('\n'), { encoding: 'utf8' }, err => err ? e(err) : c()));
|
||||
};
|
||||
|
||||
let running = true;
|
||||
const loopFlush = async () => {
|
||||
while (true) {
|
||||
await flush();
|
||||
|
||||
if (!running) {
|
||||
return;
|
||||
}
|
||||
|
||||
await new Promise(c => setTimeout(c, 1000));
|
||||
}
|
||||
};
|
||||
|
||||
const loopPromise = loopFlush();
|
||||
this.stopLogCollection = () => {
|
||||
running = false;
|
||||
return loopPromise;
|
||||
};
|
||||
}
|
||||
|
||||
this._screenCapturer = new ScreenCapturer(this.spectron, this._suiteName, screenshotsDirPath);
|
||||
this._client = new SpectronClient(this.spectron, this, this.options.waitTime);
|
||||
this._workbench = new Workbench(this);
|
||||
}
|
||||
|
||||
private async checkWindowReady(): Promise<any> {
|
||||
await this.webclient.waitUntilWindowLoaded();
|
||||
|
||||
// Pick the first workbench window here
|
||||
const count = await this.webclient.getWindowCount();
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
await this.webclient.windowByIndex(i);
|
||||
|
||||
if (/bootstrap\/index\.html/.test(await this.webclient.getUrl())) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await this.client.waitForElement('.monaco-workbench');
|
||||
}
|
||||
|
||||
private async waitForWelcome(): Promise<any> {
|
||||
await this.client.waitForElement('.explorer-folders-view');
|
||||
await this.client.waitForElement(`.editor-container[id="workbench.editor.walkThroughPart"] .welcomePage`);
|
||||
}
|
||||
|
||||
private retrieveKeybindings(): Promise<void> {
|
||||
return new Promise((c, e) => {
|
||||
fs.readFile(process.env.VSCODE_KEYBINDINGS_PATH as string, 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
try {
|
||||
this.keybindings = JSON.parse(data);
|
||||
c();
|
||||
} catch (e) {
|
||||
throw new Error(`Error parsing keybindings JSON: ${e}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the command from keybindings file and executes it with WebdriverIO client API
|
||||
* @param command command (e.g. 'workbench.action.files.newUntitledFile')
|
||||
*/
|
||||
runCommand(command: string): Promise<any> {
|
||||
const binding = this.keybindings.find(x => x['command'] === command);
|
||||
if (!binding) {
|
||||
return this.workbench.quickopen.runCommand(command);
|
||||
}
|
||||
|
||||
const keys: string = binding.key;
|
||||
let keysToPress: string[] = [];
|
||||
|
||||
const chords = keys.split(' ');
|
||||
chords.forEach((chord) => {
|
||||
const keys = chord.split('+');
|
||||
keys.forEach((key) => keysToPress.push(this.transliterate(key)));
|
||||
keysToPress.push('NULL');
|
||||
});
|
||||
|
||||
return this.client.keys(keysToPress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transliterates key names from keybindings file to WebdriverIO keyboard actions defined in:
|
||||
* https://w3c.github.io/webdriver/webdriver-spec.html#keyboard-actions
|
||||
*/
|
||||
private transliterate(key: string): string {
|
||||
switch (key) {
|
||||
case 'ctrl':
|
||||
return 'Control';
|
||||
case 'cmd':
|
||||
return 'Meta';
|
||||
default:
|
||||
return key.length === 1 ? key : key.charAt(0).toUpperCase() + key.slice(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Application } from 'spectron';
|
||||
import { RawResult, Element } from 'webdriverio';
|
||||
import { SpectronApplication } from './application';
|
||||
|
||||
/**
|
||||
* Abstracts the Spectron's WebdriverIO managed client property on the created Application instances.
|
||||
*/
|
||||
export class SpectronClient {
|
||||
|
||||
// waitFor calls should not take more than 200 * 100 = 20 seconds to complete, excluding
|
||||
// the time it takes for the actual retry call to complete
|
||||
private retryCount: number;
|
||||
private readonly retryDuration = 100; // in milliseconds
|
||||
|
||||
constructor(
|
||||
readonly spectron: Application,
|
||||
private application: SpectronApplication,
|
||||
waitTime: number
|
||||
) {
|
||||
this.retryCount = (waitTime * 1000) / this.retryDuration;
|
||||
}
|
||||
|
||||
keys(keys: string[]): Promise<void> {
|
||||
this.spectron.client.keys(keys);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
async getText(selector: string, capture: boolean = true): Promise<any> {
|
||||
return this.spectron.client.getText(selector);
|
||||
}
|
||||
|
||||
async waitForText(selector: string, text?: string, accept?: (result: string) => boolean): Promise<string> {
|
||||
accept = accept ? accept : result => text !== void 0 ? text === result : !!result;
|
||||
return this.waitFor(() => this.spectron.client.getText(selector), accept, `getText with selector ${selector}`);
|
||||
}
|
||||
|
||||
async waitForTextContent(selector: string, textContent?: string, accept?: (result: string) => boolean): Promise<string> {
|
||||
accept = accept ? accept : (result => textContent !== void 0 ? textContent === result : !!result);
|
||||
const fn = async () => await this.spectron.client.selectorExecute(selector, div => Array.isArray(div) ? div[0].textContent : div.textContent);
|
||||
return this.waitFor(fn, s => accept!(typeof s === 'string' ? s : ''), `getTextContent with selector ${selector}`);
|
||||
}
|
||||
|
||||
async waitForValue(selector: string, value?: string, accept?: (result: string) => boolean): Promise<any> {
|
||||
accept = accept ? accept : result => value !== void 0 ? value === result : !!result;
|
||||
return this.waitFor(() => this.spectron.client.getValue(selector), accept, `getValue with selector ${selector}`);
|
||||
}
|
||||
|
||||
async waitAndClick(selector: string): Promise<any> {
|
||||
return this.waitFor(() => this.spectron.client.click(selector), void 0, `click with selector ${selector}`);
|
||||
}
|
||||
|
||||
async click(selector: string): Promise<any> {
|
||||
return this.spectron.client.click(selector);
|
||||
}
|
||||
|
||||
async doubleClickAndWait(selector: string, capture: boolean = true): Promise<any> {
|
||||
return this.waitFor(() => this.spectron.client.doubleClick(selector), void 0, `doubleClick with selector ${selector}`);
|
||||
}
|
||||
|
||||
async leftClick(selector: string, xoffset: number, yoffset: number, capture: boolean = true): Promise<any> {
|
||||
return this.spectron.client.leftClick(selector, xoffset, yoffset);
|
||||
}
|
||||
|
||||
async rightClick(selector: string, capture: boolean = true): Promise<any> {
|
||||
return this.spectron.client.rightClick(selector);
|
||||
}
|
||||
|
||||
async moveToObject(selector: string, capture: boolean = true): Promise<any> {
|
||||
return this.spectron.client.moveToObject(selector);
|
||||
}
|
||||
|
||||
async waitAndMoveToObject(selector: string): Promise<any> {
|
||||
return this.waitFor(() => this.spectron.client.moveToObject(selector), void 0, `move to object with selector ${selector}`);
|
||||
}
|
||||
|
||||
async setValue(selector: string, text: string, capture: boolean = true): Promise<any> {
|
||||
return this.spectron.client.setValue(selector, text);
|
||||
}
|
||||
|
||||
async waitForElements(selector: string, accept: (result: Element[]) => boolean = result => result.length > 0): Promise<Element[]> {
|
||||
return this.waitFor<RawResult<Element[]>>(() => this.spectron.client.elements(selector), result => accept(result.value), `elements with selector ${selector}`)
|
||||
.then(result => result.value);
|
||||
}
|
||||
|
||||
async waitForElement(selector: string, accept: (result: Element | undefined) => boolean = result => !!result): Promise<Element> {
|
||||
return this.waitFor<RawResult<Element>>(() => this.spectron.client.element(selector), result => accept(result ? result.value : void 0), `element with selector ${selector}`)
|
||||
.then(result => result.value);
|
||||
}
|
||||
|
||||
async waitForVisibility(selector: string, accept: (result: boolean) => boolean = result => result): Promise<any> {
|
||||
return this.waitFor(() => this.spectron.client.isVisible(selector), accept, `isVisible with selector ${selector}`);
|
||||
}
|
||||
|
||||
async element(selector: string): Promise<Element> {
|
||||
return this.spectron.client.element(selector)
|
||||
.then(result => result.value);
|
||||
}
|
||||
|
||||
async waitForActiveElement(selector: string): Promise<any> {
|
||||
return this.waitFor(
|
||||
() => this.spectron.client.execute(s => document.activeElement.matches(s), selector),
|
||||
r => r.value,
|
||||
`wait for active element: ${selector}`
|
||||
);
|
||||
}
|
||||
|
||||
async waitForAttribute(selector: string, attribute: string, accept: (result: string) => boolean = result => !!result): Promise<string> {
|
||||
return this.waitFor<string>(() => this.spectron.client.getAttribute(selector), accept, `attribute with selector ${selector}`);
|
||||
}
|
||||
|
||||
async dragAndDrop(sourceElem: string, destinationElem: string, capture: boolean = true): Promise<any> {
|
||||
return this.spectron.client.dragAndDrop(sourceElem, destinationElem);
|
||||
}
|
||||
|
||||
async selectByValue(selector: string, value: string, capture: boolean = true): Promise<any> {
|
||||
return this.spectron.client.selectByValue(selector, value);
|
||||
}
|
||||
|
||||
async getValue(selector: string, capture: boolean = true): Promise<any> {
|
||||
return this.spectron.client.getValue(selector);
|
||||
}
|
||||
|
||||
async getAttribute(selector: string, attribute: string, capture: boolean = true): Promise<any> {
|
||||
return Promise.resolve(this.spectron.client.getAttribute(selector, attribute));
|
||||
}
|
||||
|
||||
buttonDown(): any {
|
||||
return this.spectron.client.buttonDown();
|
||||
}
|
||||
|
||||
buttonUp(): any {
|
||||
return this.spectron.client.buttonUp();
|
||||
}
|
||||
|
||||
async isVisible(selector: string, capture: boolean = true): Promise<any> {
|
||||
return this.spectron.client.isVisible(selector);
|
||||
}
|
||||
|
||||
async getTitle(): Promise<string> {
|
||||
return this.spectron.client.getTitle();
|
||||
}
|
||||
|
||||
private running = false;
|
||||
async waitFor<T>(func: () => T | Promise<T | undefined>, accept?: (result: T) => boolean | Promise<boolean>, timeoutMessage?: string, retryCount?: number): Promise<T>;
|
||||
async waitFor<T>(func: () => T | Promise<T>, accept: (result: T) => boolean | Promise<boolean> = result => !!result, timeoutMessage?: string, retryCount?: number): Promise<T> {
|
||||
if (this.running) {
|
||||
throw new Error('Not allowed to run nested waitFor calls!');
|
||||
}
|
||||
|
||||
this.running = true;
|
||||
|
||||
try {
|
||||
let trial = 1;
|
||||
retryCount = typeof retryCount === 'number' ? retryCount : this.retryCount;
|
||||
|
||||
while (true) {
|
||||
if (trial > retryCount) {
|
||||
await this.application.screenCapturer.capture('timeout');
|
||||
throw new Error(`${timeoutMessage}: Timed out after ${(retryCount * this.retryDuration) / 1000} seconds.`);
|
||||
}
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = await func();
|
||||
} catch (e) {
|
||||
// console.log(e);
|
||||
}
|
||||
|
||||
if (accept(result)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, this.retryDuration));
|
||||
trial++;
|
||||
}
|
||||
} finally {
|
||||
this.running = false;
|
||||
}
|
||||
}
|
||||
|
||||
// type(text: string): Promise<any> {
|
||||
// return new Promise((res) => {
|
||||
// let textSplit = text.split(' ');
|
||||
|
||||
// const type = async (i: number) => {
|
||||
// if (!textSplit[i] || textSplit[i].length <= 0) {
|
||||
// return res();
|
||||
// }
|
||||
|
||||
// const toType = textSplit[i + 1] ? `${textSplit[i]} ` : textSplit[i];
|
||||
// await this.keys(toType);
|
||||
// await this.keys(['NULL']);
|
||||
// await type(i + 1);
|
||||
// };
|
||||
|
||||
// return type(0);
|
||||
// });
|
||||
// }
|
||||
}
|
||||
324
test/smoke/src/vscode/code.ts
Normal file
324
test/smoke/src/vscode/code.ts
Normal file
@@ -0,0 +1,324 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as path from 'path';
|
||||
import * as cp from 'child_process';
|
||||
import * as os from 'os';
|
||||
import { tmpName } from 'tmp';
|
||||
import { IDriver, connect as connectDriver, IDisposable, IElement } from './driver';
|
||||
import { Logger } from '../logger';
|
||||
|
||||
const repoPath = path.join(__dirname, '../../../..');
|
||||
|
||||
function getDevElectronPath(): string {
|
||||
const buildPath = path.join(repoPath, '.build');
|
||||
const product = require(path.join(repoPath, 'product.json'));
|
||||
|
||||
switch (process.platform) {
|
||||
case 'darwin':
|
||||
return path.join(buildPath, 'electron', `${product.nameLong}.app`, 'Contents', 'MacOS', 'Electron');
|
||||
case 'linux':
|
||||
return path.join(buildPath, 'electron', `${product.applicationName}`);
|
||||
case 'win32':
|
||||
return path.join(buildPath, 'electron', `${product.nameShort}.exe`);
|
||||
default:
|
||||
throw new Error('Unsupported platform.');
|
||||
}
|
||||
}
|
||||
|
||||
function getBuildElectronPath(root: string): string {
|
||||
switch (process.platform) {
|
||||
case 'darwin':
|
||||
return path.join(root, 'Contents', 'MacOS', 'Electron');
|
||||
case 'linux': {
|
||||
const product = require(path.join(root, 'resources', 'app', 'product.json'));
|
||||
return path.join(root, product.applicationName);
|
||||
}
|
||||
case 'win32': {
|
||||
const product = require(path.join(root, 'resources', 'app', 'product.json'));
|
||||
return path.join(root, `${product.nameShort}.exe`);
|
||||
}
|
||||
default:
|
||||
throw new Error('Unsupported platform.');
|
||||
}
|
||||
}
|
||||
|
||||
function getDevOutPath(): string {
|
||||
return path.join(repoPath, 'out');
|
||||
}
|
||||
|
||||
function getBuildOutPath(root: string): string {
|
||||
switch (process.platform) {
|
||||
case 'darwin':
|
||||
return path.join(root, 'Contents', 'Resources', 'app', 'out');
|
||||
default:
|
||||
return path.join(root, 'resources', 'app', 'out');
|
||||
}
|
||||
}
|
||||
|
||||
async function connect(child: cp.ChildProcess, outPath: string, handlePath: string, logger: Logger): Promise<Code> {
|
||||
let errCount = 0;
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
const { client, driver } = await connectDriver(outPath, handlePath);
|
||||
return new Code(child, client, driver, logger);
|
||||
} catch (err) {
|
||||
if (++errCount > 50) {
|
||||
child.kill();
|
||||
throw err;
|
||||
}
|
||||
|
||||
// retry
|
||||
await new Promise(c => setTimeout(c, 100));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Kill all running instances, when dead
|
||||
const instances = new Set<cp.ChildProcess>();
|
||||
process.once('exit', () => instances.forEach(code => code.kill()));
|
||||
|
||||
export interface SpawnOptions {
|
||||
codePath?: string;
|
||||
workspacePath: string;
|
||||
userDataDir: string;
|
||||
extensionsPath: string;
|
||||
logger: Logger;
|
||||
extraArgs?: string[];
|
||||
}
|
||||
|
||||
async function createDriverHandle(): Promise<string> {
|
||||
if ('win32' === os.platform()) {
|
||||
const name = [...Array(15)].map(() => Math.random().toString(36)[3]).join('');
|
||||
return `\\\\.\\pipe\\${name}`;
|
||||
} else {
|
||||
return await new Promise<string>((c, e) => tmpName((err, handlePath) => err ? e(err) : c(handlePath)));
|
||||
}
|
||||
}
|
||||
|
||||
export async function spawn(options: SpawnOptions): Promise<Code> {
|
||||
const codePath = options.codePath;
|
||||
const electronPath = codePath ? getBuildElectronPath(codePath) : getDevElectronPath();
|
||||
const outPath = codePath ? getBuildOutPath(codePath) : getDevOutPath();
|
||||
const handle = await createDriverHandle();
|
||||
|
||||
const args = [
|
||||
options.workspacePath,
|
||||
'--skip-getting-started',
|
||||
'--skip-release-notes',
|
||||
'--sticky-quickopen',
|
||||
'--disable-telemetry',
|
||||
'--disable-updates',
|
||||
'--disable-crash-reporter',
|
||||
`--extensions-dir=${options.extensionsPath}`,
|
||||
`--user-data-dir=${options.userDataDir}`,
|
||||
'--driver', handle
|
||||
];
|
||||
|
||||
if (!codePath) {
|
||||
args.unshift(repoPath);
|
||||
}
|
||||
|
||||
if (options.extraArgs) {
|
||||
args.push(...options.extraArgs);
|
||||
}
|
||||
|
||||
const spawnOptions: cp.SpawnOptions = {};
|
||||
|
||||
const child = cp.spawn(electronPath, args, spawnOptions);
|
||||
|
||||
instances.add(child);
|
||||
child.once('exit', () => instances.delete(child));
|
||||
|
||||
return connect(child, outPath, handle, options.logger);
|
||||
}
|
||||
|
||||
async function poll<T>(
|
||||
fn: () => Promise<T>,
|
||||
acceptFn: (result: T) => boolean,
|
||||
timeoutMessage: string,
|
||||
retryCount: number = 200,
|
||||
retryInterval: number = 100 // millis
|
||||
): Promise<T> {
|
||||
let trial = 1;
|
||||
|
||||
while (true) {
|
||||
if (trial > retryCount) {
|
||||
throw new Error(`Timeout: ${timeoutMessage} after ${(retryCount * retryInterval) / 1000} seconds.`);
|
||||
}
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = await fn();
|
||||
|
||||
if (acceptFn(result)) {
|
||||
return result;
|
||||
}
|
||||
} catch (e) {
|
||||
// console.warn(e);
|
||||
|
||||
if (/Method not implemented/.test(e.message)) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, retryInterval));
|
||||
trial++;
|
||||
}
|
||||
}
|
||||
|
||||
export class Code {
|
||||
|
||||
private _activeWindowId: number | undefined = undefined;
|
||||
private driver: IDriver;
|
||||
|
||||
constructor(
|
||||
private process: cp.ChildProcess,
|
||||
private client: IDisposable,
|
||||
driver: IDriver,
|
||||
readonly logger: Logger
|
||||
) {
|
||||
this.driver = new Proxy(driver, {
|
||||
get(target, prop, receiver) {
|
||||
if (typeof target[prop] !== 'function') {
|
||||
return target[prop];
|
||||
}
|
||||
|
||||
return function (...args) {
|
||||
logger.log(`${prop}`, ...args.filter(a => typeof a === 'string'));
|
||||
return target[prop].apply(this, args);
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async capturePage(): Promise<string> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
return await this.driver.capturePage(windowId);
|
||||
}
|
||||
|
||||
async waitForWindowIds(fn: (windowIds: number[]) => boolean): Promise<void> {
|
||||
await poll(() => this.driver.getWindowIds(), fn, `get window ids`);
|
||||
}
|
||||
|
||||
async dispatchKeybinding(keybinding: string): Promise<void> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
await this.driver.dispatchKeybinding(windowId, keybinding);
|
||||
}
|
||||
|
||||
async reload(): Promise<void> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
await this.driver.reloadWindow(windowId);
|
||||
}
|
||||
|
||||
async waitForTextContent(selector: string, textContent?: string, accept?: (result: string) => boolean): Promise<string> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
accept = accept || (result => textContent !== void 0 ? textContent === result : !!result);
|
||||
return await poll(() => this.driver.getElements(windowId, selector).then(els => els[0].textContent), s => accept!(typeof s === 'string' ? s : ''), `get text content '${selector}'`);
|
||||
}
|
||||
|
||||
async waitAndClick(selector: string, xoffset?: number, yoffset?: number): Promise<void> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
await poll(() => this.driver.click(windowId, selector, xoffset, yoffset), () => true, `click '${selector}'`);
|
||||
}
|
||||
|
||||
async waitAndDoubleClick(selector: string): Promise<void> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
await poll(() => this.driver.doubleClick(windowId, selector), () => true, `double click '${selector}'`);
|
||||
}
|
||||
|
||||
async waitAndMove(selector: string): Promise<void> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
await poll(() => this.driver.move(windowId, selector), () => true, `move '${selector}'`);
|
||||
}
|
||||
|
||||
async waitForSetValue(selector: string, value: string): Promise<void> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
await poll(() => this.driver.setValue(windowId, selector, value), () => true, `set value '${selector}'`);
|
||||
}
|
||||
|
||||
async waitForPaste(selector: string, value: string): Promise<void> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
await poll(() => this.driver.paste(windowId, selector, value), () => true, `paste '${selector}'`);
|
||||
}
|
||||
|
||||
async waitForElements(selector: string, recursive: boolean, accept: (result: IElement[]) => boolean = result => result.length > 0): Promise<IElement[]> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
return await poll(() => this.driver.getElements(windowId, selector, recursive), accept, `get elements '${selector}'`);
|
||||
}
|
||||
|
||||
async waitForElement(selector: string, accept: (result: IElement | undefined) => boolean = result => !!result, retryCount: number = 200): Promise<IElement> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
return await poll<IElement>(() => this.driver.getElements(windowId, selector).then(els => els[0]), accept, `get element '${selector}'`, retryCount);
|
||||
}
|
||||
|
||||
async waitForActiveElement(selector: string, retryCount: number = 200): Promise<void> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
await poll(() => this.driver.isActiveElement(windowId, selector), r => r, `is active element '${selector}'`, retryCount);
|
||||
}
|
||||
|
||||
async waitForTitle(fn: (title: string) => boolean): Promise<void> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
await poll(() => this.driver.getTitle(windowId), fn, `get title`);
|
||||
}
|
||||
|
||||
async waitForTypeInEditor(selector: string, text: string): Promise<void> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
await poll(() => this.driver.typeInEditor(windowId, selector, text), () => true, `type in editor '${selector}'`);
|
||||
}
|
||||
|
||||
async waitForTerminalBuffer(selector: string, accept: (result: string[]) => boolean): Promise<void> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
await poll(() => this.driver.getTerminalBuffer(windowId, selector), accept, `get terminal buffer '${selector}'`);
|
||||
}
|
||||
|
||||
private async getActiveWindowId(): Promise<number> {
|
||||
if (typeof this._activeWindowId !== 'number') {
|
||||
const windows = await this.driver.getWindowIds();
|
||||
this._activeWindowId = windows[0];
|
||||
}
|
||||
|
||||
return this._activeWindowId;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.client.dispose();
|
||||
this.process.kill();
|
||||
}
|
||||
}
|
||||
|
||||
export function findElement(element: IElement, fn: (element: IElement) => boolean): IElement | null {
|
||||
const queue = [element];
|
||||
|
||||
while (queue.length > 0) {
|
||||
const element = queue.shift()!;
|
||||
|
||||
if (fn(element)) {
|
||||
return element;
|
||||
}
|
||||
|
||||
queue.push(...element.children);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function findElements(element: IElement, fn: (element: IElement) => boolean): IElement[] {
|
||||
const result: IElement[] = [];
|
||||
const queue = [element];
|
||||
|
||||
while (queue.length > 0) {
|
||||
const element = queue.shift()!;
|
||||
|
||||
if (fn(element)) {
|
||||
result.push(element);
|
||||
}
|
||||
|
||||
queue.push(...element.children);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
12
test/smoke/src/vscode/driver.js
Normal file
12
test/smoke/src/vscode/driver.js
Normal file
@@ -0,0 +1,12 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
const path = require('path');
|
||||
|
||||
exports.connect = function (outPath, handle) {
|
||||
const bootstrapPath = path.join(outPath, 'bootstrap-amd.js');
|
||||
const { bootstrap } = require(bootstrapPath);
|
||||
return new Promise((c, e) => bootstrap('vs/platform/driver/node/driver', ({ connect }) => connect(handle).then(c, e), e));
|
||||
};
|
||||
@@ -1,3 +1,3 @@
|
||||
--timeout 60000
|
||||
--timeout 20000
|
||||
--slow 20000
|
||||
out/main.js
|
||||
33
test/smoke/tools/copy-driver-definition.js
Normal file
33
test/smoke/tools/copy-driver-definition.js
Normal 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.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const root = path.dirname(path.dirname(path.dirname(__dirname)));
|
||||
const driverPath = path.join(root, 'src/vs/platform/driver/common/driver.ts');
|
||||
|
||||
let contents = fs.readFileSync(driverPath, 'utf8');
|
||||
contents = /\/\/\*START([\s\S]*)\/\/\*END/mi.exec(contents)[1].trim();
|
||||
contents = contents.replace(/\bTPromise\b/g, 'Promise');
|
||||
|
||||
contents = `/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
${contents}
|
||||
|
||||
export interface IDisposable {
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export function connect(outPath: string, handle: string): Promise<{ client: IDisposable, driver: IDriver }>;
|
||||
`;
|
||||
|
||||
const srcPath = path.join(path.dirname(__dirname), 'src/vscode');
|
||||
const outDriverPath = path.join(srcPath, 'driver.d.ts');
|
||||
|
||||
fs.writeFileSync(outDriverPath, contents);
|
||||
1167
test/smoke/yarn.lock
1167
test/smoke/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user