driver.ts 7.1 KB
Newer Older
J
Joao Moreno 已提交
1 2 3 4 5 6 7 8
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

import { TPromise } from 'vs/base/common/winjs.base';
J
Joao Moreno 已提交
9
import { IDisposable, toDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
J
Joao Moreno 已提交
10 11
import { IWindowDriver, IElement, WindowDriverChannel, WindowDriverRegistryChannelClient } from 'vs/platform/driver/common/driver';
import { IPCClient } from 'vs/base/parts/ipc/common/ipc';
J
Joao Moreno 已提交
12
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
J
Joao Moreno 已提交
13 14
import { getTopLeftOffset, getClientArea } from 'vs/base/browser/dom';
import * as electron from 'electron';
15
import { IWindowService } from 'vs/platform/windows/common/windows';
D
Daniel Imms 已提交
16
import { Terminal } from 'vscode-xterm';
J
Joao Moreno 已提交
17

18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
function serializeElement(element: Element, recursive: boolean): IElement {
	const attributes = Object.create(null);

	for (let j = 0; j < element.attributes.length; j++) {
		const attr = element.attributes.item(j);
		attributes[attr.name] = attr.value;
	}

	const children = [];

	if (recursive) {
		for (let i = 0; i < element.children.length; i++) {
			children.push(serializeElement(element.children.item(i), true));
		}
	}

J
Joao Moreno 已提交
34 35
	const { left, top } = getTopLeftOffset(element as HTMLElement);

36 37 38 39 40
	return {
		tagName: element.tagName,
		className: element.className,
		textContent: element.textContent || '',
		attributes,
J
Joao Moreno 已提交
41 42 43
		children,
		left,
		top
44 45 46
	};
}

J
Joao Moreno 已提交
47 48
class WindowDriver implements IWindowDriver {

49 50 51
	constructor(
		@IWindowService private windowService: IWindowService
	) { }
J
Joao Moreno 已提交
52

J
Joao Moreno 已提交
53
	click(selector: string, xoffset?: number, yoffset?: number): TPromise<void> {
J
Joao Moreno 已提交
54
		return this._click(selector, 1, xoffset, yoffset);
J
Joao Moreno 已提交
55 56 57
	}

	doubleClick(selector: string): TPromise<void> {
J
Joao Moreno 已提交
58
		return this._click(selector, 2);
J
Joao Moreno 已提交
59 60
	}

J
Joao Moreno 已提交
61
	private _getElementXY(selector: string, xoffset?: number, yoffset?: number): TPromise<{ x: number; y: number; }> {
J
Joao Moreno 已提交
62 63 64
		const element = document.querySelector(selector);

		if (!element) {
J
Joao Moreno 已提交
65
			return TPromise.wrapError(new Error('Element not found'));
J
Joao Moreno 已提交
66 67 68 69 70 71 72 73 74 75 76 77 78 79
		}

		const { left, top } = getTopLeftOffset(element as HTMLElement);
		const { width, height } = getClientArea(element as HTMLElement);
		let x: number, y: number;

		if ((typeof xoffset === 'number') || (typeof yoffset === 'number')) {
			x = left + xoffset;
			y = top + yoffset;
		} else {
			x = left + (width / 2);
			y = top + (height / 2);
		}

J
Joao Moreno 已提交
80 81 82
		x = Math.round(x);
		y = Math.round(y);

J
Joao Moreno 已提交
83
		return TPromise.as({ x, y });
J
Joao Moreno 已提交
84 85
	}

J
Joao Moreno 已提交
86 87
	private _click(selector: string, clickCount: number, xoffset?: number, yoffset?: number): TPromise<void> {
		return this._getElementXY(selector, xoffset, yoffset).then(({ x, y }) => {
88

J
Joao Moreno 已提交
89 90
			const webContents = electron.remote.getCurrentWebContents();
			webContents.sendInputEvent({ type: 'mouseDown', x, y, button: 'left', clickCount } as any);
J
Joao Moreno 已提交
91

J
Joao Moreno 已提交
92 93 94 95 96
			return TPromise.timeout(10).then(() => {
				webContents.sendInputEvent({ type: 'mouseUp', x, y, button: 'left', clickCount } as any);
				return TPromise.timeout(100);
			});
		});
J
Joao Moreno 已提交
97 98
	}

J
Joao Moreno 已提交
99
	setValue(selector: string, text: string): TPromise<void> {
J
Joao Moreno 已提交
100 101 102
		const element = document.querySelector(selector);

		if (!element) {
J
Joao Moreno 已提交
103
			return TPromise.wrapError(new Error('Element not found'));
J
Joao Moreno 已提交
104 105
		}

J
Joao Moreno 已提交
106 107 108 109 110
		const inputElement = element as HTMLInputElement;
		inputElement.value = text;

		const event = new Event('input', { bubbles: true, cancelable: true });
		inputElement.dispatchEvent(event);
J
Joao Moreno 已提交
111 112

		return TPromise.as(null);
J
Joao Moreno 已提交
113 114
	}

J
Joao Moreno 已提交
115 116
	getTitle(): TPromise<string> {
		return TPromise.as(document.title);
J
Joao Moreno 已提交
117 118
	}

J
Joao Moreno 已提交
119
	isActiveElement(selector: string): TPromise<boolean> {
J
Joao Moreno 已提交
120
		const element = document.querySelector(selector);
121 122

		if (element !== document.activeElement) {
123 124
			const chain = [];
			let el = document.activeElement;
125

126 127 128 129 130
			while (el) {
				const tagName = el.tagName;
				const id = el.id ? `#${el.id}` : '';
				const classes = el.className.split(/\s+/g).map(c => c.trim()).filter(c => !!c).map(c => `.${c}`).join('');
				chain.unshift(`${tagName}${id}${classes}`);
J
Joao Moreno 已提交
131 132

				el = el.parentElement;
133 134
			}

J
Joao Moreno 已提交
135
			return TPromise.wrapError(new Error(`Active element not found. Current active element is '${chain.join(' > ')}'`));
136 137
		}

J
Joao Moreno 已提交
138
		return TPromise.as(true);
J
Joao Moreno 已提交
139 140
	}

J
Joao Moreno 已提交
141
	getElements(selector: string, recursive: boolean): TPromise<IElement[]> {
J
Joao Moreno 已提交
142 143 144 145 146
		const query = document.querySelectorAll(selector);
		const result: IElement[] = [];

		for (let i = 0; i < query.length; i++) {
			const element = query.item(i);
147
			result.push(serializeElement(element, recursive));
J
Joao Moreno 已提交
148 149
		}

J
Joao Moreno 已提交
150
		return TPromise.as(result);
J
Joao Moreno 已提交
151
	}
J
Joao Moreno 已提交
152

J
Joao Moreno 已提交
153
	typeInEditor(selector: string, text: string): TPromise<void> {
J
Joao Moreno 已提交
154 155 156
		const element = document.querySelector(selector);

		if (!element) {
J
Joao Moreno 已提交
157
			return TPromise.wrapError(new Error('Editor not found: ' + selector));
J
Joao Moreno 已提交
158 159 160 161 162 163 164 165 166 167 168 169 170
		}

		const textarea = element as HTMLTextAreaElement;
		const start = textarea.selectionStart;
		const newStart = start + text.length;
		const value = textarea.value;
		const newValue = value.substr(0, start) + text + value.substr(start);

		textarea.value = newValue;
		textarea.setSelectionRange(newStart, newStart);

		const event = new Event('input', { 'bubbles': true, 'cancelable': true });
		textarea.dispatchEvent(event);
J
Joao Moreno 已提交
171 172

		return TPromise.as(null);
J
Joao Moreno 已提交
173 174
	}

J
Joao Moreno 已提交
175
	getTerminalBuffer(selector: string): TPromise<string[]> {
J
Joao Moreno 已提交
176 177 178
		const element = document.querySelector(selector);

		if (!element) {
J
Joao Moreno 已提交
179
			return TPromise.wrapError(new Error('Terminal not found: ' + selector));
J
Joao Moreno 已提交
180 181
		}

D
Daniel Imms 已提交
182
		const xterm: Terminal = (element as any).xterm;
J
Joao Moreno 已提交
183 184

		if (!xterm) {
J
Joao Moreno 已提交
185
			return TPromise.wrapError(new Error('Xterm not found: ' + selector));
J
Joao Moreno 已提交
186
		}
J
Joao Moreno 已提交
187 188 189

		const lines: string[] = [];

D
Daniel Imms 已提交
190 191
		for (let i = 0; i < xterm._core.buffer.lines.length; i++) {
			lines.push(xterm._core.buffer.translateBufferLineToString(i, true));
J
Joao Moreno 已提交
192 193
		}

J
Joao Moreno 已提交
194
		return TPromise.as(lines);
J
Joao Moreno 已提交
195
	}
196

J
Joao Moreno 已提交
197
	writeInTerminal(selector: string, text: string): TPromise<void> {
J
Joao Moreno 已提交
198 199 200
		const element = document.querySelector(selector);

		if (!element) {
J
Joao Moreno 已提交
201
			return TPromise.wrapError(new Error('Element not found'));
J
Joao Moreno 已提交
202 203
		}

D
Daniel Imms 已提交
204
		const xterm: Terminal = (element as any).xterm;
J
Joao Moreno 已提交
205 206

		if (!xterm) {
J
Joao Moreno 已提交
207
			return TPromise.wrapError(new Error('Xterm not found'));
J
Joao Moreno 已提交
208 209
		}

D
Daniel Imms 已提交
210
		xterm._core.handler(text);
J
Joao Moreno 已提交
211 212

		return TPromise.as(null);
J
Joao Moreno 已提交
213 214
	}

J
Joao Moreno 已提交
215 216
	openDevTools(): TPromise<void> {
		return this.windowService.openDevTools({ mode: 'detach' });
217
	}
J
Joao Moreno 已提交
218 219
}

J
Joao Moreno 已提交
220 221 222 223
export async function registerWindowDriver(
	client: IPCClient,
	windowId: number,
	instantiationService: IInstantiationService
224
): Promise<IDisposable> {
J
Joao Moreno 已提交
225
	const windowDriver = instantiationService.createInstance(WindowDriver);
J
Joao Moreno 已提交
226 227 228 229 230 231
	const windowDriverChannel = new WindowDriverChannel(windowDriver);
	client.registerChannel('windowDriver', windowDriverChannel);

	const windowDriverRegistryChannel = client.getChannel('windowDriverRegistry');
	const windowDriverRegistry = new WindowDriverRegistryChannelClient(windowDriverRegistryChannel);

232 233 234
	const options = await windowDriverRegistry.registerWindowDriver(windowId);

	if (options.verbose) {
J
Joao Moreno 已提交
235
		windowDriver.openDevTools();
236
	}
J
Joao Moreno 已提交
237

J
Joao Moreno 已提交
238 239
	const disposable = toDisposable(() => windowDriverRegistry.reloadWindowDriver(windowId));
	return combinedDisposable([disposable, client]);
240
}