Archived
1
0

Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'

This commit is contained in:
Joe Previte
2020-12-15 15:52:33 -07:00
4649 changed files with 1311795 additions and 0 deletions

9
lib/vscode/test/smoke/.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
.DS_Store
npm-debug.log
Thumbs.db
node_modules/
out/
keybindings.*.json
test_data/
src/vscode/driver.d.ts
vscode-server*/

View File

@ -0,0 +1,13 @@
# VS Code Smoke Tests Failures History
This file contains a history of smoke test failures which could be avoided if particular techniques were used in the test (e.g. binding test elements with HTML5 `data-*` attribute).
To better understand what can be employed in smoke test to ensure its stability, it is important to understand patterns that led to smoke test breakage. This markdown is a result of work on [this issue](https://github.com/microsoft/vscode/issues/27906).
# Log
1. This following change led to the smoke test failure because DOM element's attribute `a[title]` was changed:
[eac49a3](https://github.com/microsoft/vscode/commit/eac49a321b84cb9828430e9dcd3f34243a3480f7)
This attribute was used in the smoke test to grab the contents of SCM part in status bar:
[0aec2d6](https://github.com/microsoft/vscode/commit/0aec2d6838b5e65cc74c33b853ffbd9fa191d636)
2. To be continued...

View File

@ -0,0 +1,86 @@
# VS Code Smoke Test
Make sure you are on **Node v12.x**.
### Run
```bash
# Build extensions in repo (if needed)
yarn && yarn compile
# Install Dependencies and Compile
yarn --cwd test/smoke
# Dev (Electron)
yarn smoketest
# Dev (Web)
yarn smoketest --web --browser [chromium|webkit]
# Build (Electron)
yarn smoketest --build <path latest built version> --stable-build <path to previous stable version>
# Build (Web - read instructions below)
yarn smoketest --build <path to web server folder> --web --browser [chromium|webkit]
# Remote (Electron)
yarn smoketest --build <path latest built version> --remote
```
### Run for a release
You must always run the smoketest version which matches the release you are testing. So, if you want to run the smoketest for a release build (e.g. `release/1.22`), you need that version of the smoke tests too:
```bash
git checkout release/1.22
yarn && yarn compile
yarn --cwd test/smoke
```
#### Electron
In addition to the new build to be released you will need the previous stable build so that the smoketest can test the data migration.
The recommended way to make these builds available for the smoketest is by downloading their archive version (\*.zip) and extracting
them into two folders. Pass the folder paths to the smoketest as follows:
```bash
yarn smoketest --build <path latest built version> --stable-build <path to previous stable version>
```
#### Web
**macOS**: if you have downloaded the server with web bits, make sure to run the following command before unzipping it to avoid security issues on startup:
```bash
xattr -d com.apple.quarantine <path to server with web folder zip>
```
There is no support for testing an old version to a new one yet, so simply configure the `--build` command line argument to point to
the web server folder which includes the web client bits (e.g. `vscode-server-darwin-web` for macOS).
**Note**: make sure to point to the server that includes the client bits!
### Debug
- `--verbose` logs all the low level driver calls made to Code;
- `-f PATTERN` (alias `-g PATTERN`) filters the tests to be run. You can also use pretty much any mocha argument;
- `--screenshots SCREENSHOT_DIR` captures screenshots when tests fail.
### Develop
```bash
cd test/smoke
yarn watch
```
## Pitfalls
- 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... but is it the right time to do that? Can you 100% guarantee 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 Access 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 and wait for that instead.

View File

@ -0,0 +1,33 @@
{
"name": "code-oss-dev-smoke-test",
"version": "0.1.0",
"main": "./src/main.js",
"scripts": {
"postinstall": "npm run compile",
"compile": "yarn --cwd ../automation compile && tsc",
"watch": "concurrently \"yarn --cwd ../automation watch --preserveWatchOutput\" \"tsc --watch --preserveWatchOutput\"",
"mocha": "mocha"
},
"devDependencies": {
"@types/htmlparser2": "3.7.29",
"@types/mkdirp": "0.5.1",
"@types/mocha": "2.2.41",
"@types/ncp": "2.0.1",
"@types/node": "^12.11.7",
"@types/rimraf": "2.0.2",
"concurrently": "^3.5.1",
"cpx": "^1.5.0",
"htmlparser2": "^3.9.2",
"mkdirp": "^0.5.1",
"mocha": "^6.1.4",
"mocha-junit-reporter": "^1.17.0",
"mocha-multi-reporters": "^1.1.7",
"ncp": "^2.0.0",
"portastic": "^1.0.1",
"rimraf": "^2.6.1",
"strip-json-comments": "^2.0.1",
"tmp": "0.0.33",
"typescript": "3.7.5",
"watch": "^1.0.2"
}
}

View File

@ -0,0 +1,34 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Application } from '../../../../automation';
export function setup() {
describe('Editor', () => {
it('shows correct quick outline', async function () {
const app = this.app as Application;
await app.workbench.quickaccess.openFile('www');
await app.workbench.quickaccess.openQuickOutline();
await app.workbench.quickinput.waitForQuickInputElements(names => names.length >= 6);
});
// it('folds/unfolds the code correctly', async function () {
// await app.workbench.quickaccess.openFile('www');
// // Fold
// await app.workbench.editor.foldAtLine(3);
// await app.workbench.editor.waitUntilShown(3);
// await app.workbench.editor.waitUntilHidden(4);
// await app.workbench.editor.waitUntilHidden(5);
// // Unfold
// await app.workbench.editor.unfoldAtLine(3);
// await app.workbench.editor.waitUntilShown(3);
// await app.workbench.editor.waitUntilShown(4);
// await app.workbench.editor.waitUntilShown(5);
// });
});
}

View File

@ -0,0 +1,31 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Application, Quality } from '../../../../automation';
export function setup() {
describe('Extensions', () => {
it(`install and activate vscode-smoketest-check extension`, async function () {
const app = this.app as Application;
if (app.quality === Quality.Dev) {
this.skip();
return;
}
await app.workbench.extensions.openExtensionsViewlet();
await app.workbench.extensions.installExtension('michelkaporin.vscode-smoketest-check');
await app.workbench.extensions.waitForExtensionsViewlet();
if (app.remote) {
await app.reload();
}
await app.workbench.quickaccess.runCommand('Smoke Test Check');
await app.workbench.statusbar.waitForStatusbarText('smoke test', 'VS Code Smoke Test Check');
});
});
}

View File

@ -0,0 +1,42 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Application, ProblemSeverity, Problems } from '../../../../automation/out';
export function setup() {
describe('Language Features', () => {
it('verifies quick outline', async function () {
const app = this.app as Application;
await app.workbench.quickaccess.openFile('style.css');
await app.workbench.quickaccess.openQuickOutline();
await app.workbench.quickinput.waitForQuickInputElements(names => names.length === 2);
});
it('verifies problems view', async function () {
const app = this.app as Application;
await app.workbench.quickaccess.openFile('style.css');
await app.workbench.editor.waitForTypeInEditor('style.css', '.foo{}');
await app.code.waitForElement(Problems.getSelectorInEditor(ProblemSeverity.WARNING));
await app.workbench.problems.showProblemsView();
await app.code.waitForElement(Problems.getSelectorInProblemsView(ProblemSeverity.WARNING));
await app.workbench.problems.hideProblemsView();
});
it('verifies settings', async function () {
const app = this.app as Application;
await app.workbench.settingsEditor.addUserSetting('css.lint.emptyRules', '"error"');
await app.workbench.quickaccess.openFile('style.css');
await app.code.waitForElement(Problems.getSelectorInEditor(ProblemSeverity.ERROR));
await app.workbench.problems.showProblemsView();
await app.code.waitForElement(Problems.getSelectorInProblemsView(ProblemSeverity.ERROR));
await app.workbench.problems.hideProblemsView();
});
});
}

