mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-12 09:50:11 -05:00
smoke - cleanup some file names
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
import { Workbench } from './workbench';
|
||||
import { Code, launch, LaunchOptions } from './code';
|
||||
import { Logger, measureAndLog } from './logger';
|
||||
import { PlaywrightDriver } from './playwrightBrowserDriver';
|
||||
import { PlaywrightDriver } from './playwrightDriver';
|
||||
|
||||
export const enum Quality {
|
||||
Dev,
|
||||
|
||||
@@ -7,9 +7,9 @@ import { join } from 'path';
|
||||
import * as os from 'os';
|
||||
import * as cp from 'child_process';
|
||||
import { IDriver, IDisposable, IElement, Thenable, ILocalizedStrings, ILocaleInfo } from './driver';
|
||||
import { launch as launchElectron } from './electronDriver';
|
||||
import { launch as launchPlaywrightBrowser } from './playwrightBrowserDriver';
|
||||
import { launch as launchPlaywrightElectron } from './playwrightElectronDriver';
|
||||
import { launch as launchElectron } from './electron';
|
||||
import { launch as launchPlaywrightBrowser } from './playwrightBrowser';
|
||||
import { launch as launchPlaywrightElectron } from './playwrightElectron';
|
||||
import { Logger, measureAndLog } from './logger';
|
||||
import { copyExtension } from './extensions';
|
||||
import * as treekill from 'tree-kill';
|
||||
@@ -18,19 +18,19 @@ const rootPath = join(__dirname, '../../..');
|
||||
|
||||
export interface LaunchOptions {
|
||||
codePath?: string;
|
||||
workspacePath: string;
|
||||
readonly workspacePath: string;
|
||||
userDataDir: string;
|
||||
extensionsPath: string;
|
||||
logger: Logger;
|
||||
logsPath: string;
|
||||
verbose?: boolean;
|
||||
extraArgs?: string[];
|
||||
remote?: boolean;
|
||||
web?: boolean;
|
||||
legacy?: boolean;
|
||||
tracing?: boolean;
|
||||
headless?: boolean;
|
||||
browser?: 'chromium' | 'webkit' | 'firefox';
|
||||
readonly extensionsPath: string;
|
||||
readonly logger: Logger;
|
||||
readonly logsPath: string;
|
||||
readonly verbose?: boolean;
|
||||
readonly extraArgs?: string[];
|
||||
readonly remote?: boolean;
|
||||
readonly web?: boolean;
|
||||
readonly legacy?: boolean;
|
||||
readonly tracing?: boolean;
|
||||
readonly headless?: boolean;
|
||||
readonly browser?: 'chromium' | 'webkit' | 'firefox';
|
||||
}
|
||||
|
||||
interface ICodeInstance {
|
||||
@@ -101,42 +101,6 @@ export async function launch(options: LaunchOptions): Promise<Code> {
|
||||
}
|
||||
}
|
||||
|
||||
async function poll<T>(
|
||||
fn: () => Thenable<T>,
|
||||
acceptFn: (result: T) => boolean,
|
||||
logger: Logger,
|
||||
timeoutMessage: string,
|
||||
retryCount = 200,
|
||||
retryInterval = 100 // millis
|
||||
): Promise<T> {
|
||||
let trial = 1;
|
||||
let lastError: string = '';
|
||||
|
||||
while (true) {
|
||||
if (trial > retryCount) {
|
||||
logger.log('Timeout!');
|
||||
logger.log(lastError);
|
||||
logger.log(`Timeout: ${timeoutMessage} after ${(retryCount * retryInterval) / 1000} seconds.`);
|
||||
throw new Error(`Timeout: ${timeoutMessage} after ${(retryCount * retryInterval) / 1000} seconds.`);
|
||||
}
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = await fn();
|
||||
if (acceptFn(result)) {
|
||||
return result;
|
||||
} else {
|
||||
lastError = 'Did not pass accept function';
|
||||
}
|
||||
} catch (e: any) {
|
||||
lastError = Array.isArray(e.stack) ? e.stack.join(os.EOL) : e.stack;
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, retryInterval));
|
||||
trial++;
|
||||
}
|
||||
}
|
||||
|
||||
export class Code {
|
||||
|
||||
private _activeWindowId: number | undefined = undefined;
|
||||
@@ -177,7 +141,7 @@ export class Code {
|
||||
}
|
||||
|
||||
async waitForWindowIds(accept: (windowIds: number[]) => boolean): Promise<void> {
|
||||
await poll(() => this.driver.getWindowIds(), accept, this.logger, `get window ids`);
|
||||
await this.poll(() => this.driver.getWindowIds(), accept, `get window ids`);
|
||||
}
|
||||
|
||||
async dispatchKeybinding(keybinding: string): Promise<void> {
|
||||
@@ -235,10 +199,9 @@ export class Code {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
accept = accept || (result => textContent !== undefined ? textContent === result : !!result);
|
||||
|
||||
return await poll(
|
||||
return await this.poll(
|
||||
() => this.driver.getElements(windowId, selector).then(els => els.length > 0 ? Promise.resolve(els[0].textContent) : Promise.reject(new Error('Element not found for textContent'))),
|
||||
s => accept!(typeof s === 'string' ? s : ''),
|
||||
this.logger,
|
||||
`get text content '${selector}'`,
|
||||
retryCount
|
||||
);
|
||||
@@ -246,47 +209,47 @@ export class Code {
|
||||
|
||||
async waitAndClick(selector: string, xoffset?: number, yoffset?: number, retryCount: number = 200): Promise<void> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
await poll(() => this.driver.click(windowId, selector, xoffset, yoffset), () => true, this.logger, `click '${selector}'`, retryCount);
|
||||
await this.poll(() => this.driver.click(windowId, selector, xoffset, yoffset), () => true, `click '${selector}'`, retryCount);
|
||||
}
|
||||
|
||||
async waitForSetValue(selector: string, value: string): Promise<void> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
await poll(() => this.driver.setValue(windowId, selector, value), () => true, this.logger, `set value '${selector}'`);
|
||||
await this.poll(() => this.driver.setValue(windowId, selector, value), () => true, `set value '${selector}'`);
|
||||
}
|
||||
|
||||
async waitForElements(selector: string, recursive: boolean, accept: (result: IElement[]) => boolean = result => result.length > 0): Promise<IElement[]> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
return await poll(() => this.driver.getElements(windowId, selector, recursive), accept, this.logger, `get elements '${selector}'`);
|
||||
return await this.poll(() => this.driver.getElements(windowId, selector, recursive), accept, `get elements '${selector}'`);
|
||||
}
|
||||
|
||||
async waitForElement(selector: string, accept: (result: IElement | undefined) => boolean = result => !!result, retryCount: number = 200): Promise<IElement> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
return await poll<IElement>(() => this.driver.getElements(windowId, selector).then(els => els[0]), accept, this.logger, `get element '${selector}'`, retryCount);
|
||||
return await this.poll<IElement>(() => this.driver.getElements(windowId, selector).then(els => els[0]), accept, `get element '${selector}'`, retryCount);
|
||||
}
|
||||
|
||||
async waitForActiveElement(selector: string, retryCount: number = 200): Promise<void> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
await poll(() => this.driver.isActiveElement(windowId, selector), r => r, this.logger, `is active element '${selector}'`, retryCount);
|
||||
await this.poll(() => this.driver.isActiveElement(windowId, selector), r => r, `is active element '${selector}'`, retryCount);
|
||||
}
|
||||
|
||||
async waitForTitle(accept: (title: string) => boolean): Promise<void> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
await poll(() => this.driver.getTitle(windowId), accept, this.logger, `get title`);
|
||||
await this.poll(() => this.driver.getTitle(windowId), accept, `get title`);
|
||||
}
|
||||
|
||||
async waitForTypeInEditor(selector: string, text: string): Promise<void> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
await poll(() => this.driver.typeInEditor(windowId, selector, text), () => true, this.logger, `type in editor '${selector}'`);
|
||||
await this.poll(() => this.driver.typeInEditor(windowId, selector, text), () => true, `type in editor '${selector}'`);
|
||||
}
|
||||
|
||||
async waitForTerminalBuffer(selector: string, accept: (result: string[]) => boolean): Promise<void> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
await poll(() => this.driver.getTerminalBuffer(windowId, selector), accept, this.logger, `get terminal buffer '${selector}'`);
|
||||
await this.poll(() => this.driver.getTerminalBuffer(windowId, selector), accept, `get terminal buffer '${selector}'`);
|
||||
}
|
||||
|
||||
async writeInTerminal(selector: string, value: string): Promise<void> {
|
||||
const windowId = await this.getActiveWindowId();
|
||||
await poll(() => this.driver.writeInTerminal(windowId, selector, value), () => true, this.logger, `writeInTerminal '${selector}'`);
|
||||
await this.poll(() => this.driver.writeInTerminal(windowId, selector, value), () => true, `writeInTerminal '${selector}'`);
|
||||
}
|
||||
|
||||
async getLocaleInfo(): Promise<ILocaleInfo> {
|
||||
@@ -313,6 +276,42 @@ export class Code {
|
||||
dispose(): void {
|
||||
this.client.dispose();
|
||||
}
|
||||
|
||||
private async poll<T>(
|
||||
fn: () => Thenable<T>,
|
||||
acceptFn: (result: T) => boolean,
|
||||
timeoutMessage: string,
|
||||
retryCount = 200,
|
||||
retryInterval = 100 // millis
|
||||
): Promise<T> {
|
||||
let trial = 1;
|
||||
let lastError: string = '';
|
||||
|
||||
while (true) {
|
||||
if (trial > retryCount) {
|
||||
this.logger.log('Timeout!');
|
||||
this.logger.log(lastError);
|
||||
this.logger.log(`Timeout: ${timeoutMessage} after ${(retryCount * retryInterval) / 1000} seconds.`);
|
||||
|
||||
throw new Error(`Timeout: ${timeoutMessage} after ${(retryCount * retryInterval) / 1000} seconds.`);
|
||||
}
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = await fn();
|
||||
if (acceptFn(result)) {
|
||||
return result;
|
||||
} else {
|
||||
lastError = 'Did not pass accept function';
|
||||
}
|
||||
} catch (e: any) {
|
||||
lastError = Array.isArray(e.stack) ? e.stack.join(os.EOL) : e.stack;
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, retryInterval));
|
||||
trial++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function findElement(element: IElement, fn: (element: IElement) => boolean): IElement | null {
|
||||
|
||||
@@ -26,4 +26,4 @@ export * from './viewlet';
|
||||
export * from './localization';
|
||||
export * from './workbench';
|
||||
export * from './driver';
|
||||
export { getDevElectronPath, getBuildElectronPath, getBuildVersion } from './electronDriver';
|
||||
export { getDevElectronPath, getBuildElectronPath, getBuildVersion } from './electron';
|
||||
|
||||
@@ -8,202 +8,12 @@ import { ChildProcess, spawn } from 'child_process';
|
||||
import { join } from 'path';
|
||||
import { mkdir } from 'fs';
|
||||
import { promisify } from 'util';
|
||||
import { IDriver, IDisposable, IWindowDriver } from './driver';
|
||||
import { IDriver, IDisposable } from './driver';
|
||||
import { URI } from 'vscode-uri';
|
||||
import * as kill from 'tree-kill';
|
||||
import { PageFunction } from 'playwright-core/types/structs';
|
||||
import { Logger, measureAndLog } from './logger';
|
||||
import type { LaunchOptions } from './code';
|
||||
|
||||
export class PlaywrightDriver implements IDriver {
|
||||
|
||||
private static traceCounter = 1;
|
||||
|
||||
private static readonly vscodeToPlaywrightKey: { [key: string]: string } = {
|
||||
cmd: 'Meta',
|
||||
ctrl: 'Control',
|
||||
shift: 'Shift',
|
||||
enter: 'Enter',
|
||||
escape: 'Escape',
|
||||
right: 'ArrowRight',
|
||||
up: 'ArrowUp',
|
||||
down: 'ArrowDown',
|
||||
left: 'ArrowLeft',
|
||||
home: 'Home',
|
||||
esc: 'Escape'
|
||||
};
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
constructor(
|
||||
private readonly application: playwright.Browser | playwright.ElectronApplication,
|
||||
private readonly context: playwright.BrowserContext,
|
||||
private readonly page: playwright.Page,
|
||||
private readonly serverPid: number | undefined,
|
||||
private readonly options: LaunchOptions
|
||||
) {
|
||||
}
|
||||
|
||||
async getWindowIds() {
|
||||
return [1];
|
||||
}
|
||||
|
||||
async startTracing(windowId: number, name: string): Promise<void> {
|
||||
if (!this.options.tracing) {
|
||||
return; // tracing disabled
|
||||
}
|
||||
|
||||
try {
|
||||
await measureAndLog(this.context.tracing.startChunk({ title: name }), `startTracing for ${name}`, this.options.logger);
|
||||
} catch (error) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
async stopTracing(windowId: number, name: string, persist: boolean): Promise<void> {
|
||||
if (!this.options.tracing) {
|
||||
return; // tracing disabled
|
||||
}
|
||||
|
||||
try {
|
||||
let persistPath: string | undefined = undefined;
|
||||
if (persist) {
|
||||
persistPath = join(this.options.logsPath, `playwright-trace-${PlaywrightDriver.traceCounter++}-${name.replace(/\s+/g, '-')}.zip`);
|
||||
}
|
||||
|
||||
await measureAndLog(this.context.tracing.stopChunk({ path: persistPath }), `stopTracing for ${name}`, this.options.logger);
|
||||
} catch (error) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
async reload() {
|
||||
await this.page.reload();
|
||||
}
|
||||
|
||||
async exitApplication() {
|
||||
|
||||
// Stop tracing
|
||||
try {
|
||||
if (this.options.tracing) {
|
||||
await measureAndLog(this.context.tracing.stop(), 'stop tracing', this.options.logger);
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
// VSCode shutdown (desktop only)
|
||||
let mainPid: number | undefined = undefined;
|
||||
if (!this.options.web) {
|
||||
try {
|
||||
mainPid = await measureAndLog(this._evaluateWithDriver(([driver]) => (driver as unknown as IDriver).exitApplication()), 'driver.exitApplication()', this.options.logger);
|
||||
} catch (error) {
|
||||
this.options.logger.log(`Error exiting appliction (${error})`);
|
||||
}
|
||||
}
|
||||
|
||||
// Playwright shutdown
|
||||
try {
|
||||
await measureAndLog(this.application.close(), 'playwright.close()', this.options.logger);
|
||||
} catch (error) {
|
||||
this.options.logger.log(`Error closing appliction (${error})`);
|
||||
}
|
||||
|
||||
// Server shutdown
|
||||
if (typeof this.serverPid === 'number') {
|
||||
await measureAndLog(teardown(this.serverPid, this.options.logger), 'teardown server', this.options.logger);
|
||||
}
|
||||
|
||||
return mainPid ?? this.serverPid! /* when running web we must have a server Pid */;
|
||||
}
|
||||
|
||||
async dispatchKeybinding(windowId: number, keybinding: string) {
|
||||
const chords = keybinding.split(' ');
|
||||
for (let i = 0; i < chords.length; i++) {
|
||||
const chord = chords[i];
|
||||
if (i > 0) {
|
||||
await this.timeout(100);
|
||||
}
|
||||
|
||||
if (keybinding.startsWith('Alt') || keybinding.startsWith('Control') || keybinding.startsWith('Backspace')) {
|
||||
await this.page.keyboard.press(keybinding);
|
||||
return;
|
||||
}
|
||||
|
||||
const keys = chord.split('+');
|
||||
const keysDown: string[] = [];
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
if (keys[i] in PlaywrightDriver.vscodeToPlaywrightKey) {
|
||||
keys[i] = PlaywrightDriver.vscodeToPlaywrightKey[keys[i]];
|
||||
}
|
||||
await this.page.keyboard.down(keys[i]);
|
||||
keysDown.push(keys[i]);
|
||||
}
|
||||
while (keysDown.length > 0) {
|
||||
await this.page.keyboard.up(keysDown.pop()!);
|
||||
}
|
||||
}
|
||||
|
||||
await this.timeout(100);
|
||||
}
|
||||
|
||||
async click(windowId: number, selector: string, xoffset?: number | undefined, yoffset?: number | undefined) {
|
||||
const { x, y } = await this.getElementXY(windowId, selector, xoffset, yoffset);
|
||||
await this.page.mouse.click(x + (xoffset ? xoffset : 0), y + (yoffset ? yoffset : 0));
|
||||
}
|
||||
|
||||
async setValue(windowId: number, selector: string, text: string) {
|
||||
return this.page.evaluate(([driver, selector, text]) => driver.setValue(selector, text), [await this._getDriverHandle(), selector, text] as const);
|
||||
}
|
||||
|
||||
async getTitle(windowId: number) {
|
||||
return this._evaluateWithDriver(([driver]) => driver.getTitle());
|
||||
}
|
||||
|
||||
async isActiveElement(windowId: number, selector: string) {
|
||||
return this.page.evaluate(([driver, selector]) => driver.isActiveElement(selector), [await this._getDriverHandle(), selector] as const);
|
||||
}
|
||||
|
||||
async getElements(windowId: number, selector: string, recursive: boolean = false) {
|
||||
return this.page.evaluate(([driver, selector, recursive]) => driver.getElements(selector, recursive), [await this._getDriverHandle(), selector, recursive] as const);
|
||||
}
|
||||
|
||||
async getElementXY(windowId: number, selector: string, xoffset?: number, yoffset?: number) {
|
||||
return this.page.evaluate(([driver, selector, xoffset, yoffset]) => driver.getElementXY(selector, xoffset, yoffset), [await this._getDriverHandle(), selector, xoffset, yoffset] as const);
|
||||
}
|
||||
|
||||
async typeInEditor(windowId: number, selector: string, text: string) {
|
||||
return this.page.evaluate(([driver, selector, text]) => driver.typeInEditor(selector, text), [await this._getDriverHandle(), selector, text] as const);
|
||||
}
|
||||
|
||||
async getTerminalBuffer(windowId: number, selector: string) {
|
||||
return this.page.evaluate(([driver, selector]) => driver.getTerminalBuffer(selector), [await this._getDriverHandle(), selector] as const);
|
||||
}
|
||||
|
||||
async writeInTerminal(windowId: number, selector: string, text: string) {
|
||||
return this.page.evaluate(([driver, selector, text]) => driver.writeInTerminal(selector, text), [await this._getDriverHandle(), selector, text] as const);
|
||||
}
|
||||
|
||||
async getLocaleInfo(windowId: number) {
|
||||
return this._evaluateWithDriver(([driver]) => driver.getLocaleInfo());
|
||||
}
|
||||
|
||||
async getLocalizedStrings(windowId: number) {
|
||||
return this._evaluateWithDriver(([driver]) => driver.getLocalizedStrings());
|
||||
}
|
||||
|
||||
private async _evaluateWithDriver<T>(pageFunction: PageFunction<playwright.JSHandle<IWindowDriver>[], T>) {
|
||||
return this.page.evaluate(pageFunction, [await this._getDriverHandle()]);
|
||||
}
|
||||
|
||||
private timeout(ms: number): Promise<void> {
|
||||
return new Promise<void>(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
private async _getDriverHandle(): Promise<playwright.JSHandle<IWindowDriver>> {
|
||||
return this.page.evaluateHandle('window.driver');
|
||||
}
|
||||
}
|
||||
import { PlaywrightDriver } from './playwrightDriver';
|
||||
|
||||
const root = join(__dirname, '..', '..', '..');
|
||||
|
||||
@@ -316,7 +126,7 @@ async function launchBrowser(options: LaunchOptions, endpoint: string) {
|
||||
return { browser, context, page };
|
||||
}
|
||||
|
||||
async function teardown(serverPid: number | undefined, logger: Logger): Promise<void> {
|
||||
export async function teardown(serverPid: number | undefined, logger: Logger): Promise<void> {
|
||||
if (typeof serverPid !== 'number') {
|
||||
return;
|
||||
}
|
||||
202
test/automation/src/playwrightDriver.ts
Normal file
202
test/automation/src/playwrightDriver.ts
Normal file
@@ -0,0 +1,202 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as playwright from '@playwright/test';
|
||||
import { join } from 'path';
|
||||
import { IDriver, IWindowDriver } from './driver';
|
||||
import { PageFunction } from 'playwright-core/types/structs';
|
||||
import { measureAndLog } from './logger';
|
||||
import { LaunchOptions } from './code';
|
||||
import { teardown } from './playwrightBrowser';
|
||||
|
||||
export class PlaywrightDriver implements IDriver {
|
||||
|
||||
private static traceCounter = 1;
|
||||
|
||||
private static readonly vscodeToPlaywrightKey: { [key: string]: string } = {
|
||||
cmd: 'Meta',
|
||||
ctrl: 'Control',
|
||||
shift: 'Shift',
|
||||
enter: 'Enter',
|
||||
escape: 'Escape',
|
||||
right: 'ArrowRight',
|
||||
up: 'ArrowUp',
|
||||
down: 'ArrowDown',
|
||||
left: 'ArrowLeft',
|
||||
home: 'Home',
|
||||
esc: 'Escape'
|
||||
};
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
constructor(
|
||||
private readonly application: playwright.Browser | playwright.ElectronApplication,
|
||||
private readonly context: playwright.BrowserContext,
|
||||
private readonly page: playwright.Page,
|
||||
private readonly serverPid: number | undefined,
|
||||
private readonly options: LaunchOptions
|
||||
) {
|
||||
}
|
||||
|
||||
async getWindowIds() {
|
||||
return [1];
|
||||
}
|
||||
|
||||
async startTracing(windowId: number, name: string): Promise<void> {
|
||||
if (!this.options.tracing) {
|
||||
return; // tracing disabled
|
||||
}
|
||||
|
||||
try {
|
||||
await measureAndLog(this.context.tracing.startChunk({ title: name }), `startTracing for ${name}`, this.options.logger);
|
||||
} catch (error) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
async stopTracing(windowId: number, name: string, persist: boolean): Promise<void> {
|
||||
if (!this.options.tracing) {
|
||||
return; // tracing disabled
|
||||
}
|
||||
|
||||
try {
|
||||
let persistPath: string | undefined = undefined;
|
||||
if (persist) {
|
||||
persistPath = join(this.options.logsPath, `playwright-trace-${PlaywrightDriver.traceCounter++}-${name.replace(/\s+/g, '-')}.zip`);
|
||||
}
|
||||
|
||||
await measureAndLog(this.context.tracing.stopChunk({ path: persistPath }), `stopTracing for ${name}`, this.options.logger);
|
||||
} catch (error) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
async reload() {
|
||||
await this.page.reload();
|
||||
}
|
||||
|
||||
async exitApplication() {
|
||||
|
||||
// Stop tracing
|
||||
try {
|
||||
if (this.options.tracing) {
|
||||
await measureAndLog(this.context.tracing.stop(), 'stop tracing', this.options.logger);
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
// VSCode shutdown (desktop only)
|
||||
let mainPid: number | undefined = undefined;
|
||||
if (!this.options.web) {
|
||||
try {
|
||||
mainPid = await measureAndLog(this._evaluateWithDriver(([driver]) => (driver as unknown as IDriver).exitApplication()), 'driver.exitApplication()', this.options.logger);
|
||||
} catch (error) {
|
||||
this.options.logger.log(`Error exiting appliction (${error})`);
|
||||
}
|
||||
}
|
||||
|
||||
// Playwright shutdown
|
||||
try {
|
||||
await measureAndLog(this.application.close(), 'playwright.close()', this.options.logger);
|
||||
} catch (error) {
|
||||
this.options.logger.log(`Error closing appliction (${error})`);
|
||||
}
|
||||
|
||||
// Server shutdown
|
||||
if (typeof this.serverPid === 'number') {
|
||||
await measureAndLog(teardown(this.serverPid, this.options.logger), 'teardown server', this.options.logger);
|
||||
}
|
||||
|
||||
return mainPid ?? this.serverPid! /* when running web we must have a server Pid */;
|
||||
}
|
||||
|
||||
async dispatchKeybinding(windowId: number, keybinding: string) {
|
||||
const chords = keybinding.split(' ');
|
||||
for (let i = 0; i < chords.length; i++) {
|
||||
const chord = chords[i];
|
||||
if (i > 0) {
|
||||
await this.timeout(100);
|
||||
}
|
||||
|
||||
if (keybinding.startsWith('Alt') || keybinding.startsWith('Control') || keybinding.startsWith('Backspace')) {
|
||||
await this.page.keyboard.press(keybinding);
|
||||
return;
|
||||
}
|
||||
|
||||
const keys = chord.split('+');
|
||||
const keysDown: string[] = [];
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
if (keys[i] in PlaywrightDriver.vscodeToPlaywrightKey) {
|
||||
keys[i] = PlaywrightDriver.vscodeToPlaywrightKey[keys[i]];
|
||||
}
|
||||
await this.page.keyboard.down(keys[i]);
|
||||
keysDown.push(keys[i]);
|
||||
}
|
||||
while (keysDown.length > 0) {
|
||||
await this.page.keyboard.up(keysDown.pop()!);
|
||||
}
|
||||
}
|
||||
|
||||
await this.timeout(100);
|
||||
}
|
||||
|
||||
async click(windowId: number, selector: string, xoffset?: number | undefined, yoffset?: number | undefined) {
|
||||
const { x, y } = await this.getElementXY(windowId, selector, xoffset, yoffset);
|
||||
await this.page.mouse.click(x + (xoffset ? xoffset : 0), y + (yoffset ? yoffset : 0));
|
||||
}
|
||||
|
||||
async setValue(windowId: number, selector: string, text: string) {
|
||||
return this.page.evaluate(([driver, selector, text]) => driver.setValue(selector, text), [await this._getDriverHandle(), selector, text] as const);
|
||||
}
|
||||
|
||||
async getTitle(windowId: number) {
|
||||
return this._evaluateWithDriver(([driver]) => driver.getTitle());
|
||||
}
|
||||
|
||||
async isActiveElement(windowId: number, selector: string) {
|
||||
return this.page.evaluate(([driver, selector]) => driver.isActiveElement(selector), [await this._getDriverHandle(), selector] as const);
|
||||
}
|
||||
|
||||
async getElements(windowId: number, selector: string, recursive: boolean = false) {
|
||||
return this.page.evaluate(([driver, selector, recursive]) => driver.getElements(selector, recursive), [await this._getDriverHandle(), selector, recursive] as const);
|
||||
}
|
||||
|
||||
async getElementXY(windowId: number, selector: string, xoffset?: number, yoffset?: number) {
|
||||
return this.page.evaluate(([driver, selector, xoffset, yoffset]) => driver.getElementXY(selector, xoffset, yoffset), [await this._getDriverHandle(), selector, xoffset, yoffset] as const);
|
||||
}
|
||||
|
||||
async typeInEditor(windowId: number, selector: string, text: string) {
|
||||
return this.page.evaluate(([driver, selector, text]) => driver.typeInEditor(selector, text), [await this._getDriverHandle(), selector, text] as const);
|
||||
}
|
||||
|
||||
async getTerminalBuffer(windowId: number, selector: string) {
|
||||
return this.page.evaluate(([driver, selector]) => driver.getTerminalBuffer(selector), [await this._getDriverHandle(), selector] as const);
|
||||
}
|
||||
|
||||
async writeInTerminal(windowId: number, selector: string, text: string) {
|
||||
return this.page.evaluate(([driver, selector, text]) => driver.writeInTerminal(selector, text), [await this._getDriverHandle(), selector, text] as const);
|
||||
}
|
||||
|
||||
async getLocaleInfo(windowId: number) {
|
||||
return this._evaluateWithDriver(([driver]) => driver.getLocaleInfo());
|
||||
}
|
||||
|
||||
async getLocalizedStrings(windowId: number) {
|
||||
return this._evaluateWithDriver(([driver]) => driver.getLocalizedStrings());
|
||||
}
|
||||
|
||||
private async _evaluateWithDriver<T>(pageFunction: PageFunction<playwright.JSHandle<IWindowDriver>[], T>) {
|
||||
return this.page.evaluate(pageFunction, [await this._getDriverHandle()]);
|
||||
}
|
||||
|
||||
private timeout(ms: number): Promise<void> {
|
||||
return new Promise<void>(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
private async _getDriverHandle(): Promise<playwright.JSHandle<IWindowDriver>> {
|
||||
return this.page.evaluateHandle('window.driver');
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,8 @@
|
||||
import * as playwright from '@playwright/test';
|
||||
import { IDriver, IDisposable } from './driver';
|
||||
import type { LaunchOptions } from './code';
|
||||
import { PlaywrightDriver } from './playwrightBrowserDriver';
|
||||
import { IElectronConfiguration, resolveElectronConfiguration } from './electronDriver';
|
||||
import { PlaywrightDriver } from './playwrightDriver';
|
||||
import { IElectronConfiguration, resolveElectronConfiguration } from './electron';
|
||||
import { measureAndLog } from './logger';
|
||||
|
||||
export async function launch(options: LaunchOptions): Promise<{ client: IDisposable; driver: IDriver }> {
|
||||
Reference in New Issue
Block a user