Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/microsoft/vscode.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'test/automation/src/code.ts')
-rw-r--r--test/automation/src/code.ts229
1 files changed, 91 insertions, 138 deletions
diff --git a/test/automation/src/code.ts b/test/automation/src/code.ts
index 33eba97ba52..5e6b52c7d73 100644
--- a/test/automation/src/code.ts
+++ b/test/automation/src/code.ts
@@ -6,31 +6,31 @@
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 { IElement, ILocalizedStrings, ILocaleInfo } from './driver';
+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';
+import { teardown } from './processes';
+import { PlaywrightDriver } from './playwrightDriver';
const rootPath = join(__dirname, '../../..');
export interface LaunchOptions {
codePath?: string;
- workspacePath: string;
+ readonly workspacePath: string;
userDataDir: string;
- extensionsPath: string;
- logger: Logger;
+ readonly extensionsPath: string;
+ readonly logger: Logger;
logsPath: string;
- verbose?: boolean;
- extraArgs?: string[];
- remote?: boolean;
- web?: boolean;
- legacy?: boolean;
- tracing?: boolean;
- headless?: boolean;
- browser?: 'chromium' | 'webkit' | 'firefox';
+ readonly verbose?: boolean;
+ readonly extraArgs?: string[];
+ readonly remote?: boolean;
+ readonly web?: boolean;
+ readonly tracing?: boolean;
+ readonly headless?: boolean;
+ readonly browser?: 'chromium' | 'webkit' | 'firefox';
}
interface ICodeInstance {
@@ -39,8 +39,8 @@ interface ICodeInstance {
const instances = new Set<ICodeInstance>();
-function registerInstance(process: cp.ChildProcess, logger: Logger, type: string, kill: () => Promise<void>) {
- const instance = { kill };
+function registerInstance(process: cp.ChildProcess, logger: Logger, type: string) {
+ const instance = { kill: () => teardown(process, logger) };
instances.add(instance);
process.stdout?.on('data', data => logger.log(`[${type}] stdout: ${data}`));
@@ -53,7 +53,7 @@ function registerInstance(process: cp.ChildProcess, logger: Logger, type: string
});
}
-async function teardown(signal?: number) {
+async function teardownAll(signal?: number) {
stopped = true;
for (const instance of instances) {
@@ -66,9 +66,9 @@ async function teardown(signal?: number) {
}
let stopped = false;
-process.on('exit', () => teardown());
-process.on('SIGINT', () => teardown(128 + 2)); // https://nodejs.org/docs/v14.16.0/api/process.html#process_signal_events
-process.on('SIGTERM', () => teardown(128 + 15)); // same as above
+process.on('exit', () => teardownAll());
+process.on('SIGINT', () => teardownAll(128 + 2)); // https://nodejs.org/docs/v14.16.0/api/process.html#process_signal_events
+process.on('SIGTERM', () => teardownAll(128 + 15)); // same as above
export async function launch(options: LaunchOptions): Promise<Code> {
if (stopped) {
@@ -79,73 +79,29 @@ export async function launch(options: LaunchOptions): Promise<Code> {
// Browser smoke tests
if (options.web) {
- const { serverProcess, client, driver, kill } = await measureAndLog(launchPlaywrightBrowser(options), 'launch playwright (browser)', options.logger);
- registerInstance(serverProcess, options.logger, 'server', kill);
+ const { serverProcess, driver } = await measureAndLog(launchPlaywrightBrowser(options), 'launch playwright (browser)', options.logger);
+ registerInstance(serverProcess, options.logger, 'server');
- return new Code(client, driver, options.logger);
+ return new Code(driver, options.logger, serverProcess);
}
// Electron smoke tests (playwright)
- else if (!options.legacy) {
- const { client, driver } = await measureAndLog(launchPlaywrightElectron(options), 'launch playwright (electron)', options.logger);
-
- return new Code(client, driver, options.logger);
- }
-
- // Electron smoke tests (legacy driver)
else {
- const { electronProcess, client, driver, kill } = await measureAndLog(launchElectron(options), 'launch electron', options.logger);
- registerInstance(electronProcess, options.logger, 'electron', kill);
-
- return new Code(client, driver, options.logger);
- }
-}
-
-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;
- }
+ const { electronProcess, driver } = await measureAndLog(launchPlaywrightElectron(options), 'launch playwright (electron)', options.logger);
+ registerInstance(electronProcess, options.logger, 'electron');
- await new Promise(resolve => setTimeout(resolve, retryInterval));
- trial++;
+ return new Code(driver, options.logger, electronProcess);
}
}
export class Code {
- private _activeWindowId: number | undefined = undefined;
- driver: IDriver;
+ readonly driver: PlaywrightDriver;
constructor(
- private client: IDisposable,
- driver: IDriver,
- readonly logger: Logger
+ driver: PlaywrightDriver,
+ readonly logger: Logger,
+ private readonly mainProcess: cp.ChildProcess
) {
this.driver = new Proxy(driver, {
get(target, prop) {
@@ -166,42 +122,28 @@ export class Code {
});
}
- async capturePage(): Promise<string> {
- const windowId = await this.getActiveWindowId();
- return await this.driver.capturePage(windowId);
- }
-
async startTracing(name: string): Promise<void> {
- const windowId = await this.getActiveWindowId();
- if (typeof this.driver.startTracing === 'function') { // added only in 1.64
- return await this.driver.startTracing(windowId, name);
- }
+ return await this.driver.startTracing(name);
}
async stopTracing(name: string, persist: boolean): Promise<void> {
- const windowId = await this.getActiveWindowId();
- if (typeof this.driver.stopTracing === 'function') { // added only in 1.64
- return await this.driver.stopTracing(windowId, name, persist);
- }
- }
-
- async waitForWindowIds(accept: (windowIds: number[]) => boolean): Promise<void> {
- await poll(() => this.driver.getWindowIds(), accept, this.logger, `get window ids`);
+ return await this.driver.stopTracing(name, persist);
}
async dispatchKeybinding(keybinding: string): Promise<void> {
- const windowId = await this.getActiveWindowId();
- await this.driver.dispatchKeybinding(windowId, keybinding);
+ await this.driver.dispatchKeybinding(keybinding);
}
async exit(): Promise<void> {
-
- // Start the exit flow via driver
- const pid = await measureAndLog(this.driver.exitApplication(), 'driver.exitApplication()', this.logger);
-
return measureAndLog(new Promise<void>((resolve, reject) => {
+ const pid = this.mainProcess.pid!;
+
let done = false;
+ // Start the exit flow via driver
+ this.driver.exitApplication();
+
+ // Await the exit of the application
(async () => {
let retries = 0;
while (!done) {
@@ -212,7 +154,12 @@ export class Code {
// no need to await since we're polling for the process to die anyways
treekill(pid, err => {
- this.logger.log('Failed to kill Electron process tree:', err?.message);
+ try {
+ process.kill(pid, 0); // throws an exception if the process doesn't exist anymore
+ this.logger.log('Failed to kill Electron process tree:', err?.message);
+ } catch (error) {
+ // Expected when process is gone
+ }
});
}
@@ -230,92 +177,98 @@ export class Code {
}
}
})();
- }).finally(() => {
- this.dispose();
}), 'Code#exit()', this.logger);
}
async waitForTextContent(selector: string, textContent?: string, accept?: (result: string) => boolean, retryCount?: number): Promise<string> {
- const windowId = await this.getActiveWindowId();
accept = accept || (result => textContent !== undefined ? textContent === result : !!result);
- return await 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'))),
+ return await this.poll(
+ () => this.driver.getElements(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
);
}
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(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(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(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(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(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(), 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(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(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(selector, value), () => true, `writeInTerminal '${selector}'`);
}
async getLocaleInfo(): Promise<ILocaleInfo> {
- const windowId = await this.getActiveWindowId();
- return this.driver.getLocaleInfo(windowId);
+ return this.driver.getLocaleInfo();
}
async getLocalizedStrings(): Promise<ILocalizedStrings> {
- const windowId = await this.getActiveWindowId();
- return this.driver.getLocalizedStrings(windowId);
+ return this.driver.getLocalizedStrings();
}
- private async getActiveWindowId(): Promise<number> {
- if (typeof this._activeWindowId !== 'number') {
- this.logger.log('getActiveWindowId(): begin');
- const windows = await this.driver.getWindowIds();
- this._activeWindowId = windows[0];
- this.logger.log(`getActiveWindowId(): end (windowId=${this._activeWindowId})`);
- }
+ private async poll<T>(
+ fn: () => Promise<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.`);
+ }
- return this._activeWindowId;
- }
+ 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;
+ }
- dispose(): void {
- this.client.dispose();
+ await new Promise(resolve => setTimeout(resolve, retryInterval));
+ trial++;
+ }
}
}