View File

@ -0,0 +1,59 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as fs from 'fs';
import * as path from 'path';
import { Application } from '../../../../automation';
function toUri(path: string): string {
if (process.platform === 'win32') {
return `${path.replace(/\\/g, '/')}`;
}
return `${path}`;
}
async function createWorkspaceFile(workspacePath: string): Promise<string> {
const workspaceFilePath = path.join(path.dirname(workspacePath), 'smoketest.code-workspace');
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'));
return workspaceFilePath;
}
export function setup() {
describe('Multiroot', () => {
before(async function () {
const app = this.app as Application;
const workspaceFilePath = await createWorkspaceFile(app.workspacePathOrFolder);
// restart with preventing additional windows from restoring
// to ensure the window after restart is the multi-root workspace
await app.restart({ workspaceOrFolder: workspaceFilePath });
});
it('shows results from all folders', async function () {
const app = this.app as Application;
await app.workbench.quickaccess.openQuickAccess('*.*');
await app.workbench.quickinput.waitForQuickInputElements(names => names.length === 6);
await app.workbench.quickinput.closeQuickInput();
});
it('shows workspace name in title', async function () {
const app = this.app as Application;
await app.code.waitForTitle(title => /smoketest \(Workspace\)/i.test(title));
});
});
}

View File

