提交 8973edf9 编写于 作者: M Michel Kaporin

Added smoke test source code.

上级 e81c07d7
.DS_Store
npm-debug.log
Thumbs.db
node_modules/
out/
keybindings.*.json
test_data/
\ No newline at end of file
{
// Use IntelliSense to learn about possible Node.js debug attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Mocha Tests",
"program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
"args": [
"-u",
"tdd",
"--timeout",
"999999",
"--colors",
"${workspaceRoot}/out/main.js"
],
"outFiles": [ "${workspaceRoot}/out/**/*.js" ],
"internalConsoleOptions": "openOnSessionStart",
"sourceMaps": true,
"cwd": "${workspaceRoot}"
}
]
}
\ No newline at end of file
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "0.1.0",
"command": "tsc",
"isShellCommand": true,
"args": ["-p", "."],
"showOutput": "silent",
"problemMatcher": "$tsc"
}
\ No newline at end of file
# Architecture
* `main.ts` contains the main smoke test suite. It includes all tests separated into mocha `describe()` groups that represent each of the areas of [Smoke Test document](https://github.com/Microsoft/vscode/wiki/Smoke-Test).
* `./areas/` folder contains a `.ts` file per each area of the document. E.g. `'Search'` area goes under `'search.ts'`. Every area file contains a list of methods with the name that represents the action that can be performed in the corresponding test. This reduces the amount of test suite code and means that if the UI changes, the fix need only be applied in one place. The name of the method reflects the action the tester would do if he would perform the test manually. See [Selenium Page Objects Wiki](https://github.com/SeleniumHQ/selenium/wiki/PageObjects) and [Selenium Bot Style Tests Wiki](https://github.com/SeleniumHQ/selenium/wiki/Bot-Style-Tests) for a good explanation of the implementation. Every smoke test area contains methods that are used in a bot-style approach in `main.ts`.
* `./spectron/` wraps the Spectron, with WebDriverIO API wrapped in `client.ts` and instance of Spectron Application is wrapped in `application.ts`.
* `./scripts/` contains scripts to run the smoke test.
# Adding new area
To contribute a new smoke test area, add `${area}.ts` file under `./areas`. This has to follow the bot-style approach described in the links mentioned above. Methods should be calling WebDriverIO API through `SpectronClient` class. If there is no existing WebDriverIO method, add it to the class.
# Adding new test
To add new test area or test, `main.ts` should be updated. The same instruction-style principle needs to be followed with the called area method names that reflect manual tester's actions.
\ No newline at end of file
# VS Code Smoke Testing
This repository contains the smoke test automation code with Spectron for Visual Studio Code.
The following command is used to run the tests: `.\scripts\run.ps1 -latest "path\to\Code.exe"` on Windows (from PowerShell) and `./scripts/run.sh path/to/binary` on Unix system.
If you want to include 'Data Migration' area tests use `.\scripts\run.ps1 -latest "path\to\Code.exe" -stable "path\to\CurrentStable.exe"` and `./scripts/run.sh path/to/binary path/to/currentStable` respectively.
# Contributing
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
{
"name": "code-oss-dev-smoke-test",
"version": "0.1.0",
"main": "./out/main.js",
"scripts": {
"test": "mocha -u tdd --timeout 360000 --retries 2 --slow 50000 --colors ./out/main.js 2> test_data/errors.log"
},
"devDependencies": {
"@types/mocha": "^2.2.41",
"@types/node": "^6.0.70",
"@types/webdriverio": "^4.6.1",
"@types/electron": "^1.4.37",
"@types/rimraf": "^0.0.28",
"mocha": "^3.2.0",
"spectron": "^3.6.4",
"typescript": "^2.2.2",
"rimraf": "^2.6.1"
}
}
Param(
[Parameter(Position=0,mandatory=$true)]
[string] $latest,
[Parameter(Position=1,mandatory=$false)]
[string] $stable
)
# Setup sample repository for the smoke test
Set-Location ..
if (-Not (Test-Path vscode-smoketest-express)) {
git clone https://github.com/Microsoft/vscode-smoketest-express.git
Set-Location ./vscode-smoketest-express
} else {
Set-Location ./vscode-smoketest-express
git fetch origin master
git reset --hard FETCH_HEAD
git clean -fd
}
npm install
# Setup the test directory for running
Set-Location ..\smoketest
if (-Not (Test-Path node_modules)) {
npm install
}
# Configure environment variables
$env:VSCODE_LATEST_PATH = $latest
$env:VSCODE_STABLE_PATH = $stable
$env:SMOKETEST_REPO = "..\vscode-smoketest-express"
if ($latest.Contains('Insiders')) {
$env:VSCODE_EDITION = 'insiders'
}
# Retrieve key bindings config file for Windows
$testDirectory = (Resolve-Path .\).Path
$client = New-Object System.Net.WebClient
$client.DownloadFile("https://raw.githubusercontent.com/Microsoft/vscode-docs/master/scripts/keybindings/doc.keybindings.win.json","$testDirectory\test_data\keybindings.win32.json")
# Compile and launch the smoke test
tsc
npm test
\ No newline at end of file
#!/usr/bin/env bash
if [[ "$#" -ne 1 ]]; then
echo "Usage: ./scripts/run.sh path/to/binary"
echo "To perform data migration tests, use: ./scripts/run.sh path/to/latest_binary path/to/stable_binary"
exit 1
fi
# Cloning sample repository for the smoke test
cd ..
if ! [ -d vscode-smoketest-express ]; then
git clone https://github.com/Microsoft/vscode-smoketest-express.git
cd vscode-smoketest-express
else
cd vscode-smoketest-express
git fetch origin master
git reset --hard FETCH_HEAD
git clean -fd
fi
npm install
# Install Node modules for Spectron
cd ../vscode-smoketest
test -d node_modules || npm install
# Configuration
export VSCODE_LATEST_PATH="$1"
export VSCODE_STABLE_PATH="$2"
export SMOKETEST_REPO="../vscode-smoketest-express"
mkdir -p test_data
if [[ $1 == *"Insiders"* || $1 == *"insiders"* ]]; then
export VSCODE_EDITION="insiders"
fi
if [[ "$OSTYPE" == "darwin"* ]]; then
curl "https://raw.githubusercontent.com/Microsoft/vscode-docs/master/scripts/keybindings/doc.keybindings.osx.json" -o "test_data/keybindings.darwin.json" # Download OS X keybindings
else
wget https://raw.githubusercontent.com/Microsoft/vscode-docs/master/scripts/keybindings/doc.keybindings.linux.json -O test_data/keybindings.linux.json # Download Linux keybindings
fi
# Compile and launch the smoke test
tsc
exec npm test
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SpectronApplication } from "../spectron/application";
import { Util } from '../helpers/utilities';
/**
* Contains methods that are commonly used across test areas.
*/
export class CommonActions {
private util: Util;
constructor(private spectron: SpectronApplication) {
this.util = new Util();
}
public async getWindowTitle(): Promise<any> {
return this.spectron.client.getTitle();
}
public enter(): Promise<any> {
return this.spectron.client.keys(['Enter', 'NULL']);
}
public async addSetting(setting: string, value: string): Promise<any> {
await this.spectron.command('workbench.action.openGlobalSettings');
await this.spectron.wait();
await this.spectron.client.leftClick('.editable-preferences-editor-container .view-lines', 1, 1, false);
await this.spectron.client.keys(['ArrowDown', 'NULL', 'ArrowDown', 'NULL'], false);
await this.spectron.wait();
await this.spectron.client.keys(`"${setting}": "${value}"`);
await this.spectron.wait();
return this.saveOpenedFile();
}
public async newUntitledFile(): Promise<any> {
await this.spectron.command('workbench.action.files.newUntitledFile');
return this.spectron.wait();
}
public closeTab(): Promise<any> {
return this.spectron.client.keys(['Control', 'w', 'NULL']);
}
public async getTab(tabName: string, active?: boolean): Promise<any> {
let tabSelector = active ? '.tab.active' : 'div';
let el = await this.spectron.client.element(`.tabs-container ${tabSelector}[aria-label="${tabName}, tab"]`);
if (el.status === 0) {
return el;
}
return undefined;
}
public selectTab(tabName: string): Promise<any> {
return this.spectron.client.click(`.tabs-container div[aria-label="${tabName}, tab"]`);
}
public async openFirstMatchFile(fileName: string): Promise<any> {
await this.openQuickOpen();
await this.type(fileName);
await this.spectron.wait();
await this.enter();
return this.spectron.wait();
}
public saveOpenedFile(): Promise<any> {
return this.spectron.command('workbench.action.files.save');
}
public type(text: string): Promise<any> {
let spectron = this.spectron;
return new Promise(function (res) {
let textSplit = text.split(' ');
async function type(i: number) {
if (!textSplit[i] || textSplit[i].length <= 0) {
return res();
}
const toType = textSplit[i + 1] ? `${textSplit[i]} ` : textSplit[i];
await spectron.client.keys(toType, false);
await spectron.client.keys(['NULL']);
await type(i + 1);
}
return type(0);
});
}
public showCommands(): Promise<any> {
return this.spectron.command('workbench.action.showCommands');
}
public openQuickOpen(): Promise<any> {
return this.spectron.command('workbench.action.quickOpen');
}
public closeQuickOpen(): Promise<any> {
return this.spectron.command('workbench.action.closeQuickOpen');
}
public selectNextQuickOpenElement(): Promise<any> {
return this.spectron.client.keys(['ArrowDown', 'NULL']);
}
public async getQuickOpenElements(): Promise<number> {
const elements = await this.spectron.waitFor(this.spectron.client.elements, 'div[aria-label="Quick Picker"] .monaco-tree-rows.show-twisties .monaco-tree-row');
return elements.value.length;
}
public async openFile(fileName: string, explorer?: boolean): Promise<any> {
let selector = `div[class="monaco-icon-label file-icon ${fileName}-name-file-icon ${this.getExtensionSelector(fileName)}`;
if (explorer) {
selector += ' explorer-item';
}
selector += '"]';
await this.spectron.waitFor(this.spectron.client.doubleClick, selector);
return this.spectron.wait();
}
public getExtensionSelector(fileName: string): string {
const extension = fileName.split('.')[1];
if (extension === 'js') {
return 'js-ext-file-icon javascript-lang-file-icon';
} else if (extension === 'json') {
return 'json-ext-file-icon json-lang-file-icon';
} else if (extension === 'md') {
return 'md-ext-file-icon markdown-lang-file-icon';
}
throw new Error('No class defined for this file extension');
}
public async getEditorFirstLinePlainText(): Promise<any> {
try {
const span = await this.spectron.client.getText('.view-lines span span');
if (Array.isArray(span)) {
return span[0];
}
return span;
} catch (e) {
return undefined;
}
}
public removeFile(filePath: string): void {
this.util.removeFile(filePath);
}
public removeDirectory(directory: string): Promise<any> {
try {
return this.util.rimraf(directory);
} catch (e) {
throw new Error(`Failed to remove ${directory} with an error: ${e}`);
}
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SpectronApplication } from '../spectron/application';
export enum ActivityBarPosition {
LEFT = 0,
RIGHT = 1
};
export class ConfigurationView {
// Stores key binding defined for the toggle of activity bar position
private keybinding: string[];
constructor(private spectron: SpectronApplication) {
// noop
}
public getEditorLineNumbers(): any {
return this.spectron.client.elements('.line-numbers');
}
public enterKeybindingsView(): any {
return this.spectron.command('workbench.action.openGlobalKeybindings');
}
public selectFirstKeybindingsMatch(): any {
return this.spectron.waitFor(this.spectron.client.click, 'div[aria-label="Keybindings"] .monaco-list-row.keybinding-item');
}
public changeKeybinding(): any {
return this.spectron.command('editor.action.defineKeybinding');
}
public enterBinding(keys: string[]): any {
this.keybinding = keys;
return this.spectron.client.keys(keys);
}
public toggleActivityBarPosition(): any {
return this.spectron.client.keys(this.keybinding);
}
public async getActivityBar(position: ActivityBarPosition) {
let positionClass: string;
if (position === ActivityBarPosition.LEFT) {
positionClass = 'left';
} else if (position === ActivityBarPosition.RIGHT) {
positionClass = 'right';
} else {
throw new Error('No such position for activity bar defined.');
}
try {
return await this.spectron.waitFor(this.spectron.client.getHTML, `.part.activitybar.${positionClass}`);
} catch (e) {
return undefined;
};
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SpectronApplication } from '../spectron/application';
export enum CSSProblem {
WARNING = 0,
ERROR = 1
};
export class CSS {
constructor(private spectron: SpectronApplication) {
// noop
}
public openQuickOutline(): any {
return this.spectron.command('workbench.action.gotoSymbol');
}
public toggleProblemsView(): any {
return this.spectron.command('workbench.actions.view.problems');
}
public async getEditorProblem(problemType: CSSProblem): Promise<any> {
let selector;
if (problemType === CSSProblem.WARNING) {
selector = 'greensquiggly';
} else if (problemType === CSSProblem.ERROR) {
selector = 'redsquiggly';
} else {
throw new Error('No such problem type defined.');
}
let el = await this.spectron.client.element(`.view-overlays .cdr.${selector}`);
if (el.status === 0) {
return el;
}
return undefined;
}
public async getProblemsViewsProblem(problemType: CSSProblem): Promise<any> {
let selector;
if (problemType === CSSProblem.WARNING) {
selector = 'warning';
} else if (problemType === CSSProblem.ERROR) {
selector = 'error';
} else {
throw new Error('No such problem type defined.');
}
let el = await this.spectron.client.element(`div[aria-label="Problems grouped by files"] .icon.${selector}`);
if (el.status === 0) {
return el;
}
return undefined;
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SpectronApplication } from "../spectron/application";
export class DataLoss {
constructor(private spectron: SpectronApplication) {
}
public openExplorerViewlet(): Promise<any> {
return this.spectron.command('workbench.view.explorer');
}
public async verifyTabIsDirty(tabName: string, active?: boolean): Promise<any> {
let activeSelector = active ? '.active' : '';
let el = await this.spectron.client.element(`.tabs-container .tab.dirty${activeSelector}[aria-label="${tabName}, tab"]`);
if (el.status === 0) {
return el;
}
return undefined;
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SpectronApplication } from '../spectron/application';
import { CommonActions } from "./common";
export class Extensions {
private readonly extensionsViewletSelector = 'div[id="workbench.view.extensions"]';
constructor(private spectron: SpectronApplication, private common: CommonActions) {
}
public async openExtensionsViewlet(): Promise<any> {
await this.spectron.command('workbench.view.extensions');
return this.spectron.wait();
}
public async searchForExtension(name: string): Promise<any> {
const searchBoxSelector = `${this.extensionsViewletSelector} .search-box`;
await this.spectron.client.clearElement(searchBoxSelector);
await this.spectron.client.click(searchBoxSelector, false);
await this.spectron.client.keys(name);
return this.spectron.client.keys(['NULL', 'Enter', 'NULL']);
}
public installFirstResult(): Promise<any> {
return this.spectron.client.click(`${this.extensionsViewletSelector} .monaco-list-rows>:nth-child(1) .extension .extension-action.install`);
}
public getFirstReloadText(): Promise<any> {
return this.spectron.waitFor(this.spectron.client.getText, `${this.extensionsViewletSelector} .monaco-list-rows>:nth-child(1) .extension .extension-action.reload`);
}
public async selectMinimalIconsTheme(): Promise<any> {
await this.common.showCommands();
await this.common.type('File Icon Theme');
await this.spectron.wait();
await this.common.enter();
return this.spectron.client.keys(['ArrowDown', 'NULL', 'Enter', 'NULL']);
}
public async verifyFolderIconAppearance(): Promise<any> {
return this.spectron.waitFor(this.spectron.client.getHTML, 'style[class="contributedIconTheme"]');
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SpectronApplication } from "../spectron/application";
export class FirstExperience {
constructor(private spectron: SpectronApplication) {
// noop
}
public async getWelcomeTab(): Promise<any> {
let el = await this.spectron.client.element('.vs_code_welcome_page-name-file-icon');
if (el.status === 0) {
return el;
}
return undefined;
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SpectronApplication } from '../spectron/application';
import { CommonActions } from "./common";
export class Git {
private readonly bodyVarSelector = '.view-lines>:nth-child(6) .mtk11';
constructor(private spectron: SpectronApplication, private commonActions: CommonActions) {
// noop
}
public openGitViewlet(): Promise<any> {
return this.spectron.command('workbench.view.git');
}
public getScmIconChanges(): Promise<any> {
return this.spectron.waitFor(this.spectron.client.getText, 'div[id="workbench.parts.activitybar"] .badge.scm-viewlet-label .badge-content');
}
public async verifyScmChange(fileName: string): Promise<any> {
let el = await this.spectron.client.element(`div[class="monaco-icon-label file-icon ${fileName}-name-file-icon ${this.commonActions.getExtensionSelector(fileName)}"]`);
if (el.status === 0) {
return el;
}
return undefined;
}
public getOriginalAppJsBodyVarName(): Promise<any> {
return this.spectron.waitFor(this.spectron.client.getText, `.editor.original ${this.bodyVarSelector}`);
}
public getModifiedAppJsBodyVarName(): Promise<any> {
return this.spectron.waitFor(this.spectron.client.getText, `.editor.modified ${this.bodyVarSelector}`);
}
public async stageFile(fileName: string): Promise<any> {
await this.spectron.client.moveToObject(`div[class="monaco-icon-label file-icon ${fileName}-name-file-icon ${this.commonActions.getExtensionSelector(fileName)}"`);
await this.spectron.client.click('.action-label.icon.contrib-cmd-icon-4');
return this.spectron.wait();
}
public async unstageFile(fileName: string): Promise<any> {
await this.spectron.client.moveToObject(`div[class="monaco-icon-label file-icon ${fileName}-name-file-icon ${this.commonActions.getExtensionSelector(fileName)}"`);
await this.spectron.client.click('.action-label.icon.contrib-cmd-icon-6');
return this.spectron.wait();
}
public getStagedCount(): Promise<any> {
return this.spectron.waitFor(this.spectron.client.getText, '.scm-status.show-file-icons .monaco-list-rows>:nth-child(1) .monaco-count-badge');
}
public focusOnCommitBox(): Promise<any> {
return this.spectron.client.click('div[id="workbench.view.scm"] textarea');
}
public async pressCommit(): Promise<any> {
await this.spectron.client.click('.action-label.icon.contrib-cmd-icon-10');
return this.spectron.wait();
}
public getOutgoingChanges(): Promise<string> {
return this.spectron.client.getText('a[title="Synchronize changes"]');
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SpectronApplication } from '../spectron/application';
import { CommonActions } from "./common";
export class IntegratedTerminal {
constructor(private spectron: SpectronApplication) {
// noop
}
public async openTerminal(commonActions: CommonActions): Promise<any> {
// Backquote dispatching does not work in OS X
if (process.platform === 'darwin') {
await commonActions.showCommands();
await commonActions.type('Toggle Integrated Terminal');
return commonActions.enter();
}
return this.spectron.command('workbench.action.terminal.toggleTerminal');
}
public async getCommandOutput(command: string): Promise<string> {
const selector = 'div[id="workbench.panel.terminal"] .xterm-rows';
let readRow = process.platform === 'win32' ? 5 : 2;
let output: string = await this.spectron.client.getText(`${selector}>:nth-child(${readRow})`);
// If ended up on the wrong line, it could be terminal's restored session (e.g. on OS X)
if (output.trim().endsWith(command)) {
output = await this.spectron.client.getText(`${selector}>:nth-child(${readRow+1})`); // try next line
}
return output.trim(); // remove many &nbsp; tags
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SpectronApplication } from '../spectron/application';
export class JavaScriptDebug {
private readonly sidebarSelector = '.margin-view-overlays';
constructor(private spectron: SpectronApplication) {
// noop
}
public openDebugViewlet(): Promise<any> {
return this.spectron.command('workbench.view.debug');
}
public async pressConfigureLaunchJson(): Promise<any> {
await this.spectron.waitFor(this.spectron.client.click, 'ul[aria-label="Debug actions"] .action-label.icon.debug-action.configure');
await this.spectron.wait();
await this.spectron.client.keys(['ArrowDown', 'NULL', 'Enter']);
return this.spectron.wait();
}
public getProgramConfigValue(): Promise<any> {
return this.spectron.client.getText('.view-lines>:nth-child(11) .mtk7');
}
public setBreakpointOnLine(lineNumber: number): Promise<any> {
return this.spectron.client.leftClick(`${this.sidebarSelector}>:nth-child(${lineNumber})`, 5, 5);
}
public async verifyBreakpointOnLine(lineNumber: number): Promise<any> {
let el = await this.spectron.client.element(`${this.sidebarSelector}>:nth-child(${lineNumber}) .cgmr.debug-breakpoint-glyph`);
if (el.status === 0) {
return el;
}
return undefined;
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SpectronApplication } from '../spectron/application';
export class JavaScript {
private readonly appVarSelector = '.view-lines>:nth-child(7) .mtk11';
private readonly firstCommentSelector = '.margin-view-overlays>:nth-child(3)';
private readonly expressVarSelector = '.view-lines>:nth-child(11) .mtk10';
constructor(private spectron: SpectronApplication) {
// noop
}
public openQuickOutline(): Promise<any> {
return this.spectron.command('workbench.action.gotoSymbol');
}
public async findAppReferences(): Promise<any> {
await this.spectron.client.click(this.appVarSelector, false);
return this.spectron.command('editor.action.referenceSearch.trigger');
}
public async getTitleReferencesCount(): Promise<any> {
const meta = await this.spectron.client.getText('.reference-zone-widget.results-loaded .peekview-title .meta');
return meta.match(/\d+/)[0];
}
public async getTreeReferencesCount(): Promise<any> {
const treeElems = await this.spectron.client.elements('.reference-zone-widget.results-loaded .ref-tree.inline .show-twisties .monaco-tree-row');
return treeElems.value.length;
}
public async renameApp(newValue: string): Promise<any> {
await this.spectron.client.click(this.appVarSelector);
await this.spectron.command('editor.action.rename');
await this.spectron.wait();
return this.spectron.client.keys(newValue, false);
}
public async getNewAppName(): Promise<any> {
return this.spectron.client.getText(this.appVarSelector);
}
public async toggleFirstCommentFold(): Promise<any> {
return this.spectron.client.click(`${this.firstCommentSelector} .cldr.folding`);
}
public async getFirstCommentFoldedIcon(): Promise<any> {
return this.spectron.client.getHTML(`${this.firstCommentSelector} .cldr.folding.collapsed`);
}
public async getNextLineNumberAfterFold(): Promise<any> {
return this.spectron.client.getText(`.margin-view-overlays>:nth-child(4) .line-numbers`)
}
public async goToExpressDefinition(): Promise<any> {
await this.spectron.client.click(this.expressVarSelector);
return this.spectron.command('editor.action.goToDeclaration');
}
public async peekExpressDefinition(): Promise<any> {
await this.spectron.client.click(this.expressVarSelector);
return this.spectron.command('editor.action.previewDeclaration');
}
public async getPeekExpressResultName(): Promise<any> {
return this.spectron.client.getText('.reference-zone-widget.results-loaded .filename');
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SpectronApplication } from '../spectron/application';
export enum ViewletType {
SEARCH = 0,
SCM = 1,
DEBUG = 2,
EXTENSIONS = 3
}
export class Localization {
constructor(private spectron: SpectronApplication) {
// noop
}
public async getOpenEditorsText(): Promise<string> {
const explorerTitles = await this.spectron.client.getText('div[id="workbench.view.explorer"] .title span');
return explorerTitles[0];
}
public openViewlet(type: ViewletType): Promise<any> {
let command;
switch (type) {
case ViewletType.SEARCH:
command = 'workbench.view.search';
break;
case ViewletType.SCM:
command = 'workbench.view.scm';
break;
case ViewletType.DEBUG:
command = 'workbench.view.debug';
break;
case ViewletType.EXTENSIONS:
command = 'workbench.view.extensions';
break;
}
return this.spectron.command(command, false);
}
public getOpenedViewletTitle(): Promise<string> {
return this.spectron.client.getText('div[id="workbench.parts.sidebar"] .title-label span');
}
public getExtensionsSearchPlaceholder(): Promise<string> {
return this.spectron.client.getAttribute('div[id="workbench.view.extensions"] .search-box', 'placeholder');
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SpectronApplication } from '../spectron/application';
export class Search {
constructor(private spectron: SpectronApplication) {
// noop
}
public openSearchViewlet(): Promise<any> {
return this.spectron.command('workbench.view.search');
}
public async searchFor(text: string): Promise<any> {
await this.spectron.client.keys(text);
return this.spectron.client.keys(['NULL', 'Enter', 'NULL'], false);
}
public setReplaceText(text: string): any {
return this.spectron.client.setValue('.viewlet .input[title="Replace"]', text);
}
public replaceFirstMatch(): any {
return this.spectron.client.click('.monaco-tree-rows.show-twisties .action-label.icon.action-replace-all');
}
public getResultText(): any {
return this.spectron.waitFor(this.spectron.client.getText, '.search-viewlet .message>p');
}
public toggleSearchDetails(): any {
return this.spectron.client.click('.query-details .more');
}
public toggleReplace(): any {
return this.spectron.client.click('.monaco-button.toggle-replace-button.collapse');
}
public hoverOverResultCount(): any {
return this.spectron.waitFor(this.spectron.client.moveToObject, '.monaco-count-badge');
}
public dismissResult(): any {
return this.spectron.client.click('.action-label.icon.action-remove')
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SpectronApplication } from '../spectron/application';
export enum StatusBarElement {
BRANCH_STATUS = 0,
SYNC_STATUS = 1,
PROBLEMS_STATUS = 2,
SELECTION_STATUS = 3,
INDENTATION_STATUS = 4,
ENCODING_STATUS = 5,
EOL_STATUS = 6,
LANGUAGE_STATUS = 7,
FEEDBACK_ICON = 8
}
export class StatusBar {
private selectorsMap: Map<StatusBarElement, string>;
private readonly mainSelector = 'div[id="workbench.parts.statusbar"]';
constructor(private spectron: SpectronApplication) {
this.populateSelectorsMap();
}
public async isVisible(element: StatusBarElement): Promise<boolean> {
const selector = this.selectorsMap.get(element);
if (!selector) {
throw new Error('No such element in the status bar defined.');
}
return this.spectron.client.isVisible(selector);
}
public async clickOn(element: StatusBarElement): Promise<any> {
const selector = this.selectorsMap.get(element);
if (!selector) {
throw new Error('No such element in the status bar defined.');
}
return this.spectron.client.click(selector);
}
public async getProblemsView(): Promise<any> {
let el = await this.spectron.client.element('div[id="workbench.panel.markers"]');
if (el.status === 0) {
return el;
}
return undefined;
}
public async getFeedbackView(): Promise<any> {
let el = await this.spectron.client.element('.feedback-form');
if (el.status === 0) {
return el;
}
return undefined;
}
public isQuickOpenWidgetVisible(): Promise<any> {
return this.spectron.client.isVisible('.quick-open-widget');
}
public async getEditorHighlightedLine(lineNumber: number): Promise<any> {
let el = await this.spectron.client.element(`.monaco-editor .view-overlays>:nth-child(${lineNumber}) .current-line`);
if (el.status === 0) {
return el;
}
return undefined;
}
public async getEOLMode(): Promise<any> {
const selector = this.selectorsMap.get(StatusBarElement.EOL_STATUS);
if (!selector) {
throw new Error('No such element in the status bar defined.');
}
return this.spectron.client.getText(selector);
}
private populateSelectorsMap(): void {
this.selectorsMap = new Map<StatusBarElement, string>();
this.selectorsMap.set(StatusBarElement.BRANCH_STATUS, `${this.mainSelector} .octicon.octicon-git-branch`);
this.selectorsMap.set(StatusBarElement.SYNC_STATUS, `${this.mainSelector} .octicon.octicon-sync`);
this.selectorsMap.set(StatusBarElement.PROBLEMS_STATUS, `${this.mainSelector} .task-statusbar-item[title="Problems"]`);
this.selectorsMap.set(StatusBarElement.SELECTION_STATUS, `${this.mainSelector} .editor-status-selection`);
this.selectorsMap.set(StatusBarElement.INDENTATION_STATUS, `${this.mainSelector} .editor-status-indentation`);
this.selectorsMap.set(StatusBarElement.ENCODING_STATUS, `${this.mainSelector} .editor-status-encoding`);
this.selectorsMap.set(StatusBarElement.EOL_STATUS, `${this.mainSelector} .editor-status-eol`);
this.selectorsMap.set(StatusBarElement.LANGUAGE_STATUS, `${this.mainSelector} .editor-status-mode`);
this.selectorsMap.set(StatusBarElement.FEEDBACK_ICON, `${this.mainSelector} .dropdown.send-feedback`);
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SpectronApplication } from '../spectron/application';
export class Tasks {
private readonly outputViewSelector = 'div[id="workbench.panel.output"] .view-lines';
private readonly workbenchPanelSelector = 'div[id="workbench.parts.panel"]';
private readonly problemsViewSelector = 'div[id="workbench.panel.markers"] .monaco-tree-row.expanded';
constructor(private spectron: SpectronApplication) {
// noop
}
public build(): Promise<any> {
return this.spectron.command('workbench.action.tasks.build');
}
public openProblemsView(): Promise<any> {
return this.spectron.command('workbench.actions.view.problems');
}
public async firstOutputLineEndsWith(fileName: string): Promise<boolean> {
const firstLine = await this.spectron.waitFor(this.spectron.client.getText, `${this.outputViewSelector}>:nth-child(2)`);
return firstLine.endsWith(fileName);
}
public getOutputResult(): Promise<any> {
return this.spectron.waitFor(this.spectron.client.getText, `${this.outputViewSelector}>:nth-child(10) span.mtk1`);
}
public selectOutputViewType(type: string): Promise<any> {
return this.spectron.client.selectByValue(`${this.workbenchPanelSelector} .select-box`, type);
}
public getOutputViewType(): Promise<any> {
return this.spectron.client.getValue(`${this.workbenchPanelSelector} .select-box`);
}
public getProblemsViewFirstElementName(): Promise<any> {
return this.spectron.waitFor(this.spectron.client.getText, `${this.problemsViewSelector} .label-name`);
}
public getProblemsViewFirstElementCount(): Promise<any> {
return this.spectron.waitFor(this.spectron.client.getText, `${this.problemsViewSelector} .monaco-count-badge`);
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SpectronApplication } from "../spectron/application";
var fs = require('fs');
const __testTime = new Date().toISOString();
export class Screenshot {
private index: number = 0;
private testPath: string;
constructor(private spectron: SpectronApplication, testName: string) {
const testTime = this.sanitizeFolderName(__testTime);
testName = this.sanitizeFolderName(testName);
this.testPath = `test_data/screenshots/${testTime}/${testName}`;
this.createFolder(this.testPath);
}
public capture(): Promise<any> {
return new Promise(async (res, rej) => {
const image: Electron.NativeImage = await this.spectron.app.browserWindow.capturePage();
fs.writeFile(`${this.testPath}/${this.index}.png`, image, (err) => {
if (err) {
rej(err);
}
});
this.index++;
res();
});
}
private createFolder(name: string) {
name.split('/').forEach((folderName, i, fullPath) => {
const folder = fullPath.slice(0, i + 1).join('/');
if (!fs.existsSync(folder)) {
fs.mkdirSync(folder);
}
});
}
private sanitizeFolderName(name: string): string {
return name.replace(/[&*:\/]/g, '');
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var fs = require('fs');
var rimraf = require('rimraf');
/**
* Contains methods that are commonly used across test areas.
*/
export class Util {
constructor() {
// noop
}
public removeFile(filePath: string): void {
try {
fs.unlinkSync(`${filePath}`);
} catch (e) {
if (e.code !== 'ENOENT') {
throw e;
}
}
}
public rimraf(directory: string): Promise<any> {
return new Promise((res, rej) => {
rimraf(directory, (err) => {
if (err) {
rej(err);
}
res();
});
});
}
}
\ No newline at end of file
此差异已折叠。
/*---------------------------------------------------------------------------------------------
* 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 'spectron';
import { SpectronClient } from './client';
import { Screenshot } from "../helpers/screenshot";
var fs = require('fs');
/**
* Wraps Spectron's Application instance with its used methods.
*/
export class SpectronApplication {
public client: SpectronClient;
private spectron: Application;
private readonly pollTrials = 5;
private readonly pollTimeout = 3; // in secs
private keybindings: any[];
private screenshot: Screenshot;
constructor(electronPath: string, testName: string, private testRetry: number, args?: string[], chromeDriverArgs?: string[]) {
if (!args) {
args = [];
}
this.spectron = new Application({
path: electronPath,
args: args.concat(['--skip-getting-started']), // prevent 'Getting Started' web page from opening on clean user-data-dir
chromeDriverArgs: chromeDriverArgs
});
this.screenshot = new Screenshot(this, testName);
this.client = new SpectronClient(this.spectron, this.screenshot);
this.testRetry += 1; // avoid multiplication by 0 for wait times
this.retrieveKeybindings();
}
public get app(): Application {
return this.spectron;
}
public async start(): Promise<any> {
try {
await this.spectron.start();
await this.focusOnWindow(1); // focuses on main renderer window
return this.checkWindowReady();
} catch (err) {
throw err;
}
}
public async stop(): Promise<any> {
if (this.spectron && this.spectron.isRunning()) {
return await this.spectron.stop();
}
}
public waitFor(func: (...args: any[]) => any, args: any): Promise<any> {
return this.callClientAPI(func, args, 0);
}
public wait(): Promise<any> {
return new Promise(resolve => setTimeout(resolve, this.testRetry * this.pollTimeout * 1000));
}
public focusOnWindow(index: number): Promise<any> {
return this.client.windowByIndex(index);
}
private checkWindowReady(): Promise<any> {
return this.waitFor(this.spectron.client.getHTML, '[id="workbench.main.container"]');
}
private retrieveKeybindings() {
const os = process.platform;
fs.readFile(`test_data/keybindings.${os}.json`, (err, data) => {
if (err) {
throw err;
}
this.keybindings = JSON.parse(data);
});
}
private callClientAPI(func: (...args: any[]) => Promise<any>, args: any, trial: number): Promise<any> {
if (trial > this.pollTrials) {
return Promise.reject(`Could not retrieve the element in ${this.testRetry * this.pollTrials * this.pollTimeout} seconds.`);
}
return new Promise(async (res, rej) => {
let resolved = false, capture = false;
const tryCall = async (resolve: any, reject: any): Promise<any> => {
await this.wait();
try {
const result = await this.callClientAPI(func, args, ++trial);
res(result);
} catch (error) {
rej(error);
}
}
try {
const result = await func.call(this.client, args, capture);
if (!resolved && result === '') {
resolved = true;
await tryCall(res, rej);
} else if (!resolved) {
resolved = true;
await this.screenshot.capture();
res(result);
}
} catch (e) {
if (!resolved) {
resolved = true;
await tryCall(res, rej);
}
}
});
}
/**
* Retrieves the command from keybindings file and executes it with WebdriverIO client API
* @param command command (e.g. 'workbench.action.files.newUntitledFile')
*/
public command(command: string, capture?: boolean): Promise<any> {
const binding = this.keybindings.find(x => x['command'] == command);
const keys: string = binding.key;
let keysToPress: string[] = [];
const chords = keys.split(' ');
chords.forEach((chord) => {
const keys = chord.split('+');
keys.forEach((key) => keysToPress.push(this.transliterate(key)));
keysToPress.push('NULL');
});
return this.client.keys(keysToPress, capture);
}
/**
* Transliterates key names from keybindings file to WebdriverIO keyboard actions defined in:
* https://w3c.github.io/webdriver/webdriver-spec.html#keyboard-actions
*/
private transliterate(key: string): string {
switch (key) {
case 'ctrl':
return 'Control';
case 'cmd':
return 'Meta';
default:
return key.length === 1 ? key : key.charAt(0).toUpperCase() + key.slice(1);
};
}
}
/*---------------------------------------------------------------------------------------------
* 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 'spectron';
import { Screenshot } from '../helpers/screenshot';
/**
* Abstracts the Spectron's WebdriverIO managed client property on the created Application instances.
*/
export class SpectronClient {
constructor(private spectron: Application, private shot: Screenshot) {
// noop
}
public windowByIndex(index: number): Promise<any> {
return this.spectron.client.windowByIndex(index);
}
public async keys(keys: string[] | string, capture: boolean = true): Promise<any> {
await this.execute(capture);
return this.spectron.client.keys(keys);
}
public async getText(selector: string, capture: boolean = true): Promise<any> {
await this.execute(capture);
return this.spectron.client.getText(selector);
}
public async getHTML(selector: string, capture: boolean = true): Promise<any> {
await this.execute(capture);
return this.spectron.client.getHTML(selector);
}
public async click(selector: string, capture: boolean = true): Promise<any> {
await this.execute(capture);
return this.spectron.client.click(selector);
}
public async doubleClick(selector: string, capture: boolean = true): Promise<any> {
await this.execute(capture);
return this.spectron.client.doubleClick(selector);
}
public async leftClick(selector: string, xoffset: number, yoffset: number, capture: boolean = true): Promise<any> {
await this.execute(capture);
return this.spectron.client.leftClick(selector, xoffset, yoffset);
}
public async rightClick(selector: string, capture: boolean = true): Promise<any> {
await this.execute(capture);
return this.spectron.client.rightClick(selector);
}
public async moveToObject(selector: string, capture: boolean = true): Promise<any> {
await this.execute(capture);
return this.spectron.client.moveToObject(selector);
}
public async setValue(selector: string, text: string, capture: boolean = true): Promise<any> {
await this.execute(capture);
return this.spectron.client.setValue(selector, text);
}
public async elements(selector: string, capture: boolean = true): Promise<any> {
await this.execute(capture);
return this.spectron.client.elements(selector);
}
public async element(selector: string, capture: boolean = true): Promise<any> {
await this.execute(capture);
return this.spectron.client.element(selector);
}
public async dragAndDrop(sourceElem: string, destinationElem: string, capture: boolean = true): Promise<any> {
await this.execute(capture);
return this.spectron.client.dragAndDrop(sourceElem, destinationElem);
}
public async selectByValue(selector: string, value: string, capture: boolean = true): Promise<any> {
await this.execute(capture);
return this.spectron.client.selectByValue(selector, value);
}
public async getValue(selector: string, capture: boolean = true): Promise<any> {
await this.execute(capture);
return this.spectron.client.getValue(selector);
}
public async getAttribute(selector: string, attribute: string, capture: boolean = true): Promise<any> {
await this.execute(capture);
return Promise.resolve(this.spectron.client.getAttribute(selector, attribute));
}
public clearElement(selector: string): any {
return this.spectron.client.clearElement(selector);
}
public buttonDown(): any {
return this.spectron.client.buttonDown();
}
public buttonUp(): any {
return this.spectron.client.buttonUp();
}
public async isVisible(selector: string, capture: boolean = true): Promise<any> {
await this.execute(capture);
return this.spectron.client.isVisible(selector);
}
public getTitle(): string {
return this.spectron.client.getTitle();
}
private async execute(capture: boolean): Promise<any> {
if (capture) {
try {
await this.shot.capture();
} catch (e) {
throw new Error(`Screenshot could not be captured: ${e}`);
}
}
}
}
\ No newline at end of file
{
"compilerOptions": {
"module": "commonjs",
"noImplicitAny": false,
"removeComments": false,
"preserveConstEnums": true,
"target": "es2016",
"strictNullChecks": true,
"noUnusedParameters": false,
"noUnusedLocals": true,
"outDir": "out",
"sourceMap": true,
"lib": [
"es2016",
"dom"
]
},
"exclude": [
"node_modules"
]
}
\ No newline at end of file
vscode-smoketest-express @ 636dd7a2
Subproject commit 636dd7a2ff71d266c0e015411b47cf5c3a25be5a
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册