Skip to content

Commit 96d1767

Browse files
committed
Start of browser driver and driver lib sharing
1 parent 2e9593d commit 96d1767

3 files changed

Lines changed: 287 additions & 0 deletions

File tree

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
7+
import { getTopLeftOffset } from 'vs/base/browser/dom';
8+
// TODO: Allow this
9+
// tslint:disable-next-line: import-patterns
10+
import { Terminal } from 'xterm';
11+
import { coalesce } from 'vs/base/common/arrays';
12+
import { IElement, IWindowDriver } from 'vs/platform/driver/common/driver';
13+
14+
function serializeElement(element: Element, recursive: boolean): IElement {
15+
const attributes = Object.create(null);
16+
17+
for (let j = 0; j < element.attributes.length; j++) {
18+
const attr = element.attributes.item(j);
19+
if (attr) {
20+
attributes[attr.name] = attr.value;
21+
}
22+
}
23+
24+
const children: IElement[] = [];
25+
26+
if (recursive) {
27+
for (let i = 0; i < element.children.length; i++) {
28+
const child = element.children.item(i);
29+
if (child) {
30+
children.push(serializeElement(child, true));
31+
}
32+
}
33+
}
34+
35+
const { left, top } = getTopLeftOffset(element as HTMLElement);
36+
37+
return {
38+
tagName: element.tagName,
39+
className: element.className,
40+
textContent: element.textContent || '',
41+
attributes,
42+
children,
43+
left,
44+
top
45+
};
46+
}
47+
48+
class BrowserWindowDriver implements IWindowDriver {
49+
50+
constructor() { }
51+
52+
click(selector: string, xoffset?: number, yoffset?: number): Promise<void> {
53+
const offset = typeof xoffset === 'number' && typeof yoffset === 'number' ? { x: xoffset, y: yoffset } : undefined;
54+
return this._click(selector, 1, offset);
55+
}
56+
57+
doubleClick(selector: string): Promise<void> {
58+
return this._click(selector, 2);
59+
}
60+
61+
// private async _getElementXY(selector: string, offset?: { x: number, y: number }): Promise<{ x: number; y: number; }> {
62+
// const element = document.querySelector(selector);
63+
64+
// if (!element) {
65+
// return Promise.reject(new Error(`Element not found: ${selector}`));
66+
// }
67+
68+
// const { left, top } = getTopLeftOffset(element as HTMLElement);
69+
// const { width, height } = getClientArea(element as HTMLElement);
70+
// let x: number, y: number;
71+
72+
// if (offset) {
73+
// x = left + offset.x;
74+
// y = top + offset.y;
75+
// } else {
76+
// x = left + (width / 2);
77+
// y = top + (height / 2);
78+
// }
79+
80+
// x = Math.round(x);
81+
// y = Math.round(y);
82+
83+
// return { x, y };
84+
// }
85+
86+
private async _click(selector: string, clickCount: number, offset?: { x: number, y: number }): Promise<void> {
87+
console.log('NYI');
88+
// const { x, y } = await this._getElementXY(selector, offset);
89+
90+
// const webContents: electron.WebContents = (electron as any).remote.getCurrentWebContents();
91+
// webContents.sendInputEvent({ type: 'mouseDown', x, y, button: 'left', clickCount } as any);
92+
// await timeout(10);
93+
94+
// webContents.sendInputEvent({ type: 'mouseUp', x, y, button: 'left', clickCount } as any);
95+
// await timeout(100);
96+
}
97+
98+
async setValue(selector: string, text: string): Promise<void> {
99+
const element = document.querySelector(selector);
100+
101+
if (!element) {
102+
return Promise.reject(new Error(`Element not found: ${selector}`));
103+
}
104+
105+
const inputElement = element as HTMLInputElement;
106+
inputElement.value = text;
107+
108+
const event = new Event('input', { bubbles: true, cancelable: true });
109+
inputElement.dispatchEvent(event);
110+
}
111+
112+
async getTitle(): Promise<string> {
113+
return document.title;
114+
}
115+
116+
async isActiveElement(selector: string): Promise<boolean> {
117+
const element = document.querySelector(selector);
118+
119+
if (element !== document.activeElement) {
120+
const chain: string[] = [];
121+
let el = document.activeElement;
122+
123+
while (el) {
124+
const tagName = el.tagName;
125+
const id = el.id ? `#${el.id}` : '';
126+
const classes = coalesce(el.className.split(/\s+/g).map(c => c.trim())).map(c => `.${c}`).join('');
127+
chain.unshift(`${tagName}${id}${classes}`);
128+
129+
el = el.parentElement;
130+
}
131+
132+
throw new Error(`Active element not found. Current active element is '${chain.join(' > ')}'. Looking for ${selector}`);
133+
}
134+
135+
return true;
136+
}
137+
138+
async getElements(selector: string, recursive: boolean): Promise<IElement[]> {
139+
const query = document.querySelectorAll(selector);
140+
const result: IElement[] = [];
141+
142+
for (let i = 0; i < query.length; i++) {
143+
const element = query.item(i);
144+
result.push(serializeElement(element, recursive));
145+
}
146+
147+
return result;
148+
}
149+
150+
async typeInEditor(selector: string, text: string): Promise<void> {
151+
const element = document.querySelector(selector);
152+
153+
if (!element) {
154+
throw new Error(`Editor not found: ${selector}`);
155+
}
156+
157+
const textarea = element as HTMLTextAreaElement;
158+
const start = textarea.selectionStart;
159+
const newStart = start + text.length;
160+
const value = textarea.value;
161+
const newValue = value.substr(0, start) + text + value.substr(start);
162+
163+
textarea.value = newValue;
164+
textarea.setSelectionRange(newStart, newStart);
165+
166+
const event = new Event('input', { 'bubbles': true, 'cancelable': true });
167+
textarea.dispatchEvent(event);
168+
}
169+
170+
async getTerminalBuffer(selector: string): Promise<string[]> {
171+
const element = document.querySelector(selector);
172+
173+
if (!element) {
174+
throw new Error(`Terminal not found: ${selector}`);
175+
}
176+
177+
const xterm: Terminal = (element as any).xterm;
178+
179+
if (!xterm) {
180+
throw new Error(`Xterm not found: ${selector}`);
181+
}
182+
183+
const lines: string[] = [];
184+
185+
for (let i = 0; i < xterm.buffer.length; i++) {
186+
lines.push(xterm.buffer.getLine(i)!.translateToString(true));
187+
}
188+
189+
return lines;
190+
}
191+
192+
async writeInTerminal(selector: string, text: string): Promise<void> {
193+
const element = document.querySelector(selector);
194+
195+
if (!element) {
196+
throw new Error(`Element not found: ${selector}`);
197+
}
198+
199+
const xterm: Terminal = (element as any).xterm;
200+
201+
if (!xterm) {
202+
throw new Error(`Xterm not found: ${selector}`);
203+
}
204+
205+
xterm._core._coreService.triggerDataEvent(text);
206+
}
207+
208+
async openDevTools(): Promise<void> {
209+
// await this.windowService.openDevTools({ mode: 'detach' });
210+
}
211+
}
212+
213+
export async function registerWindowDriver(): Promise<IDisposable> {
214+
console.log('registerWindowDriver');
215+
(<any>window).driver = new BrowserWindowDriver();
216+
// const windowDriverChannel = new WindowDriverChannel(windowDriver);
217+
// mainProcessService.registerChannel('windowDriver', windowDriverChannel);
218+
219+
// const windowDriverRegistryChannel = mainProcessService.getChannel('windowDriverRegistry');
220+
// const windowDriverRegistry = new WindowDriverRegistryChannelClient(windowDriverRegistryChannel);
221+
222+
// await windowDriverRegistry.registerWindowDriver(windowService.windowId);
223+
// const options = await windowDriverRegistry.registerWindowDriver(windowId);
224+
225+
// if (options.verbose) {
226+
// windowDriver.openDevTools();
227+
// }
228+
229+
// return toDisposable(() => windowDriverRegistry.reloadWindowDriver(windowService.windowId));
230+
return toDisposable(() => {
231+
return { dispose: () => { } };
232+
});
233+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
// TODO: Change smoketest build to read off common instead
7+
8+
// !! Do not remove the following START and END markers, they are parsed by the smoketest build
9+
10+
//*START
11+
export interface IElement {
12+
tagName: string;
13+
className: string;
14+
textContent: string;
15+
attributes: { [name: string]: string; };
16+
children: IElement[];
17+
top: number;
18+
left: number;
19+
}
20+
21+
export interface IDriver {
22+
_serviceBrand: any;
23+
24+
getWindowIds(): Promise<number[]>;
25+
capturePage(windowId: number): Promise<string>;
26+
reloadWindow(windowId: number): Promise<void>;
27+
exitApplication(): Promise<void>;
28+
dispatchKeybinding(windowId: number, keybinding: string): Promise<void>;
29+
click(windowId: number, selector: string, xoffset?: number | undefined, yoffset?: number | undefined): Promise<void>;
30+
doubleClick(windowId: number, selector: string): Promise<void>;
31+
setValue(windowId: number, selector: string, text: string): Promise<void>;
32+
getTitle(windowId: number): Promise<string>;
33+
isActiveElement(windowId: number, selector: string): Promise<boolean>;
34+
getElements(windowId: number, selector: string, recursive?: boolean): Promise<IElement[]>;
35+
typeInEditor(windowId: number, selector: string, text: string): Promise<void>;
36+
getTerminalBuffer(windowId: number, selector: string): Promise<string[]>;
37+
writeInTerminal(windowId: number, selector: string, text: string): Promise<void>;
38+
}
39+
//*END
40+
41+
export interface IWindowDriver {
42+
click(selector: string, xoffset?: number | undefined, yoffset?: number | undefined): Promise<void>;
43+
doubleClick(selector: string): Promise<void>;
44+
setValue(selector: string, text: string): Promise<void>;
45+
getTitle(): Promise<string>;
46+
isActiveElement(selector: string): Promise<boolean>;
47+
getElements(selector: string, recursive: boolean): Promise<IElement[]>;
48+
typeInEditor(selector: string, text: string): Promise<void>;
49+
getTerminalBuffer(selector: string): Promise<string[]>;
50+
writeInTerminal(selector: string, text: string): Promise<void>;
51+
}

src/vs/workbench/workbench.web.main.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@ import 'vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution';
349349

350350
// Outline
351351
import 'vs/workbench/contrib/outline/browser/outline.contribution';
352+
import { registerWindowDriver } from 'vs/platform/driver/browser/driver';
352353

353354
// Experiments
354355
// import 'vs/workbench/contrib/experiments/electron-browser/experiments.contribution';
@@ -357,3 +358,5 @@ import 'vs/workbench/contrib/outline/browser/outline.contribution';
357358
// import 'vs/workbench/contrib/issue/electron-browser/issue.contribution';
358359

359360
//#endregion
361+
362+
registerWindowDriver();

0 commit comments

Comments
 (0)