@ -0,0 +1,74 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as cp from 'child_process';
import { Application } from '../../../../automation';
// function wait(ms: number): Promise<void> {
// return new Promise(r => setTimeout(r, ms));
// }
export function setup() {
describe('Notebooks', () => {
after(async function () {
const app = this.app as Application;
cp.execSync('git checkout . --quiet', { cwd: app.workspacePathOrFolder });
cp.execSync('git reset --hard origin/master --quiet', { cwd: app.workspacePathOrFolder });
});
afterEach(async function () {
const app = this.app as Application;
await app.workbench.quickaccess.runCommand('workbench.action.files.save');
await app.workbench.quickaccess.runCommand('workbench.action.closeActiveEditor');
});
it('inserts/edits code cell', async function () {
const app = this.app as Application;
await app.workbench.notebook.openNotebook();
await app.workbench.notebook.focusNextCell();
await app.workbench.notebook.insertNotebookCell('code');
await app.workbench.notebook.waitForTypeInEditor('// some code');
await app.workbench.notebook.stopEditingCell();
});
it('inserts/edits markdown cell', async function () {
const app = this.app as Application;
await app.workbench.notebook.openNotebook();
await app.workbench.notebook.focusNextCell();
await app.workbench.notebook.insertNotebookCell('markdown');
await app.workbench.notebook.waitForTypeInEditor('## hello2! ');
await app.workbench.notebook.stopEditingCell();
await app.workbench.notebook.waitForMarkdownContents('h2', 'hello2!');
});
it('moves focus as it inserts/deletes a cell', async function () {
const app = this.app as Application;
await app.workbench.notebook.openNotebook();
await app.workbench.notebook.insertNotebookCell('code');
await app.workbench.notebook.waitForActiveCellEditorContents('');
await app.workbench.notebook.stopEditingCell();
await app.workbench.notebook.deleteActiveCell();
await app.workbench.notebook.waitForMarkdownContents('p', 'Markdown Cell');
});
it.skip('moves focus in and out of output', async function () {
const app = this.app as Application;
await app.workbench.notebook.openNotebook();
await app.workbench.notebook.executeActiveCell();
await app.workbench.notebook.focusInCellOutput();
await app.workbench.notebook.focusOutCellOutput();
await app.workbench.notebook.waitForActiveCellEditorContents('code()');
});
it.skip('cell action execution', async function () {
const app = this.app as Application;
await app.workbench.notebook.openNotebook();
await app.workbench.notebook.insertNotebookCell('code');
await app.workbench.notebook.executeCellAction('.notebook-editor .monaco-list-row.focused div.monaco-toolbar .codicon-debug');
await app.workbench.notebook.waitForActiveCellEditorContents('test');
});
});
}

View File

@ -0,0 +1,39 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Application, ActivityBarPosition } from '../../../../automation';
export function setup() {
describe('Preferences', () => {
it('turns off editor line numbers and verifies the live change', async function () {
const app = this.app as Application;
await app.workbench.quickaccess.openFile('app.js');
await app.code.waitForElements('.line-numbers', false, elements => !!elements.length);
await app.workbench.settingsEditor.addUserSetting('editor.lineNumbers', '"off"');
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 Application;
await app.workbench.activitybar.waitForActivityBar(ActivityBarPosition.LEFT);
await app.workbench.keybindingsEditor.updateKeybinding('workbench.action.toggleSidebarPosition', 'ctrl+u', 'Control+U');
await app.code.dispatchKeybinding('ctrl+u');
await app.workbench.activitybar.waitForActivityBar(ActivityBarPosition.RIGHT);
});
after(async function () {
const app = this.app as Application;
await app.workbench.settingsEditor.clearUserSettings();
// Wait for settings to be applied, which will happen after the settings file is empty
await app.workbench.activitybar.waitForActivityBar(ActivityBarPosition.LEFT);
});
});
}

View File

