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

J
Joao Moreno 已提交
6
import { IDisposable, toDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
J
Joao Moreno 已提交
7 8
import { IWindowDriver, IElement, WindowDriverChannel, WindowDriverRegistryChannelClient } from 'vs/platform/driver/node/driver';
import { IPCClient } from 'vs/base/parts/ipc/node/ipc';
J
Joao Moreno 已提交
9
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
J
Joao Moreno 已提交
10 11
import { getTopLeftOffset, getClientArea } from 'vs/base/browser/dom';
import * as electron from 'electron';
12
import { IWindowService } from 'vs/platform/windows/common/windows';
D
Daniel Imms 已提交
13
import { Terminal } from 'vscode-xterm';
J
Joao Moreno 已提交
14
import { timeout } from 'vs/base/common/async';
M
Matt Bierner 已提交
15
import { coalesce } from 'vs/base/common/arrays';
J
Joao Moreno 已提交
16

17 18 19 20 21
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);
M
Matt Bierner 已提交
22 23 24
		if (attr) {
			attributes[attr.name] = attr.value;
		}
25 26
	}

M
Matt Bierner 已提交
27
	const children: IElement[] = [];
28 29 30

	if (recursive) {
		for (let i = 0; i < element.children.length; i++) {
M
Matt Bierner 已提交
31 32 33 34
			const child = element.children.item(i);
			if (child) {
				children.push(serializeElement(child, true));
			}
35 36 37
		}
	}

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

40 41 42 43 44
	return {
		tagName: element.tagName,
		className: element.className,
		textContent: element.textContent || '',
		attributes,
J
Joao Moreno 已提交
45 46 47
		children,
		left,
		top
48 49 50
	};
}

J
Joao Moreno 已提交
51 52
class WindowDriver implements IWindowDriver {

53
	constructor(
54
		@IWindowService private readonly windowService: IWindowService
55
	) { }
J
Joao Moreno 已提交
56

J
Joao Moreno 已提交
57
	click(selector: string, xoffset?: number, yoffset?: number): Promise<void> {
M
Matt Bierner 已提交
58 59
		const offset = typeof xoffset === 'number' && typeof yoffset === 'number' ? { x: xoffset, y: yoffset } : undefined;
		return this._click(selector, 1, offset);
J
Joao Moreno 已提交
60 61
	}

J
Joao Moreno 已提交
62
	doubleClick(selector: string): Promise<void> {
J
Joao Moreno 已提交
63
		return this._click(selector, 2);
J
Joao Moreno 已提交
64 65
	}

M
Matt Bierner 已提交
66
	private async _getElementXY(selector: string, offset?: { x: number, y: number }): Promise<{ x: number; y: number; }> {
J
Joao Moreno 已提交
67 68 69
		const element = document.querySelector(selector);

		if (!element) {
J
Joao Moreno 已提交
70
			return Promise.reject(new Error(`Element not found: ${selector}`));
J
Joao Moreno 已提交
71 72 73 74 75 76
		}

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

M
Matt Bierner 已提交
77 78 79
		if (offset) {
			x = left + offset.x;
			y = top + offset.y;
J
Joao Moreno 已提交
80 81 82 83 84
		} else {
			x = left + (width / 2);
			y = top + (height / 2);
		}

J
Joao Moreno 已提交
85 86 87
		x = Math.round(x);
		y = Math.round(y);

J
Joao Moreno 已提交
88
		return { x, y };
J
Joao Moreno 已提交
89 90
	}

M
Matt Bierner 已提交
91 92
	private async _click(selector: string, clickCount: number, offset?: { x: number, y: number }): Promise<void> {
		const { x, y } = await this._getElementXY(selector, offset);
93

J
Joao Moreno 已提交
94 95 96
		const webContents: electron.WebContents = (electron as any).remote.getCurrentWebContents();
		webContents.sendInputEvent({ type: 'mouseDown', x, y, button: 'left', clickCount } as any);
		await timeout(10);
J
Joao Moreno 已提交
97

J
Joao Moreno 已提交
98 99
		webContents.sendInputEvent({ type: 'mouseUp', x, y, button: 'left', clickCount } as any);
		await timeout(100);
J
Joao Moreno 已提交
100 101
	}

J
Joao Moreno 已提交
102
	async setValue(selector: string, text: string): Promise<void> {
J
Joao Moreno 已提交
103 104 105
		const element = document.querySelector(selector);

		if (!element) {
J
Joao Moreno 已提交
106
			return Promise.reject(new Error(`Element not found: ${selector}`));
J
Joao Moreno 已提交
107 108
		}

J
Joao Moreno 已提交
109 110 111 112 113
		const inputElement = element as HTMLInputElement;
		inputElement.value = text;

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

J
Joao Moreno 已提交
116 117
	async getTitle(): Promise<string> {
		return document.title;
J
Joao Moreno 已提交
118 119
	}

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

		if (element !== document.activeElement) {
M
Matt Bierner 已提交
124
			const chain: string[] = [];
125
			let el = document.activeElement;
126

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

				el = el.parentElement;
134 135
			}

J
Joao Moreno 已提交
136
			throw new Error(`Active element not found. Current active element is '${chain.join(' > ')}'. Looking for ${selector}`);
137 138
		}

J
Joao Moreno 已提交
139
		return true;
J
Joao Moreno 已提交
140 141
	}

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

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

J
Joao Moreno 已提交
151
		return result;
J
Joao Moreno 已提交
152
	}
J
Joao Moreno 已提交
153

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

		if (!element) {
J
Joao Moreno 已提交
158
			throw new Error(`Editor not found: ${selector}`);
J
Joao Moreno 已提交
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
		}

		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 已提交
174
	async getTerminalBuffer(selector: string): Promise<string[]> {
J
Joao Moreno 已提交
175 176 177
		const element = document.querySelector(selector);

		if (!element) {
J
Joao Moreno 已提交
178
			throw new Error(`Terminal not found: ${selector}`);
J
Joao Moreno 已提交
179 180
		}

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

		if (!xterm) {
J
Joao Moreno 已提交
184
			throw new Error(`Xterm not found: ${selector}`);
J
Joao Moreno 已提交
185
		}
J
Joao Moreno 已提交
186 187 188

		const lines: string[] = [];

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

J
Joao Moreno 已提交
193
		return lines;
J
Joao Moreno 已提交
194
	}
195

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

		if (!element) {
J
Joao Moreno 已提交
200
			throw new Error(`Element not found: ${selector}`);
J
Joao Moreno 已提交
201 202
		}

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

		if (!xterm) {
J
Joao Moreno 已提交
206
			throw new Error(`Xterm not found: ${selector}`);
J
Joao Moreno 已提交
207 208
		}

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

J
Joao Moreno 已提交
212 213
	async openDevTools(): Promise<void> {
		await this.windowService.openDevTools({ mode: 'detach' });
214
	}
J
Joao Moreno 已提交
215 216
}

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

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

J
Joao Moreno 已提交
229
	await windowDriverRegistry.registerWindowDriver(windowId);
230
	// const options = await windowDriverRegistry.registerWindowDriver(windowId);
231

232 233 234
	// if (options.verbose) {
	// 	windowDriver.openDevTools();
	// }
J
Joao Moreno 已提交
235

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