mirror of
https://github.com/Microsoft/vscode
synced 2024-10-05 19:02:54 +00:00
Share code between electron and browser drivers
This commit is contained in:
parent
f9dc56ccfe
commit
65cd41503b
|
@ -64,7 +64,6 @@
|
|||
"@types/minimist": "^1.2.0",
|
||||
"@types/mocha": "2.2.39",
|
||||
"@types/node": "^10.12.12",
|
||||
"@types/puppeteer": "^1.12.4",
|
||||
"@types/semver": "^5.5.0",
|
||||
"@types/sinon": "^1.16.36",
|
||||
"@types/webpack": "^4.4.10",
|
||||
|
|
166
src/vs/platform/driver/browser/baseDriver.ts
Normal file
166
src/vs/platform/driver/browser/baseDriver.ts
Normal file
|
@ -0,0 +1,166 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { getTopLeftOffset } from 'vs/base/browser/dom';
|
||||
// TODO: Allow this
|
||||
// tslint:disable-next-line: import-patterns
|
||||
import { Terminal } from 'xterm';
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
import { IElement, IWindowDriver } from 'vs/platform/driver/common/driver';
|
||||
|
||||
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);
|
||||
if (attr) {
|
||||
attributes[attr.name] = attr.value;
|
||||
}
|
||||
}
|
||||
|
||||
const children: IElement[] = [];
|
||||
|
||||
if (recursive) {
|
||||
for (let i = 0; i < element.children.length; i++) {
|
||||
const child = element.children.item(i);
|
||||
if (child) {
|
||||
children.push(serializeElement(child, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { left, top } = getTopLeftOffset(element as HTMLElement);
|
||||
|
||||
return {
|
||||
tagName: element.tagName,
|
||||
className: element.className,
|
||||
textContent: element.textContent || '',
|
||||
attributes,
|
||||
children,
|
||||
left,
|
||||
top
|
||||
};
|
||||
}
|
||||
|
||||
export abstract class BaseWindowDriver implements IWindowDriver {
|
||||
|
||||
constructor() { }
|
||||
|
||||
// TODO: This doesn't work in browser driver
|
||||
abstract click(selector: string, xoffset?: number, yoffset?: number): Promise<void>;
|
||||
abstract doubleClick(selector: string): Promise<void>;
|
||||
|
||||
async setValue(selector: string, text: string): Promise<void> {
|
||||
const element = document.querySelector(selector);
|
||||
|
||||
if (!element) {
|
||||
return Promise.reject(new Error(`Element not found: ${selector}`));
|
||||
}
|
||||
|
||||
const inputElement = element as HTMLInputElement;
|
||||
inputElement.value = text;
|
||||
|
||||
const event = new Event('input', { bubbles: true, cancelable: true });
|
||||
inputElement.dispatchEvent(event);
|
||||
}
|
||||
|
||||
async getTitle(): Promise<string> {
|
||||
return document.title;
|
||||
}
|
||||
|
||||
async isActiveElement(selector: string): Promise<boolean> {
|
||||
const element = document.querySelector(selector);
|
||||
|
||||
if (element !== document.activeElement) {
|
||||
const chain: string[] = [];
|
||||
let el = document.activeElement;
|
||||
|
||||
while (el) {
|
||||
const tagName = el.tagName;
|
||||
const id = el.id ? `#${el.id}` : '';
|
||||
const classes = coalesce(el.className.split(/\s+/g).map(c => c.trim())).map(c => `.${c}`).join('');
|
||||
chain.unshift(`${tagName}${id}${classes}`);
|
||||
|
||||
el = el.parentElement;
|
||||
}
|
||||
|
||||
throw new Error(`Active element not found. Current active element is '${chain.join(' > ')}'. Looking for ${selector}`);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async getElements(selector: string, recursive: boolean): Promise<IElement[]> {
|
||||
const query = document.querySelectorAll(selector);
|
||||
const result: IElement[] = [];
|
||||
|
||||
for (let i = 0; i < query.length; i++) {
|
||||
const element = query.item(i);
|
||||
result.push(serializeElement(element, recursive));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async typeInEditor(selector: string, text: string): Promise<void> {
|
||||
const element = document.querySelector(selector);
|
||||
|
||||
if (!element) {
|
||||
throw new Error(`Editor not found: ${selector}`);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
async getTerminalBuffer(selector: string): Promise<string[]> {
|
||||
const element = document.querySelector(selector);
|
||||
|
||||
if (!element) {
|
||||
throw new Error(`Terminal not found: ${selector}`);
|
||||
}
|
||||
|
||||
const xterm: Terminal = (element as any).xterm;
|
||||
|
||||
if (!xterm) {
|
||||
throw new Error(`Xterm not found: ${selector}`);
|
||||
}
|
||||
|
||||
const lines: string[] = [];
|
||||
|
||||
for (let i = 0; i < xterm.buffer.length; i++) {
|
||||
lines.push(xterm.buffer.getLine(i)!.translateToString(true));
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
async writeInTerminal(selector: string, text: string): Promise<void> {
|
||||
const element = document.querySelector(selector);
|
||||
|
||||
if (!element) {
|
||||
throw new Error(`Element not found: ${selector}`);
|
||||
}
|
||||
|
||||
const xterm: Terminal = (element as any).xterm;
|
||||
|
||||
if (!xterm) {
|
||||
throw new Error(`Xterm not found: ${selector}`);
|
||||
}
|
||||
|
||||
xterm._core._coreService.triggerDataEvent(text);
|
||||
}
|
||||
|
||||
abstract async openDevTools(): Promise<void>;
|
||||
}
|
|
@ -4,209 +4,17 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { getTopLeftOffset } from 'vs/base/browser/dom';
|
||||
// TODO: Allow this
|
||||
// tslint:disable-next-line: import-patterns
|
||||
import { Terminal } from 'xterm';
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
import { IElement, IWindowDriver } from 'vs/platform/driver/common/driver';
|
||||
import { BaseWindowDriver } from 'vs/platform/driver/browser/baseDriver';
|
||||
|
||||
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);
|
||||
if (attr) {
|
||||
attributes[attr.name] = attr.value;
|
||||
}
|
||||
class BrowserWindowDriver extends BaseWindowDriver {
|
||||
click(selector: string, xoffset?: number | undefined, yoffset?: number | undefined): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
const children: IElement[] = [];
|
||||
|
||||
if (recursive) {
|
||||
for (let i = 0; i < element.children.length; i++) {
|
||||
const child = element.children.item(i);
|
||||
if (child) {
|
||||
children.push(serializeElement(child, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { left, top } = getTopLeftOffset(element as HTMLElement);
|
||||
|
||||
return {
|
||||
tagName: element.tagName,
|
||||
className: element.className,
|
||||
textContent: element.textContent || '',
|
||||
attributes,
|
||||
children,
|
||||
left,
|
||||
top
|
||||
};
|
||||
}
|
||||
|
||||
class BrowserWindowDriver implements IWindowDriver {
|
||||
|
||||
constructor() { }
|
||||
|
||||
click(selector: string, xoffset?: number, yoffset?: number): Promise<void> {
|
||||
const offset = typeof xoffset === 'number' && typeof yoffset === 'number' ? { x: xoffset, y: yoffset } : undefined;
|
||||
return this._click(selector, 1, offset);
|
||||
}
|
||||
|
||||
doubleClick(selector: string): Promise<void> {
|
||||
return this._click(selector, 2);
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
// private async _getElementXY(selector: string, offset?: { x: number, y: number }): Promise<{ x: number; y: number; }> {
|
||||
// const element = document.querySelector(selector);
|
||||
|
||||
// if (!element) {
|
||||
// return Promise.reject(new Error(`Element not found: ${selector}`));
|
||||
// }
|
||||
|
||||
// const { left, top } = getTopLeftOffset(element as HTMLElement);
|
||||
// const { width, height } = getClientArea(element as HTMLElement);
|
||||
// let x: number, y: number;
|
||||
|
||||
// if (offset) {
|
||||
// x = left + offset.x;
|
||||
// y = top + offset.y;
|
||||
// } else {
|
||||
// x = left + (width / 2);
|
||||
// y = top + (height / 2);
|
||||
// }
|
||||
|
||||
// x = Math.round(x);
|
||||
// y = Math.round(y);
|
||||
|
||||
// return { x, y };
|
||||
// }
|
||||
|
||||
private async _click(selector: string, clickCount: number, offset?: { x: number, y: number }): Promise<void> {
|
||||
console.log('NYI');
|
||||
// const { x, y } = await this._getElementXY(selector, offset);
|
||||
|
||||
// const webContents: electron.WebContents = (electron as any).remote.getCurrentWebContents();
|
||||
// webContents.sendInputEvent({ type: 'mouseDown', x, y, button: 'left', clickCount } as any);
|
||||
// await timeout(10);
|
||||
|
||||
// webContents.sendInputEvent({ type: 'mouseUp', x, y, button: 'left', clickCount } as any);
|
||||
// await timeout(100);
|
||||
}
|
||||
|
||||
async setValue(selector: string, text: string): Promise<void> {
|
||||
const element = document.querySelector(selector);
|
||||
|
||||
if (!element) {
|
||||
return Promise.reject(new Error(`Element not found: ${selector}`));
|
||||
}
|
||||
|
||||
const inputElement = element as HTMLInputElement;
|
||||
inputElement.value = text;
|
||||
|
||||
const event = new Event('input', { bubbles: true, cancelable: true });
|
||||
inputElement.dispatchEvent(event);
|
||||
}
|
||||
|
||||
async getTitle(): Promise<string> {
|
||||
return document.title;
|
||||
}
|
||||
|
||||
async isActiveElement(selector: string): Promise<boolean> {
|
||||
const element = document.querySelector(selector);
|
||||
|
||||
if (element !== document.activeElement) {
|
||||
const chain: string[] = [];
|
||||
let el = document.activeElement;
|
||||
|
||||
while (el) {
|
||||
const tagName = el.tagName;
|
||||
const id = el.id ? `#${el.id}` : '';
|
||||
const classes = coalesce(el.className.split(/\s+/g).map(c => c.trim())).map(c => `.${c}`).join('');
|
||||
chain.unshift(`${tagName}${id}${classes}`);
|
||||
|
||||
el = el.parentElement;
|
||||
}
|
||||
|
||||
throw new Error(`Active element not found. Current active element is '${chain.join(' > ')}'. Looking for ${selector}`);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async getElements(selector: string, recursive: boolean): Promise<IElement[]> {
|
||||
const query = document.querySelectorAll(selector);
|
||||
const result: IElement[] = [];
|
||||
|
||||
for (let i = 0; i < query.length; i++) {
|
||||
const element = query.item(i);
|
||||
result.push(serializeElement(element, recursive));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async typeInEditor(selector: string, text: string): Promise<void> {
|
||||
const element = document.querySelector(selector);
|
||||
|
||||
if (!element) {
|
||||
throw new Error(`Editor not found: ${selector}`);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
async getTerminalBuffer(selector: string): Promise<string[]> {
|
||||
const element = document.querySelector(selector);
|
||||
|
||||
if (!element) {
|
||||
throw new Error(`Terminal not found: ${selector}`);
|
||||
}
|
||||
|
||||
const xterm: Terminal = (element as any).xterm;
|
||||
|
||||
if (!xterm) {
|
||||
throw new Error(`Xterm not found: ${selector}`);
|
||||
}
|
||||
|
||||
const lines: string[] = [];
|
||||
|
||||
for (let i = 0; i < xterm.buffer.length; i++) {
|
||||
lines.push(xterm.buffer.getLine(i)!.translateToString(true));
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
async writeInTerminal(selector: string, text: string): Promise<void> {
|
||||
const element = document.querySelector(selector);
|
||||
|
||||
if (!element) {
|
||||
throw new Error(`Element not found: ${selector}`);
|
||||
}
|
||||
|
||||
const xterm: Terminal = (element as any).xterm;
|
||||
|
||||
if (!xterm) {
|
||||
throw new Error(`Xterm not found: ${selector}`);
|
||||
}
|
||||
|
||||
xterm._core._coreService.triggerDataEvent(text);
|
||||
}
|
||||
|
||||
async openDevTools(): Promise<void> {
|
||||
// await this.windowService.openDevTools({ mode: 'detach' });
|
||||
openDevTools(): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,55 +4,22 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IWindowDriver, IElement, WindowDriverChannel, WindowDriverRegistryChannelClient } from 'vs/platform/driver/node/driver';
|
||||
import { WindowDriverChannel, WindowDriverRegistryChannelClient } from 'vs/platform/driver/node/driver';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService';
|
||||
import { getTopLeftOffset, getClientArea } from 'vs/base/browser/dom';
|
||||
import * as electron from 'electron';
|
||||
import { IWindowService } from 'vs/platform/windows/common/windows';
|
||||
import { Terminal } from 'xterm';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
import { BaseWindowDriver } from 'vs/platform/driver/browser/baseDriver';
|
||||
|
||||
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);
|
||||
if (attr) {
|
||||
attributes[attr.name] = attr.value;
|
||||
}
|
||||
}
|
||||
|
||||
const children: IElement[] = [];
|
||||
|
||||
if (recursive) {
|
||||
for (let i = 0; i < element.children.length; i++) {
|
||||
const child = element.children.item(i);
|
||||
if (child) {
|
||||
children.push(serializeElement(child, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { left, top } = getTopLeftOffset(element as HTMLElement);
|
||||
|
||||
return {
|
||||
tagName: element.tagName,
|
||||
className: element.className,
|
||||
textContent: element.textContent || '',
|
||||
attributes,
|
||||
children,
|
||||
left,
|
||||
top
|
||||
};
|
||||
}
|
||||
|
||||
class WindowDriver implements IWindowDriver {
|
||||
class WindowDriver extends BaseWindowDriver {
|
||||
|
||||
constructor(
|
||||
@IWindowService private readonly windowService: IWindowService
|
||||
) { }
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
click(selector: string, xoffset?: number, yoffset?: number): Promise<void> {
|
||||
const offset = typeof xoffset === 'number' && typeof yoffset === 'number' ? { x: xoffset, y: yoffset } : undefined;
|
||||
|
@ -99,116 +66,6 @@ class WindowDriver implements IWindowDriver {
|
|||
await timeout(100);
|
||||
}
|
||||
|
||||
async setValue(selector: string, text: string): Promise<void> {
|
||||
const element = document.querySelector(selector);
|
||||
|
||||
if (!element) {
|
||||
return Promise.reject(new Error(`Element not found: ${selector}`));
|
||||
}
|
||||
|
||||
const inputElement = element as HTMLInputElement;
|
||||
inputElement.value = text;
|
||||
|
||||
const event = new Event('input', { bubbles: true, cancelable: true });
|
||||
inputElement.dispatchEvent(event);
|
||||
}
|
||||
|
||||
async getTitle(): Promise<string> {
|
||||
return document.title;
|
||||
}
|
||||
|
||||
async isActiveElement(selector: string): Promise<boolean> {
|
||||
const element = document.querySelector(selector);
|
||||
|
||||
if (element !== document.activeElement) {
|
||||
const chain: string[] = [];
|
||||
let el = document.activeElement;
|
||||
|
||||
while (el) {
|
||||
const tagName = el.tagName;
|
||||
const id = el.id ? `#${el.id}` : '';
|
||||
const classes = coalesce(el.className.split(/\s+/g).map(c => c.trim())).map(c => `.${c}`).join('');
|
||||
chain.unshift(`${tagName}${id}${classes}`);
|
||||
|
||||
el = el.parentElement;
|
||||
}
|
||||
|
||||
throw new Error(`Active element not found. Current active element is '${chain.join(' > ')}'. Looking for ${selector}`);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async getElements(selector: string, recursive: boolean): Promise<IElement[]> {
|
||||
const query = document.querySelectorAll(selector);
|
||||
const result: IElement[] = [];
|
||||
|
||||
for (let i = 0; i < query.length; i++) {
|
||||
const element = query.item(i);
|
||||
result.push(serializeElement(element, recursive));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async typeInEditor(selector: string, text: string): Promise<void> {
|
||||
const element = document.querySelector(selector);
|
||||
|
||||
if (!element) {
|
||||
throw new Error(`Editor not found: ${selector}`);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
async getTerminalBuffer(selector: string): Promise<string[]> {
|
||||
const element = document.querySelector(selector);
|
||||
|
||||
if (!element) {
|
||||
throw new Error(`Terminal not found: ${selector}`);
|
||||
}
|
||||
|
||||
const xterm: Terminal = (element as any).xterm;
|
||||
|
||||
if (!xterm) {
|
||||
throw new Error(`Xterm not found: ${selector}`);
|
||||
}
|
||||
|
||||
const lines: string[] = [];
|
||||
|
||||
for (let i = 0; i < xterm.buffer.length; i++) {
|
||||
lines.push(xterm.buffer.getLine(i)!.translateToString(true));
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
async writeInTerminal(selector: string, text: string): Promise<void> {
|
||||
const element = document.querySelector(selector);
|
||||
|
||||
if (!element) {
|
||||
throw new Error(`Element not found: ${selector}`);
|
||||
}
|
||||
|
||||
const xterm: Terminal = (element as any).xterm;
|
||||
|
||||
if (!xterm) {
|
||||
throw new Error(`Xterm not found: ${selector}`);
|
||||
}
|
||||
|
||||
xterm._core._coreService.triggerDataEvent(text);
|
||||
}
|
||||
|
||||
async openDevTools(): Promise<void> {
|
||||
await this.windowService.openDevTools({ mode: 'detach' });
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
"@types/mocha": "2.2.41",
|
||||
"@types/ncp": "2.0.1",
|
||||
"@types/node": "^10.14.8",
|
||||
"@types/puppeteer": "^1.19.0",
|
||||
"@types/rimraf": "2.0.2",
|
||||
"@types/webdriverio": "4.6.1",
|
||||
"concurrently": "^3.5.1",
|
||||
|
|
|
@ -236,7 +236,9 @@ after(async function () {
|
|||
await new Promise((c, e) => rimraf(testDataPath, { maxBusyTries: 10 }, err => err ? e(err) : c()));
|
||||
});
|
||||
|
||||
setupDataMigrationTests(stableCodePath, testDataPath);
|
||||
if (!opts.web) {
|
||||
setupDataMigrationTests(stableCodePath, testDataPath);
|
||||
}
|
||||
|
||||
describe('Running Code', () => {
|
||||
before(async function () {
|
||||
|
@ -271,6 +273,7 @@ describe('Running Code', () => {
|
|||
}
|
||||
|
||||
if (opts.web) {
|
||||
console.log('setup term tests only');
|
||||
setupTerminalTests();
|
||||
return;
|
||||
}
|
||||
|
@ -290,4 +293,6 @@ describe('Running Code', () => {
|
|||
setupDataLocalizationTests();
|
||||
});
|
||||
|
||||
setupLaunchTests();
|
||||
if (!opts.web) {
|
||||
setupLaunchTests();
|
||||
}
|
||||
|
|
|
@ -139,181 +139,28 @@ function buildDriver(browser: puppeteer.Browser, page: puppeteer.Page): IDriver
|
|||
`);
|
||||
await page.mouse.click(x + (xoffset ? xoffset : 0), y + (yoffset ? yoffset : 0));
|
||||
},
|
||||
doubleClick: (windowId, selector) => Promise.resolve(),
|
||||
setValue: async (windowId, selector, text) => {
|
||||
return page.evaluate(`
|
||||
(function() {
|
||||
const element = document.querySelector('${selector}');
|
||||
|
||||
if (!element) {
|
||||
throw new Error('Element not found: ${selector}');
|
||||
}
|
||||
|
||||
const inputElement = element;
|
||||
inputElement.value = '${text}';
|
||||
|
||||
const event = new Event('input', { bubbles: true, cancelable: true });
|
||||
inputElement.dispatchEvent(event);
|
||||
return true;
|
||||
})();
|
||||
`);
|
||||
doubleClick: async (windowId, selector) => {
|
||||
await this.click(windowId, selector, 0, 0);
|
||||
await timeout(60);
|
||||
await this.click(windowId, selector, 0, 0);
|
||||
await timeout(100);
|
||||
},
|
||||
getTitle: (windowId) => page.title(),
|
||||
isActiveElement: (windowId, selector) => {
|
||||
return page.evaluate(`document.querySelector('${selector}') === document.activeElement`);
|
||||
},
|
||||
getElements: (windowId, selector, recursive) => {
|
||||
return page.evaluate(`
|
||||
(function() {
|
||||
function convertToPixels(element, value) {
|
||||
return parseFloat(value) || 0;
|
||||
}
|
||||
function getDimension(element, cssPropertyName, jsPropertyName) {
|
||||
let computedStyle = getComputedStyle(element);
|
||||
let value = '0';
|
||||
if (computedStyle) {
|
||||
if (computedStyle.getPropertyValue) {
|
||||
value = computedStyle.getPropertyValue(cssPropertyName);
|
||||
} else {
|
||||
// IE8
|
||||
value = (computedStyle).getAttribute(jsPropertyName);
|
||||
}
|
||||
}
|
||||
return convertToPixels(element, value);
|
||||
}
|
||||
function getBorderLeftWidth(element) {
|
||||
return getDimension(element, 'border-left-width', 'borderLeftWidth');
|
||||
}
|
||||
function getBorderRightWidth(element) {
|
||||
return getDimension(element, 'border-right-width', 'borderRightWidth');
|
||||
}
|
||||
function getBorderTopWidth(element) {
|
||||
return getDimension(element, 'border-top-width', 'borderTopWidth');
|
||||
}
|
||||
function getBorderBottomWidth(element) {
|
||||
return getDimension(element, 'border-bottom-width', 'borderBottomWidth');
|
||||
}
|
||||
function getTopLeftOffset(element) {
|
||||
// Adapted from WinJS.Utilities.getPosition
|
||||
// and added borders to the mix
|
||||
|
||||
let offsetParent = element.offsetParent, top = element.offsetTop, left = element.offsetLeft;
|
||||
|
||||
while ((element = element.parentNode) !== null && element !== document.body && element !== document.documentElement) {
|
||||
top -= element.scrollTop;
|
||||
let c = getComputedStyle(element);
|
||||
if (c) {
|
||||
left -= c.direction !== 'rtl' ? element.scrollLeft : -element.scrollLeft;
|
||||
}
|
||||
|
||||
if (element === offsetParent) {
|
||||
left += getBorderLeftWidth(element);
|
||||
top += getBorderTopWidth(element);
|
||||
top += element.offsetTop;
|
||||
left += element.offsetLeft;
|
||||
offsetParent = element.offsetParent;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
left: left,
|
||||
top: top
|
||||
};
|
||||
}
|
||||
function serializeElement(element, recursive) {
|
||||
const attributes = Object.create(null);
|
||||
|
||||
for (let j = 0; j < element.attributes.length; j++) {
|
||||
const attr = element.attributes.item(j);
|
||||
if (attr) {
|
||||
attributes[attr.name] = attr.value;
|
||||
}
|
||||
}
|
||||
|
||||
const children = [];
|
||||
|
||||
if (recursive) {
|
||||
for (let i = 0; i < element.children.length; i++) {
|
||||
const child = element.children.item(i);
|
||||
if (child) {
|
||||
children.push(serializeElement(child, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { left, top } = getTopLeftOffset(element);
|
||||
|
||||
return {
|
||||
tagName: element.tagName,
|
||||
className: element.className,
|
||||
textContent: element.textContent || '',
|
||||
attributes,
|
||||
children,
|
||||
left,
|
||||
top
|
||||
};
|
||||
}
|
||||
|
||||
const query = document.querySelectorAll('${selector}');
|
||||
const result = [];
|
||||
|
||||
for (let i = 0; i < query.length; i++) {
|
||||
const element = query.item(i);
|
||||
result.push(serializeElement(element, ${recursive}));
|
||||
}
|
||||
|
||||
return result;
|
||||
})();
|
||||
`);
|
||||
},
|
||||
typeInEditor: (windowId, selector, text) => Promise.resolve(),
|
||||
getTerminalBuffer: (windowId, selector) => {
|
||||
return page.evaluate(`
|
||||
(function () {
|
||||
const element = document.querySelector('${selector}');
|
||||
|
||||
if (!element) {
|
||||
throw new Error('Terminal not found: ${selector}');
|
||||
}
|
||||
|
||||
const xterm = element.xterm;
|
||||
|
||||
if (!xterm) {
|
||||
throw new Error('Xterm not found: ${selector}');
|
||||
}
|
||||
|
||||
const lines = [];
|
||||
|
||||
for (let i = 0; i < xterm.buffer.length; i++) {
|
||||
lines.push(xterm.buffer.getLine(i).translateToString(true));
|
||||
}
|
||||
|
||||
return lines;
|
||||
})();
|
||||
`);
|
||||
},
|
||||
writeInTerminal: (windowId, selector, text) => {
|
||||
return page.evaluate(`
|
||||
(function () {
|
||||
const element = document.querySelector('${selector}');
|
||||
|
||||
if (!element) {
|
||||
throw new Error('Element not found: ${selector}');
|
||||
}
|
||||
|
||||
const xterm = element.xterm;
|
||||
|
||||
if (!xterm) {
|
||||
throw new Error('Xterm not found: ${selector}');
|
||||
}
|
||||
|
||||
xterm._core._coreService.triggerDataEvent('${text}');
|
||||
})();
|
||||
`);
|
||||
}
|
||||
setValue: async (windowId, selector, text) => page.evaluate(`window.driver.setValue('${selector}', '${text}')`),
|
||||
getTitle: (windowId) => page.evaluate(`window.driver.getTitle()`),
|
||||
isActiveElement: (windowId, selector) => page.evaluate(`window.driver.isActiveElement('${selector}')`),
|
||||
getElements: (windowId, selector, recursive) => page.evaluate(`window.driver.getElements('${selector}', ${recursive})`),
|
||||
typeInEditor: (windowId, selector, text) => page.evaluate(`window.driver.typeInEditor('${selector}', '${text}')`),
|
||||
getTerminalBuffer: (windowId, selector) => page.evaluate(`window.driver.getTerminalBuffer('${selector}')`),
|
||||
writeInTerminal: (windowId, selector, text) => page.evaluate(`window.driver.writeInTerminal('${selector}', '${text}')`)
|
||||
};
|
||||
}
|
||||
|
||||
function timeout(ms: number): Promise<void> {
|
||||
return new Promise<void>(r => setTimeout(r, ms));
|
||||
}
|
||||
|
||||
// function runInDriver(call: string, args: (string | boolean)[]): Promise<any> {}
|
||||
|
||||
let args;
|
||||
|
||||
export function launch(_args): void {
|
||||
|
@ -389,4 +236,4 @@ export interface IDriver {
|
|||
|
||||
export interface IDisposable {
|
||||
dispose(): void;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,6 +54,13 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9"
|
||||
integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw==
|
||||
|
||||
"@types/puppeteer@^1.19.0":
|
||||
version "1.19.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-1.19.0.tgz#59f0050bae019cee7c3af2bb840a25892a3078b6"
|
||||
integrity sha512-Db9LWOuTm2bR/qgPE7PQCmnsCQ6flHdULuIDWTks8YdQ/SGHKg5WGWG54gl0734NDKCTF5MbqAp2qWuvBiyQ3Q==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/rimraf@2.0.2":
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-2.0.2.tgz#7f0fc3cf0ff0ad2a99bb723ae1764f30acaf8b6e"
|
||||
|
|
|
@ -137,13 +137,6 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.12.tgz#e15a9d034d9210f00320ef718a50c4a799417c47"
|
||||
integrity sha512-Pr+6JRiKkfsFvmU/LK68oBRCQeEg36TyAbPhc2xpez24OOZZCuoIhWGTd39VZy6nGafSbxzGouFPTFD/rR1A0A==
|
||||
|
||||
"@types/puppeteer@^1.12.4":
|
||||
version "1.12.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-1.12.4.tgz#8388efdb0b30a54a7e7c4831ca0d709191d77ff1"
|
||||
integrity sha512-aaGbJaJ9TuF9vZfTeoh876sBa+rYJWPwtsmHmYr28pGr42ewJnkDTq2aeSKEmS39SqUdkwLj73y/d7rBSp7mDQ==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/semver@^5.4.0", "@types/semver@^5.5.0":
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45"
|
||||
|
|
Loading…
Reference in a new issue