@ -0,0 +1,91 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as cp from 'child_process';
import { Application } from '../../../../automation';
export function setup() {
describe('Search', () => {
after(function () {
const app = this.app as Application;
cp.execSync('git checkout . --quiet', { cwd: app.workspacePathOrFolder });
cp.execSync('git reset --hard origin/master --quiet', { cwd: app.workspacePathOrFolder });
});
it('searches for body & checks for correct result number', async function () {
const app = this.app as Application;
await app.workbench.search.openSearchViewlet();
await app.workbench.search.searchFor('body');
await app.workbench.search.waitForResultText('16 results in 5 files');
});
it('searches only for *.js files & checks for correct result number', async function () {
const app = this.app as Application;
await app.workbench.search.searchFor('body');
await app.workbench.search.showQueryDetails();
await app.workbench.search.setFilesToIncludeText('*.js');
await app.workbench.search.submitSearch();
await app.workbench.search.waitForResultText('4 results in 1 file');
await app.workbench.search.setFilesToIncludeText('');
await app.workbench.search.hideQueryDetails();
});
it('dismisses result & checks for correct result number', async function () {
const app = this.app as Application;
await app.workbench.search.searchFor('body');
await app.workbench.search.removeFileMatch('app.js');
await app.workbench.search.waitForResultText('12 results in 4 files');
});
it('replaces first search result with a replace term', async function () {
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('app.js');
await app.workbench.search.waitForResultText('12 results in 4 files');
await app.workbench.search.searchFor('ydob');
await app.workbench.search.setReplaceText('body');
await app.workbench.search.replaceFileMatch('app.js');
await app.workbench.search.waitForNoResultText();
});
});
describe('Quick Access', () => {
it('quick access search produces correct result', async function () {
const app = this.app as Application;
const expectedNames = [
'.eslintrc.json',
'tasks.json',
'app.js',
'index.js',
'users.js',
'package.json',
'jsconfig.json'
];
await app.workbench.quickaccess.openQuickAccess('.js');
await app.workbench.quickinput.waitForQuickInputElements(names => expectedNames.every(n => names.some(m => n === m)));
await app.code.dispatchKeybinding('escape');
});
it('quick access respects fuzzy matching', async function () {
const app = this.app as Application;
const expectedNames = [
'tasks.json',
'app.js',
'package.json'
];
await app.workbench.quickaccess.openQuickAccess('a.s');
await app.workbench.quickinput.waitForQuickInputElements(names => expectedNames.every(n => names.some(m => n === m)));
await app.code.dispatchKeybinding('escape');
});
});
}

View File

@ -0,0 +1,98 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Application, Quality, StatusBarElement } from '../../../../automation';
export function setup(isWeb) {
describe('Statusbar', () => {
it('verifies presence of all default status bar elements', async function () {
const app = this.app as Application;
await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.BRANCH_STATUS);
if (app.quality !== Quality.Dev) {
await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.FEEDBACK_ICON);
}
await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.SYNC_STATUS);
await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.PROBLEMS_STATUS);
await app.workbench.quickaccess.openFile('app.js');
if (!isWeb) {
// Encoding picker currently hidden in web (only UTF-8 supported)
await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.ENCODING_STATUS);
}
await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.EOL_STATUS);
await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.INDENTATION_STATUS);
await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.LANGUAGE_STATUS);
await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.SELECTION_STATUS);
});
it(`verifies that 'quick input' opens when clicking on status bar elements`, async function () {
const app = this.app as Application;
await app.workbench.statusbar.clickOn(StatusBarElement.BRANCH_STATUS);
await app.workbench.quickinput.waitForQuickInputOpened();
await app.workbench.quickinput.closeQuickInput();
await app.workbench.quickaccess.openFile('app.js');
await app.workbench.statusbar.clickOn(StatusBarElement.INDENTATION_STATUS);
await app.workbench.quickinput.waitForQuickInputOpened();
await app.workbench.quickinput.closeQuickInput();
if (!isWeb) {
// Encoding picker currently hidden in web (only UTF-8 supported)
await app.workbench.statusbar.clickOn(StatusBarElement.ENCODING_STATUS);
await app.workbench.quickinput.waitForQuickInputOpened();
await app.workbench.quickinput.closeQuickInput();
}
await app.workbench.statusbar.clickOn(StatusBarElement.EOL_STATUS);
await app.workbench.quickinput.waitForQuickInputOpened();
await app.workbench.quickinput.closeQuickInput();
await app.workbench.statusbar.clickOn(StatusBarElement.LANGUAGE_STATUS);
await app.workbench.quickinput.waitForQuickInputOpened();
await app.workbench.quickinput.closeQuickInput();
});
it(`verifies that 'Problems View' appears when clicking on 'Problems' status element`, async function () {
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 Application;
if (app.quality === Quality.Dev) {
return this.skip();
}
await app.workbench.statusbar.clickOn(StatusBarElement.FEEDBACK_ICON);
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 Application;
await app.workbench.quickaccess.openFile('app.js');
await app.workbench.statusbar.clickOn(StatusBarElement.SELECTION_STATUS);
await app.workbench.quickinput.waitForQuickInputOpened();
await app.workbench.quickinput.submit(':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 Application;
await app.workbench.quickaccess.openFile('app.js');
await app.workbench.statusbar.clickOn(StatusBarElement.EOL_STATUS);
await app.workbench.quickinput.waitForQuickInputOpened();
await app.workbench.quickinput.selectQuickInputElement(1);
await app.workbench.statusbar.waitForEOL('CRLF');
});
});
}

