application.ts 5.2 KB
Newer Older
M
Michel Kaporin 已提交
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  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';
8
import { Screenshot } from '../helpers/screenshot';
M
Michel Kaporin 已提交
9
var fs = require('fs');
10
var path = require('path');
M
Michel Kaporin 已提交
11

J
Joao 已提交
12
export const LATEST_PATH = process.env.VSCODE_PATH || '';
J
Joao 已提交
13 14 15
export const STABLE_PATH = process.env.VSCODE_STABLE_PATH || '';
export const WORKSPACE_PATH = process.env.SMOKETEST_REPO || '';
export const CODE_WORKSPACE_PATH = process.env.VSCODE_WORKSPACE_PATH || '';
M
Michel Kaporin 已提交
16 17 18
export const USER_DIR = 'test_data/temp_user_dir';
export const EXTENSIONS_DIR = 'test_data/temp_extensions_dir';

M
Michel Kaporin 已提交
19 20 21 22 23 24 25 26 27 28
/**
 * Wraps Spectron's Application instance with its used methods.
 */
export class SpectronApplication {
	public client: SpectronClient;

	private spectron: Application;
	private keybindings: any[];
	private screenshot: Screenshot;

M
Michel Kaporin 已提交
29
	private readonly sampleExtensionsDir: string = 'test_data/sample_extensions_dir';
30 31
	private readonly pollTrials = 50;
	private readonly pollTimeout = 1; // in secs
M
Michel Kaporin 已提交
32

M
Michel Kaporin 已提交
33 34 35 36 37
	constructor(electronPath: string, testName: string, private testRetry: number, args?: string[], chromeDriverArgs?: string[]) {
		if (!args) {
			args = [];
		}

M
Michel Kaporin 已提交
38 39 40 41 42 43 44 45
		// Prevent 'Getting Started' web page from opening on clean user-data-dir
		args.push('--skip-getting-started');

		// Ensure that running over custom extensions directory, rather than picking up the one that was used by a tester previously
		let extensionDirIsSet = false;
		for (let arg of args) {
			if (arg.startsWith('--extensions-dir')) {
				extensionDirIsSet = true;
46
				break;
M
Michel Kaporin 已提交
47 48 49 50 51 52
			}
		}
		if (!extensionDirIsSet) {
			args.push(`--extensions-dir=${this.sampleExtensionsDir}`);
		}

J
Joao 已提交
53 54 55 56 57
		const repo = process.env.VSCODE_REPOSITORY;
		if (repo) {
			args = [repo, ...args];
		}

M
Michel Kaporin 已提交
58 59
		this.spectron = new Application({
			path: electronPath,
M
Michel Kaporin 已提交
60
			args: args,
M
Michel Kaporin 已提交
61
			chromeDriverArgs: chromeDriverArgs,
J
Joao 已提交
62 63
			startTimeout: 10000,
			requireName: 'nodeRequire'
M
Michel Kaporin 已提交
64 65
		});
		this.testRetry += 1; // avoid multiplication by 0 for wait times
66 67
		this.screenshot = new Screenshot(this, testName, testRetry);
		this.client = new SpectronClient(this.spectron, this.screenshot);
M
Michel Kaporin 已提交
68 69 70 71 72 73 74 75
		this.retrieveKeybindings();
	}

	public get app(): Application {
		return this.spectron;
	}

	public async start(): Promise<any> {
J
Joao 已提交
76 77 78
		await this.spectron.start();
		await this.focusOnWindow(1); // focuses on main renderer window
		await this.checkWindowReady();
M
Michel Kaporin 已提交
79 80 81 82 83 84 85 86 87
	}

	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> {
88
		return this.callClientAPI(func, args);
M
Michel Kaporin 已提交
89 90 91 92 93 94 95 96 97 98
	}

	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);
	}

J
Joao 已提交
99 100
	private async checkWindowReady(): Promise<any> {
		await this.waitFor(this.spectron.client.getHTML, '[id="workbench.main.container"]');
M
Michel Kaporin 已提交
101 102 103
	}

	private retrieveKeybindings() {
J
Joao 已提交
104
		fs.readFile(path.join(__dirname, '../../test_data/keybindings.json'), 'utf8', (err, data) => {
M
Michel Kaporin 已提交
105 106 107
			if (err) {
				throw err;
			}
108 109 110 111 112
			try {
				this.keybindings = JSON.parse(data);
			} catch (e) {
				throw new Error(`Error parsing keybindings JSON: ${e}`);
			}
M
Michel Kaporin 已提交
113 114 115
		});
	}

J
Joao 已提交
116
	private async callClientAPI(func: (...args: any[]) => Promise<any>, args: any): Promise<any> {
117
		let trial = 1;
J
Joao 已提交
118 119 120 121

		while (true) {
			if (trial > this.pollTrials) {
				throw new Error(`Could not retrieve the element in ${this.testRetry * this.pollTrials * this.pollTimeout} seconds.`);
M
Michel Kaporin 已提交
122
			}
J
Joao 已提交
123 124 125 126 127 128 129 130 131 132 133 134 135 136

			let result;
			try {
				result = await func.call(this.client, args, false);
			} catch (e) { }

			if (result && result !== '') {
				await this.screenshot.capture();
				return result;
			}

			await this.wait();
			trial++;
		}
M
Michel Kaporin 已提交
137 138 139 140 141 142 143
	}

	/**
	 * 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> {
144
		const binding = this.keybindings.find(x => x['command'] === command);
145
		if (!binding) {
146
			return Promise.reject(`Key binding for ${command} was not found.`);
147 148
		}

M
Michel Kaporin 已提交
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
		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);
		};
	}
}