View File

@ -0,0 +1,33 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Application } from '../../../../automation';
export function setup() {
describe('Dataloss', () => {
it(`verifies that 'hot exit' works for dirty files`, async function () {
const app = this.app as Application;
await app.workbench.editors.newUntitledFile();
const untitled = 'Untitled-1';
const textToTypeInUntitled = 'Hello from Untitled';
await app.workbench.editor.waitForTypeInEditor(untitled, textToTypeInUntitled);
const readmeMd = 'readme.md';
const textToType = 'Hello, Code';
await app.workbench.quickaccess.openFile(readmeMd);
await app.workbench.editor.waitForTypeInEditor(readmeMd, textToType);
await app.reload();
await app.workbench.editors.waitForActiveTab(readmeMd, true);
await app.workbench.editor.waitForEditorContents(readmeMd, c => c.indexOf(textToType) > -1);
await app.workbench.editors.waitForTab(untitled);
await app.workbench.editors.selectTab(untitled);
await app.workbench.editor.waitForEditorContents(untitled, c => c.indexOf(textToTypeInUntitled) > -1);
});
});
}

View File

@ -0,0 +1,94 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Application, ApplicationOptions } from '../../../../automation';
import { join } from 'path';
export function setup(stableCodePath: string, testDataPath: string) {
describe('Datamigration', () => {
it(`verifies opened editors are restored`, async function () {
if (!stableCodePath) {
this.skip();
}
const userDataDir = join(testDataPath, 'd2'); // different data dir from the other tests
const stableOptions: ApplicationOptions = Object.assign({}, this.defaultOptions);
stableOptions.codePath = stableCodePath;
stableOptions.userDataDir = userDataDir;
const stableApp = new Application(stableOptions);
await stableApp!.start();
// Open 3 editors and pin 2 of them
await stableApp.workbench.quickaccess.openFile('www');
await stableApp.workbench.quickaccess.runCommand('View: Keep Editor');
await stableApp.workbench.quickaccess.openFile('app.js');
await stableApp.workbench.quickaccess.runCommand('View: Keep Editor');
await stableApp.workbench.editors.newUntitledFile();
await stableApp.stop();
const insiderOptions: ApplicationOptions = Object.assign({}, this.defaultOptions);
insiderOptions.userDataDir = userDataDir;
const insidersApp = new Application(insiderOptions);
await insidersApp!.start(false /* not expecting walkthrough path */);
// Verify 3 editors are open
await insidersApp.workbench.editors.waitForEditorFocus('Untitled-1');
await insidersApp.workbench.editors.selectTab('app.js');
await insidersApp.workbench.editors.selectTab('www');
await insidersApp.stop();
});
it(`verifies that 'hot exit' works for dirty files`, async function () {
if (!stableCodePath) {
this.skip();
}
const userDataDir = join(testDataPath, 'd3'); // different data dir from the other tests
const stableOptions: ApplicationOptions = Object.assign({}, this.defaultOptions);
stableOptions.codePath = stableCodePath;
stableOptions.userDataDir = userDataDir;
const stableApp = new Application(stableOptions);
await stableApp!.start();
await stableApp.workbench.editors.newUntitledFile();
const untitled = 'Untitled-1';
const textToTypeInUntitled = 'Hello from Untitled';
await stableApp.workbench.editor.waitForTypeInEditor(untitled, textToTypeInUntitled);
const readmeMd = 'readme.md';
const textToType = 'Hello, Code';
await stableApp.workbench.quickaccess.openFile(readmeMd);
await stableApp.workbench.editor.waitForTypeInEditor(readmeMd, textToType);
await stableApp.stop();
const insiderOptions: ApplicationOptions = Object.assign({}, this.defaultOptions);
insiderOptions.userDataDir = userDataDir;
const insidersApp = new Application(insiderOptions);
await insidersApp!.start(false /* not expecting walkthrough path */);
await insidersApp.workbench.editors.waitForActiveTab(readmeMd, true);
await insidersApp.workbench.editor.waitForEditorContents(readmeMd, c => c.indexOf(textToType) > -1);
await insidersApp.workbench.editors.waitForTab(untitled, true);
await insidersApp.workbench.editors.selectTab(untitled);
await insidersApp.workbench.editor.waitForEditorContents(untitled, c => c.indexOf(textToTypeInUntitled) > -1);
await insidersApp.stop();
});
});
}

View File

@ -0,0 +1,38 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import { Application, ApplicationOptions } from '../../../../automation';
export function setup() {
describe('Launch', () => {
let app: Application;
after(async function () {
if (app) {
await app.stop();
}
});
afterEach(async function () {
if (app) {
if (this.currentTest.state === 'failed') {
const name = this.currentTest.fullTitle().replace(/[^a-z0-9\-]/ig, '_');
await app.captureScreenshot(name);
}
}
});
it(`verifies that application launches when user data directory has non-ascii characters`, async function () {
const defaultOptions = this.defaultOptions as ApplicationOptions;
const options: ApplicationOptions = { ...defaultOptions, userDataDir: path.join(defaultOptions.userDataDir, 'abcdø') };
app = new Application(options);
await app.start();
});
});
}

View File

@ -0,0 +1,47 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Application, Quality } from '../../../../automation';
export function setup() {
describe('Localization', () => {
before(async function () {
const app = this.app as Application;
if (app.quality === Quality.Dev) {
return;
}
await app.workbench.extensions.openExtensionsViewlet();
await app.workbench.extensions.installExtension('ms-ceintl.vscode-language-pack-de');
await app.restart({ extraArgs: ['--locale=DE'] });
});
it(`starts with 'DE' locale and verifies title and viewlets text is in German`, async function () {
const app = this.app as Application;
if (app.quality === Quality.Dev) {
this.skip();
return;
}
await app.workbench.explorer.waitForOpenEditorsViewTitle(title => /geöffnete editoren/i.test(title));
await app.workbench.search.openSearchViewlet();
await app.workbench.search.waitForTitle(title => /suchen/i.test(title));
// await app.workbench.scm.openSCMViewlet();
// await app.workbench.scm.waitForTitle(title => /quellcodeverwaltung/i.test(title));
// See https://github.com/microsoft/vscode/issues/93462
// await app.workbench.debug.openDebugViewlet();
// await app.workbench.debug.waitForTitle(title => /starten/i.test(title));
// await app.workbench.extensions.openExtensionsViewlet();
// await app.workbench.extensions.waitForTitle(title => /extensions/i.test(title));
});
});
}

View File

@ -0,0 +1,329 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as fs from 'fs';
import * as cp from 'child_process';
import * as path from 'path';
import * as minimist from 'minimist';
import * as tmp from 'tmp';
import * as rimraf from 'rimraf';
import * as mkdirp from 'mkdirp';
import { ncp } from 'ncp';
import {
Application,
Quality,
ApplicationOptions,
MultiLogger,
Logger,
ConsoleLogger,
FileLogger,
} from '../../automation';
import { setup as setupDataMigrationTests } from './areas/workbench/data-migration.test';
import { setup as setupDataLossTests } from './areas/workbench/data-loss.test';
import { setup as setupDataPreferencesTests } from './areas/preferences/preferences.test';
import { setup as setupDataSearchTests } from './areas/search/search.test';
import { setup as setupDataNotebookTests } from './areas/notebook/notebook.test';
import { setup as setupDataLanguagesTests } from './areas/languages/languages.test';
import { setup as setupDataEditorTests } from './areas/editor/editor.test';
import { setup as setupDataStatusbarTests } from './areas/statusbar/statusbar.test';
import { setup as setupDataExtensionTests } from './areas/extensions/extensions.test';
import { setup as setupDataMultirootTests } from './areas/multiroot/multiroot.test';
import { setup as setupDataLocalizationTests } from './areas/workbench/localization.test';
import { setup as setupLaunchTests } from './areas/workbench/launch.test';
if (!/^v10/.test(process.version) && !/^v12/.test(process.version)) {
console.error('Error: Smoketest must be run using Node 10/12. Currently running', process.version);
process.exit(1);
}
const tmpDir = tmp.dirSync({ prefix: 't' }) as { name: string; removeCallback: Function; };
const testDataPath = tmpDir.name;
process.once('exit', () => rimraf.sync(testDataPath));
const [, , ...args] = process.argv;
const opts = minimist(args, {
string: [
'browser',
'build',
'stable-build',
'wait-time',
'test-repo',
'screenshots',
'log'
],
boolean: [
'verbose',
'remote',
'web'
],
default: {
verbose: false
}
});
const testRepoUrl = 'https://github.com/microsoft/vscode-smoketest-express';
const workspacePath = path.join(testDataPath, 'vscode-smoketest-express');
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);
}
const repoPath = path.join(__dirname, '..', '..', '..');
let quality: Quality;
//
// #### Electron Smoke Tests ####
//
if (!opts.web) {
function getDevElectronPath(): string {
const buildPath = path.join(repoPath, '.build');
const product = require(path.join(repoPath, 'product.json'));
switch (process.platform) {
case 'darwin':
return path.join(buildPath, 'electron', `${product.nameLong}.app`, 'Contents', 'MacOS', 'Electron');
case 'linux':
return path.join(buildPath, 'electron', `${product.applicationName}`);
case 'win32':
return path.join(buildPath, 'electron', `${product.nameShort}.exe`);
default:
throw new Error('Unsupported platform.');
}
}
function getBuildElectronPath(root: string): string {
switch (process.platform) {
case 'darwin':
return path.join(root, 'Contents', 'MacOS', 'Electron');
case 'linux': {
const product = require(path.join(root, 'resources', 'app', 'product.json'));
return path.join(root, product.applicationName);
}
case 'win32': {
const product = require(path.join(root, 'resources', 'app', 'product.json'));
return path.join(root, `${product.nameShort}.exe`);
}
default:
throw new Error('Unsupported platform.');
}
}
let testCodePath = opts.build;
let stableCodePath = opts['stable-build'];
let electronPath: string;
let stablePath: string | undefined = undefined;
if (testCodePath) {
electronPath = getBuildElectronPath(testCodePath);
if (stableCodePath) {
stablePath = getBuildElectronPath(stableCodePath);
}
} else {
testCodePath = getDevElectronPath();
electronPath = testCodePath;
process.env.VSCODE_REPOSITORY = repoPath;
process.env.VSCODE_DEV = '1';
process.env.VSCODE_CLI = '1';
}
if (!fs.existsSync(electronPath || '')) {
fail(`Can't find VSCode at ${electronPath}.`);
}
if (typeof stablePath === 'string' && !fs.existsSync(stablePath)) {
fail(`Can't find Stable VSCode at ${stablePath}.`);
}
if (process.env.VSCODE_DEV === '1') {
quality = Quality.Dev;
} else if (electronPath.indexOf('Code - Insiders') >= 0 /* macOS/Windows */ || electronPath.indexOf('code-insiders') /* Linux */ >= 0) {
quality = Quality.Insiders;
} else {
quality = Quality.Stable;
}
console.log(`Running desktop smoke tests against ${electronPath}`);
}
//
// #### Web Smoke Tests ####
//
else {
const testCodeServerPath = opts.build || process.env.VSCODE_REMOTE_SERVER_PATH;
if (typeof testCodeServerPath === 'string') {
if (!fs.existsSync(testCodeServerPath)) {
fail(`Can't find Code server at ${testCodeServerPath}.`);
} else {
console.log(`Running web smoke tests against ${testCodeServerPath}`);
}
}
if (!testCodeServerPath) {
process.env.VSCODE_REPOSITORY = repoPath;
process.env.VSCODE_DEV = '1';
process.env.VSCODE_CLI = '1';
console.log(`Running web smoke out of sources`);
}
if (process.env.VSCODE_DEV === '1') {
quality = Quality.Dev;
} else {
quality = Quality.Insiders;
}
}
const userDataDir = path.join(testDataPath, 'd');
async function setupRepository(): Promise<void> {
if (opts['test-repo']) {
console.log('*** Copying test project repository:', opts['test-repo']);
rimraf.sync(workspacePath);
// not platform friendly
if (process.platform === 'win32') {
cp.execSync(`xcopy /E "${opts['test-repo']}" "${workspacePath}"\\*`);
} else {
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 yarn...');
cp.execSync('yarn', { cwd: workspacePath, stdio: 'inherit' });
}
}
async function setup(): Promise<void> {
console.log('*** Test data:', testDataPath);
console.log('*** Preparing smoketest setup...');
await setupRepository();
console.log('*** Smoketest setup done!\n');
}
function createOptions(): ApplicationOptions {
const loggers: Logger[] = [];
if (opts.verbose) {
loggers.push(new ConsoleLogger());
}
let log: string | undefined = undefined;
if (opts.log) {
loggers.push(new FileLogger(opts.log));
log = 'trace';
}
return {
quality,
codePath: opts.build,
workspacePath,
userDataDir,
extensionsPath,
waitTime: parseInt(opts['wait-time'] || '0') || 20,
logger: new MultiLogger(loggers),
verbose: opts.verbose,
log,
screenshotsPath,
remote: opts.remote,
web: opts.web,
browser: opts.browser
};
}
before(async function () {
this.timeout(2 * 60 * 1000); // allow two minutes for setup
await setup();
this.defaultOptions = createOptions();
});
after(async function () {
await new Promise(c => setTimeout(c, 500)); // wait for shutdown
if (opts.log) {
const logsDir = path.join(userDataDir, 'logs');
const destLogsDir = path.join(path.dirname(opts.log), 'logs');
await new Promise((c, e) => ncp(logsDir, destLogsDir, err => err ? e(err) : c()));
}
await new Promise((c, e) => rimraf(testDataPath, { maxBusyTries: 10 }, err => err ? e(err) : c()));
});
describe(`VSCode Smoke Tests (${opts.web ? 'Web' : 'Electron'})`, () => {
if (screenshotsPath) {
afterEach(async function () {
if (this.currentTest.state !== 'failed') {
return;
}
const app = this.app as Application;
const name = this.currentTest.fullTitle().replace(/[^a-z0-9\-]/ig, '_');
await app.captureScreenshot(name);
});
}
if (opts.log) {
beforeEach(async function () {
const app = this.app as Application;
const title = this.currentTest.fullTitle();
app.logger.log('*** Test start:', title);
});
}
if (!opts.web && opts['stable-build']) {
describe(`Stable vs Insiders Smoke Tests: This test MUST run before releasing by providing the --stable-build command line argument`, () => {
setupDataMigrationTests(opts['stable-build'], testDataPath);
});
}
describe(`VSCode Smoke Tests (${opts.web ? 'Web' : 'Electron'})`, () => {
before(async function () {
const app = new Application(this.defaultOptions);
await app!.start(opts.web ? false : undefined);
this.app = app;
});
after(async function () {
await this.app.stop();
});
if (!opts.web) { setupDataLossTests(); }
if (!opts.web) { setupDataPreferencesTests(); }
setupDataSearchTests();
setupDataNotebookTests();
setupDataLanguagesTests();
setupDataEditorTests();
setupDataStatusbarTests(!!opts.web);
if (!opts.web) { setupDataExtensionTests(); }
if (!opts.web) { setupDataMultirootTests(); }
if (!opts.web) { setupDataLocalizationTests(); }
if (!opts.web) { setupLaunchTests(); }
});
});

View File

@ -0,0 +1,18 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ISuiteCallbackContext, ITestCallbackContext } from 'mocha';
export function describeRepeat(n: number, description: string, callback: (this: ISuiteCallbackContext) => void): void {
for (let i = 0; i < n; i++) {
describe(`${description} (iteration ${i})`, callback);
}
}
export function itRepeat(n: number, description: string, callback: (this: ITestCallbackContext, done: MochaDone) => any): void {
for (let i = 0; i < n; i++) {
it(`${description} (iteration ${i})`, callback);
}
}

View File

@ -0,0 +1,38 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const path = require('path');
const Mocha = require('mocha');
const minimist = require('minimist');
const [, , ...args] = process.argv;
const opts = minimist(args, {
boolean: 'web',
string: ['f', 'g']
});
const suite = opts['web'] ? 'Browser Smoke Tests' : 'Smoke Tests';
const options = {
color: true,
timeout: 60000,
slow: 30000,
grep: opts['f'] || opts['g']
};
if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) {
options.reporter = 'mocha-multi-reporters';
options.reporterOptions = {
reporterEnabled: 'spec, mocha-junit-reporter',
mochaJunitReporterReporterOptions: {
testsuitesTitle: `${suite} ${process.platform}`,
mochaFile: path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${process.arch}-${suite.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`)
}
};
}
const mocha = new Mocha(options);
mocha.addFile('out/main.js');
mocha.run(failures => process.exit(failures ? -1 : 0));

View File

@ -0,0 +1,21 @@
{
"compilerOptions": {
"module": "commonjs",
"noImplicitAny": false,
"removeComments": false,
"preserveConstEnums": true,
"target": "es2017",
"strictNullChecks": true,
"noUnusedParameters": false,
"noUnusedLocals": true,
"outDir": "out",
"sourceMap": true,
"lib": [
"es2016",
"dom"
]
},
"exclude": [
"node_modules"
]
}

File diff suppressed because it is too large Load Diff