diff --git a/src/vs/platform/driver/common/driver.ts b/src/vs/platform/driver/common/driver.ts index f859f45075ba61cd98224bc899d9922c711fce91..6abbc5d59ad43e674216aead732316bae8cac4d8 100644 --- a/src/vs/platform/driver/common/driver.ts +++ b/src/vs/platform/driver/common/driver.ts @@ -34,7 +34,7 @@ export interface IDriver { setValue(windowId: number, selector: string, text: string): TPromise; getTitle(windowId: number): TPromise; isActiveElement(windowId: number, selector: string): TPromise; - getElements(windowId: number, selector: string, recursive: boolean): TPromise; + getElements(windowId: number, selector: string, recursive?: boolean): TPromise; typeInEditor(windowId: number, selector: string, text: string): TPromise; getTerminalBuffer(windowId: number, selector: string): TPromise; } diff --git a/test/smoke/src/api.ts b/test/smoke/src/api.ts deleted file mode 100644 index f11ae031575fb0e8bc23adbaa82f20fd1315aad6..0000000000000000000000000000000000000000 --- a/test/smoke/src/api.ts +++ /dev/null @@ -1,267 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IDriver, IElement } from './vscode/driver'; - -export class CodeDriver { - - constructor( - private driver: IDriver, - private verbose: boolean - ) { } - - private _activeWindowId: number | undefined = undefined; - - async dispatchKeybinding(keybinding: string): Promise { - if (this.verbose) { - console.log('- dispatchKeybinding:', keybinding); - } - - const windowId = await this.getWindowId(); - await this.driver.dispatchKeybinding(windowId, keybinding); - } - - async click(selector: string, xoffset?: number | undefined, yoffset?: number | undefined): Promise { - if (this.verbose) { - console.log('- click:', selector); - } - - const windowId = await this.getWindowId(); - await this.driver.click(windowId, selector, xoffset, yoffset); - } - - async doubleClick(selector: string): Promise { - if (this.verbose) { - console.log('- doubleClick:', selector); - } - - const windowId = await this.getWindowId(); - await this.driver.doubleClick(windowId, selector); - } - - async move(selector: string): Promise { - if (this.verbose) { - console.log('- move:', selector); - } - - const windowId = await this.getWindowId(); - await this.driver.move(windowId, selector); - } - - async setValue(selector: string, text: string): Promise { - if (this.verbose) { - console.log('- setValue:', selector, text); - } - - const windowId = await this.getWindowId(); - await this.driver.setValue(windowId, selector, text); - } - - async getTitle(): Promise { - if (this.verbose) { - console.log('- getTitle:'); - } - - const windowId = await this.getWindowId(); - return await this.driver.getTitle(windowId); - } - - async isActiveElement(selector: string): Promise { - if (this.verbose) { - console.log('- isActiveElement:', selector); - } - - const windowId = await this.getWindowId(); - return await this.driver.isActiveElement(windowId, selector); - } - - async getElements(selector: string, recursive = false): Promise { - if (this.verbose) { - console.log('- getElements:', selector); - } - - const windowId = await this.getWindowId(); - return await this.driver.getElements(windowId, selector, recursive); - } - - async typeInEditor(selector: string, text: string): Promise { - if (this.verbose) { - console.log('- typeInEditor:', selector, text); - } - - const windowId = await this.getWindowId(); - return await this.driver.typeInEditor(windowId, selector, text); - } - - async getTerminalBuffer(selector: string): Promise { - if (this.verbose) { - console.log('- getTerminalBuffer:', selector); - } - - const windowId = await this.getWindowId(); - return await this.driver.getTerminalBuffer(windowId, selector); - } - - private async getWindowId(): Promise { - if (typeof this._activeWindowId !== 'number') { - const windows = await this.driver.getWindowIds(); - this._activeWindowId = windows[0]; - } - - return this._activeWindowId; - } -} - -export function findElement(element: IElement, fn: (element: IElement) => boolean): IElement | null { - const queue = [element]; - - while (queue.length > 0) { - const element = queue.shift()!; - - if (fn(element)) { - return element; - } - - queue.push(...element.children); - } - - return null; -} - -export function findElements(element: IElement, fn: (element: IElement) => boolean): IElement[] { - const result: IElement[] = []; - const queue = [element]; - - while (queue.length > 0) { - const element = queue.shift()!; - - if (fn(element)) { - result.push(element); - } - - queue.push(...element.children); - } - - return result; -} - -export class API { - - // waitFor calls should not take more than 200 * 100 = 20 seconds to complete, excluding - // the time it takes for the actual retry call to complete - private retryCount: number; - private readonly retryDuration = 100; // in milliseconds - - constructor( - private driver: CodeDriver, - waitTime: number - ) { - this.retryCount = (waitTime * 1000) / this.retryDuration; - } - - dispatchKeybinding(keybinding: string): Promise { - return this.driver.dispatchKeybinding(keybinding); - } - - waitForTextContent(selector: string, textContent?: string, accept?: (result: string) => boolean): Promise { - accept = accept ? accept : (result => textContent !== void 0 ? textContent === result : !!result); - return this.waitFor(() => this.driver.getElements(selector).then(els => els[0].textContent), s => accept!(typeof s === 'string' ? s : ''), `getTextContent with selector ${selector}`); - } - - async waitAndClick(selector: string, xoffset?: number, yoffset?: number): Promise { - await this.waitForElement(selector); - return await this.driver.click(selector, xoffset, yoffset); - } - - async waitAndDoubleClick(selector: string): Promise { - await this.waitForElement(selector); - return await this.driver.doubleClick(selector); - } - - async waitAndMove(selector: string): Promise { - await this.waitForElement(selector); - return await this.driver.move(selector); - } - - async setValue(selector: string, text: string): Promise { - await this.waitForElement(selector); - return await this.driver.setValue(selector, text); - } - - async doesElementExist(selector: string): Promise { - const elements = await this.driver.getElements(selector); - return elements.length > 0; - } - - async getElementCount(selector: string): Promise { - const elements = await this.driver.getElements(selector); - return elements.length; - } - - waitForElements(selector: string, recursive: boolean, accept: (result: IElement[]) => boolean = result => result.length > 0): Promise { - return this.waitFor(() => this.driver.getElements(selector, recursive), accept, `elements with selector ${selector}`) as Promise; - } - - waitForElement(selector: string, accept: (result: IElement | undefined) => boolean = result => !!result): Promise { - return this.waitFor(() => this.driver.getElements(selector).then(els => els[0]), accept, `element with selector ${selector}`) as Promise; - } - - waitForActiveElement(selector: string): Promise { - return this.waitFor(() => this.driver.isActiveElement(selector), undefined, `wait for active element: ${selector}`); - } - - getTitle(): Promise { - return this.driver.getTitle(); - } - - typeInEditor(selector: string, text: string): Promise { - return this.driver.typeInEditor(selector, text); - } - - getTerminalBuffer(selector: string): Promise { - return this.driver.getTerminalBuffer(selector); - } - - private running = false; - async waitFor(func: () => T | Promise, accept?: (result: T) => boolean | Promise, timeoutMessage?: string, retryCount?: number): Promise; - async waitFor(func: () => T | Promise, accept: (result: T) => boolean | Promise = result => !!result, timeoutMessage?: string, retryCount?: number): Promise { - if (this.running) { - throw new Error('Not allowed to run nested waitFor calls!'); - } - - this.running = true; - - try { - let trial = 1; - retryCount = typeof retryCount === 'number' ? retryCount : this.retryCount; - - while (true) { - if (trial > retryCount) { - throw new Error(`${timeoutMessage}: Timed out after ${(retryCount * this.retryDuration) / 1000} seconds.`); - } - - let result; - try { - result = await func(); - } catch (e) { - // console.warn(e); - - if (/Method not implemented/.test(e.message)) { - throw e; - } - } - - if (accept(result)) { - return result; - } - - await new Promise(resolve => setTimeout(resolve, this.retryDuration)); - trial++; - } - } finally { - this.running = false; - } - } -} \ No newline at end of file diff --git a/test/smoke/src/application.ts b/test/smoke/src/application.ts index 0b43ca0b346773cac8452245dc3d7a21d2c364e3..b8f57eae65f16a8ec7ef275a09b30c5e093c5675 100644 --- a/test/smoke/src/application.ts +++ b/test/smoke/src/application.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { API, CodeDriver } from './api'; import { Workbench } from './areas/workbench/workbench'; import * as fs from 'fs'; import * as cp from 'child_process'; @@ -25,9 +24,8 @@ export interface ApplicationOptions extends SpawnOptions { export class Application { - private _api: API; + private _code: Code | undefined; private _workbench: Workbench; - private codeInstance: Code | undefined; private keybindings: any[]; private stopLogCollection: (() => Promise) | undefined; @@ -37,8 +35,8 @@ export class Application { return this.options.quality; } - get api(): API { - return this._api; + get code(): Code { + return this._code!; } get workbench(): Workbench { @@ -97,14 +95,14 @@ export class Application { this.stopLogCollection = undefined; } - if (this.codeInstance) { - this.codeInstance.dispose(); - this.codeInstance = undefined; + if (this._code) { + this._code.dispose(); + this._code = undefined; } } private async startApplication(workspaceOrFolder: string, extraArgs: string[] = []): Promise { - this.codeInstance = await spawn({ + this._code = await spawn({ codePath: this.options.codePath, workspacePath: workspaceOrFolder, userDataDir: this.options.userDataDir, @@ -113,13 +111,11 @@ export class Application { extraArgs }); - const driver = new CodeDriver(this.codeInstance.driver, this.options.verbose); - this._api = new API(driver, this.options.waitTime); - this._workbench = new Workbench(this._api, this.keybindings, this.userDataPath); + this._workbench = new Workbench(this._code, this.keybindings, this.userDataPath); } private async checkWindowReady(): Promise { - if (!this.codeInstance) { + if (!this._code) { console.error('No code instance found'); return; } @@ -127,7 +123,7 @@ export class Application { let retries = 0; while (++retries < 300) { // 30 seconds - const ids = await this.codeInstance.driver.getWindowIds(); + const ids = await this._code.getWindowIds(); if (ids.length > 0) { break; @@ -136,12 +132,12 @@ export class Application { await new Promise(c => setTimeout(c, 100)); } - await this.api.waitForElement('.monaco-workbench'); + await this.code.waitForElement('.monaco-workbench'); } private async waitForWelcome(): Promise { - await this.api.waitForElement('.explorer-folders-view'); - await this.api.waitForElement(`.editor-container[id="workbench.editor.walkThroughPart"] .welcomePage`); + await this.code.waitForElement('.explorer-folders-view'); + await this.code.waitForElement(`.editor-container[id="workbench.editor.walkThroughPart"] .welcomePage`); } private retrieveKeybindings(): Promise { diff --git a/test/smoke/src/areas/activitybar/activityBar.ts b/test/smoke/src/areas/activitybar/activityBar.ts index 46665d64009e8442eee1ab0804e30f4f9bac1859..22d43a8f8c9444fabe7c19924fdc8c5b5d73bd08 100644 --- a/test/smoke/src/areas/activitybar/activityBar.ts +++ b/test/smoke/src/areas/activitybar/activityBar.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { API } from '../../api'; +import { Code } from '../../vscode/code'; export enum ActivityBarPosition { LEFT = 0, @@ -12,11 +12,9 @@ export enum ActivityBarPosition { export class ActivityBar { - constructor(private api: API) { - // noop - } + constructor(private code: Code) { } - public async getActivityBar(position: ActivityBarPosition): Promise { + async getActivityBar(position: ActivityBarPosition): Promise { let positionClass: string; if (position === ActivityBarPosition.LEFT) { @@ -27,6 +25,6 @@ export class ActivityBar { throw new Error('No such position for activity bar defined.'); } - return this.api.waitForElement(`.part.activitybar.${positionClass}`); + await this.code.waitForElement(`.part.activitybar.${positionClass}`); } } \ No newline at end of file diff --git a/test/smoke/src/areas/css/css.test.ts b/test/smoke/src/areas/css/css.test.ts index 10f473a59a97aeb0636a83ffcf508dfec2508ff0..176e634bf6d7a0b16371ac086098cadf29e8aed8 100644 --- a/test/smoke/src/areas/css/css.test.ts +++ b/test/smoke/src/areas/css/css.test.ts @@ -21,10 +21,10 @@ export function setup() { await app.workbench.quickopen.openFile('style.css'); await app.workbench.editor.waitForTypeInEditor('style.css', '.foo{}'); - await app.api.waitForElement(Problems.getSelectorInEditor(ProblemSeverity.WARNING)); + await app.code.waitForElement(Problems.getSelectorInEditor(ProblemSeverity.WARNING)); await app.workbench.problems.showProblemsView(); - await app.api.waitForElement(Problems.getSelectorInProblemsView(ProblemSeverity.WARNING)); + await app.code.waitForElement(Problems.getSelectorInProblemsView(ProblemSeverity.WARNING)); await app.workbench.problems.hideProblemsView(); }); @@ -34,11 +34,11 @@ export function setup() { await app.workbench.quickopen.openFile('style.css'); await app.workbench.editor.waitForTypeInEditor('style.css', '.foo{}'); - await app.api.waitForElement(Problems.getSelectorInEditor(ProblemSeverity.ERROR)); + await app.code.waitForElement(Problems.getSelectorInEditor(ProblemSeverity.ERROR)); - const problems = new Problems(app.api, app.workbench); + const problems = new Problems(app.code, app.workbench); await problems.showProblemsView(); - await app.api.waitForElement(Problems.getSelectorInProblemsView(ProblemSeverity.ERROR)); + await app.code.waitForElement(Problems.getSelectorInProblemsView(ProblemSeverity.ERROR)); await problems.hideProblemsView(); }); }); diff --git a/test/smoke/src/areas/debug/debug.test.ts b/test/smoke/src/areas/debug/debug.test.ts index 45710a1b6f71140f28e10e2edc3fb73914e0f664..b193d856cfa48b60f730259ac4037009525f9125 100644 --- a/test/smoke/src/areas/debug/debug.test.ts +++ b/test/smoke/src/areas/debug/debug.test.ts @@ -62,16 +62,16 @@ export function setup() { it('focus stack frames and variables', async function () { const app = this.app as Application; - await app.api.waitFor(() => app.workbench.debug.getLocalVariableCount(), c => c === 4, 'there should be 4 local variables'); + await app.workbench.debug.waitForVariableCount(4); await app.workbench.debug.focusStackFrame('layer.js', 'looking for layer.js'); - await app.api.waitFor(() => app.workbench.debug.getLocalVariableCount(), c => c === 5, 'there should be 5 local variables'); + await app.workbench.debug.waitForVariableCount(5); await app.workbench.debug.focusStackFrame('route.js', 'looking for route.js'); - await app.api.waitFor(() => app.workbench.debug.getLocalVariableCount(), c => c === 3, 'there should be 3 local variables'); + await app.workbench.debug.waitForVariableCount(3); await app.workbench.debug.focusStackFrame('index.js', 'looking for index.js'); - await app.api.waitFor(() => app.workbench.debug.getLocalVariableCount(), c => c === 4, 'there should be 4 local variables'); + await app.workbench.debug.waitForVariableCount(4); }); it('stepOver, stepIn, stepOut', async function () { diff --git a/test/smoke/src/areas/debug/debug.ts b/test/smoke/src/areas/debug/debug.ts index f19b3b5143d85faa3b2e144fdd3d3a87b6668141..8ee59bb86755b31d8ab5690b290dfa6dc0cd324c 100644 --- a/test/smoke/src/areas/debug/debug.ts +++ b/test/smoke/src/areas/debug/debug.ts @@ -5,7 +5,7 @@ import { Viewlet } from '../workbench/viewlet'; import { Commands } from '../workbench/workbench'; -import { API, findElement } from '../../api'; +import { Code, findElement } from '../../vscode/code'; import { Editors } from '../editor/editors'; import { Editor } from '../editor/editor'; import { IElement } from '../../vscode/driver'; @@ -50,30 +50,30 @@ function toStackFrame(element: IElement): IStackFrame { export class Debug extends Viewlet { - constructor(api: API, private commands: Commands, private editors: Editors, private editor: Editor) { - super(api); + constructor(code: Code, private commands: Commands, private editors: Editors, private editor: Editor) { + super(code); } async openDebugViewlet(): Promise { await this.commands.runCommand('workbench.view.debug'); - await this.api.waitForElement(DEBUG_VIEW); + await this.code.waitForElement(DEBUG_VIEW); } async configure(): Promise { - await this.api.waitAndClick(CONFIGURE); + await this.code.waitAndClick(CONFIGURE); await this.editors.waitForEditorFocus('launch.json'); } async setBreakpointOnLine(lineNumber: number): Promise { - await this.api.waitForElement(`${GLYPH_AREA}(${lineNumber})`); - await this.api.waitAndClick(`${GLYPH_AREA}(${lineNumber})`, 5, 5); - await this.api.waitForElement(BREAKPOINT_GLYPH); + await this.code.waitForElement(`${GLYPH_AREA}(${lineNumber})`); + await this.code.waitAndClick(`${GLYPH_AREA}(${lineNumber})`, 5, 5); + await this.code.waitForElement(BREAKPOINT_GLYPH); } async startDebugging(): Promise { await this.commands.runCommand('workbench.action.debug.start'); - await this.api.waitForElement(PAUSE); - await this.api.waitForElement(DEBUG_STATUS_BAR); + await this.code.waitForElement(PAUSE); + await this.code.waitForElement(DEBUG_STATUS_BAR); const portPrefix = 'Port: '; const output = await this.waitForOutput(output => output.some(line => line.indexOf(portPrefix) >= 0)); @@ -83,60 +83,60 @@ export class Debug extends Viewlet { } async stepOver(): Promise { - await this.api.waitAndClick(STEP_OVER); + await this.code.waitAndClick(STEP_OVER); } async stepIn(): Promise { - await this.api.waitAndClick(STEP_IN); + await this.code.waitAndClick(STEP_IN); } async stepOut(): Promise { - await this.api.waitAndClick(STEP_OUT); + await this.code.waitAndClick(STEP_OUT); } async continue(): Promise { - await this.api.waitAndClick(CONTINUE); + await this.code.waitAndClick(CONTINUE); await this.waitForStackFrameLength(0); } async stopDebugging(): Promise { - await this.api.waitAndClick(STOP); - await this.api.waitForElement(TOOLBAR_HIDDEN); - await this.api.waitForElement(NOT_DEBUG_STATUS_BAR); + await this.code.waitAndClick(STOP); + await this.code.waitForElement(TOOLBAR_HIDDEN); + await this.code.waitForElement(NOT_DEBUG_STATUS_BAR); } async waitForStackFrame(func: (stackFrame: IStackFrame) => boolean, message: string): Promise { - const elements = await this.api.waitForElements(STACK_FRAME, true, elements => elements.some(e => func(toStackFrame(e)))); + const elements = await this.code.waitForElements(STACK_FRAME, true, elements => elements.some(e => func(toStackFrame(e)))); return elements.map(toStackFrame).filter(s => func(s))[0]; } async waitForStackFrameLength(length: number): Promise { - await this.api.waitForElements(STACK_FRAME, false, result => result.length === length); + await this.code.waitForElements(STACK_FRAME, false, result => result.length === length); } async focusStackFrame(name: string, message: string): Promise { - await this.api.waitAndClick(SPECIFIC_STACK_FRAME(name)); + await this.code.waitAndClick(SPECIFIC_STACK_FRAME(name)); await this.editors.waitForTab(name); } async waitForReplCommand(text: string, accept: (result: string) => boolean): Promise { await this.commands.runCommand('Debug: Focus Debug Console'); - await this.api.waitForActiveElement(REPL_FOCUSED); - await this.api.setValue(REPL_FOCUSED, text); + await this.code.waitForActiveElement(REPL_FOCUSED); + await this.code.setValue(REPL_FOCUSED, text); // Wait for the keys to be picked up by the editor model such that repl evalutes what just got typed await this.editor.waitForEditorContents('debug:input', s => s.indexOf(text) >= 0); - await this.api.dispatchKeybinding('enter'); - await this.api.waitForElement(CONSOLE_INPUT_OUTPUT); + await this.code.dispatchKeybinding('enter'); + await this.code.waitForElement(CONSOLE_INPUT_OUTPUT); await this.waitForOutput(output => accept(output[output.length - 1] || '')); } - async getLocalVariableCount(): Promise { - return await this.api.getElementCount(VARIABLE); + async waitForVariableCount(count: number): Promise { + await this.code.waitForElements(VARIABLE, false, els => els.length === count); } private async waitForOutput(fn: (output: string[]) => boolean): Promise { - const elements = await this.api.waitForElements(CONSOLE_OUTPUT, false, elements => fn(elements.map(e => e.textContent))); + const elements = await this.code.waitForElements(CONSOLE_OUTPUT, false, elements => fn(elements.map(e => e.textContent))); return elements.map(e => e.textContent); } } diff --git a/test/smoke/src/areas/editor/editor.ts b/test/smoke/src/areas/editor/editor.ts index fdb5f4ca9c24e634efef42e4e7aac15792dd19f5..8bc52eaeaf54c8123808c3806afd2f02a8d2096b 100644 --- a/test/smoke/src/areas/editor/editor.ts +++ b/test/smoke/src/areas/editor/editor.ts @@ -5,7 +5,7 @@ import { References } from './peek'; import { Commands } from '../workbench/workbench'; -import { API } from '../../api'; +import { Code } from '../../vscode/code'; const RENAME_BOX = '.monaco-editor .monaco-editor.rename-box'; const RENAME_INPUT = `${RENAME_BOX} .rename-input`; @@ -18,12 +18,12 @@ export class Editor { private static readonly FOLDING_EXPANDED = '.monaco-editor .margin .margin-view-overlays>:nth-child(${INDEX}) .folding'; private static readonly FOLDING_COLLAPSED = `${Editor.FOLDING_EXPANDED}.collapsed`; - constructor(private api: API, private commands: Commands) { } + constructor(private code: Code, private commands: Commands) { } async findReferences(term: string, line: number): Promise { await this.clickOnTerm(term, line); await this.commands.runCommand('Find All References'); - const references = new References(this.api); + const references = new References(this.code); await references.waitUntilOpen(); return references; } @@ -32,10 +32,10 @@ export class Editor { await this.clickOnTerm(from, line); await this.commands.runCommand('Rename Symbol'); - await this.api.waitForActiveElement(RENAME_INPUT); - await this.api.setValue(RENAME_INPUT, to); + await this.code.waitForActiveElement(RENAME_INPUT); + await this.code.setValue(RENAME_INPUT, to); - await this.api.dispatchKeybinding('enter'); + await this.code.dispatchKeybinding('enter'); } async gotoDefinition(term: string, line: number): Promise { @@ -46,7 +46,7 @@ export class Editor { async peekDefinition(term: string, line: number): Promise { await this.clickOnTerm(term, line); await this.commands.runCommand('Peek Definition'); - const peek = new References(this.api); + const peek = new References(this.code); await peek.waitUntilOpen(); return peek; } @@ -54,7 +54,7 @@ export class Editor { async waitForHighlightingLine(line: number): Promise { const currentLineIndex = await this.getViewLineIndex(line); if (currentLineIndex) { - await this.api.waitForElement(`.monaco-editor .view-overlays>:nth-child(${currentLineIndex}) .current-line`); + await this.code.waitForElement(`.monaco-editor .view-overlays>:nth-child(${currentLineIndex}) .current-line`); return; } throw new Error('Cannot find line ' + line); @@ -68,14 +68,14 @@ export class Editor { async foldAtLine(line: number): Promise { const lineIndex = await this.getViewLineIndex(line); - await this.api.waitAndClick(Editor.FOLDING_EXPANDED.replace('${INDEX}', '' + lineIndex)); - await this.api.waitForElement(Editor.FOLDING_COLLAPSED.replace('${INDEX}', '' + lineIndex)); + await this.code.waitAndClick(Editor.FOLDING_EXPANDED.replace('${INDEX}', '' + lineIndex)); + await this.code.waitForElement(Editor.FOLDING_COLLAPSED.replace('${INDEX}', '' + lineIndex)); } async unfoldAtLine(line: number): Promise { const lineIndex = await this.getViewLineIndex(line); - await this.api.waitAndClick(Editor.FOLDING_COLLAPSED.replace('${INDEX}', '' + lineIndex)); - await this.api.waitForElement(Editor.FOLDING_EXPANDED.replace('${INDEX}', '' + lineIndex)); + await this.code.waitAndClick(Editor.FOLDING_COLLAPSED.replace('${INDEX}', '' + lineIndex)); + await this.code.waitForElement(Editor.FOLDING_EXPANDED.replace('${INDEX}', '' + lineIndex)); } async waitUntilShown(line: number): Promise { @@ -84,18 +84,18 @@ export class Editor { async clickOnTerm(term: string, line: number): Promise { const selector = await this.getSelector(term, line); - await this.api.waitAndClick(selector); + await this.code.waitAndClick(selector); } async waitForTypeInEditor(filename: string, text: string, selectorPrefix = ''): Promise { const editor = [selectorPrefix || '', EDITOR(filename)].join(' '); - await this.api.waitForElement(editor); + await this.code.waitForElement(editor); const textarea = `${editor} textarea`; - await this.api.waitForActiveElement(textarea); + await this.code.waitForActiveElement(textarea); - await this.api.typeInEditor(textarea, text); + await this.code.typeInEditor(textarea, text); await this.waitForEditorContents(filename, c => c.indexOf(text) > -1, selectorPrefix); } @@ -103,17 +103,17 @@ export class Editor { async waitForEditorContents(filename: string, accept: (contents: string) => boolean, selectorPrefix = ''): Promise { const selector = [selectorPrefix || '', `${EDITOR(filename)} .view-lines`].join(' '); - return this.api.waitForTextContent(selector, undefined, c => accept(c.replace(/\u00a0/g, ' '))); + return this.code.waitForTextContent(selector, undefined, c => accept(c.replace(/\u00a0/g, ' '))); } private async getClassSelectors(term: string, viewline: number): Promise { - const elements = await this.api.waitForElements(`${Editor.VIEW_LINES}>:nth-child(${viewline}) span span`, false, els => els.some(el => el.textContent === term)); + const elements = await this.code.waitForElements(`${Editor.VIEW_LINES}>:nth-child(${viewline}) span span`, false, els => els.some(el => el.textContent === term)); const { className } = elements.filter(r => r.textContent === term)[0]; return className.split(/\s/g); } private async getViewLineIndex(line: number): Promise { - const elements = await this.api.waitForElements(Editor.LINE_NUMBERS, false, els => { + const elements = await this.code.waitForElements(Editor.LINE_NUMBERS, false, els => { return els.some(el => el.textContent === `${line}`); }); diff --git a/test/smoke/src/areas/editor/editors.ts b/test/smoke/src/areas/editor/editors.ts index 300e57bf965f6bfffb42abcdc7cbabc5c5bcbbe8..4ac026780e46de5135a5f1437b9f6bcf4e11727e 100644 --- a/test/smoke/src/areas/editor/editors.ts +++ b/test/smoke/src/areas/editor/editors.ts @@ -3,25 +3,25 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { API } from '../../api'; import { Commands } from '../workbench/workbench'; +import { Code } from '../../vscode/code'; export class Editors { - constructor(private api: API, private commands: Commands) { } + constructor(private code: Code, private commands: Commands) { } async saveOpenedFile(): Promise { await this.commands.runCommand('workbench.action.files.save'); } async selectTab(tabName: string, untitled: boolean = false): Promise { - await this.api.waitAndClick(`.tabs-container div.tab[aria-label="${tabName}, tab"]`); + await this.code.waitAndClick(`.tabs-container div.tab[aria-label="${tabName}, tab"]`); await this.waitForEditorFocus(tabName, untitled); } async waitForActiveEditor(filename: string): Promise { const selector = `.editor-container .monaco-editor[data-uri$="${filename}"] textarea`; - return this.api.waitForActiveElement(selector); + return this.code.waitForActiveElement(selector); } async waitForEditorFocus(fileName: string, untitled: boolean = false): Promise { @@ -30,11 +30,11 @@ export class Editors { } async waitForActiveTab(fileName: string, isDirty: boolean = false): Promise { - await this.api.waitForElement(`.tabs-container div.tab.active${isDirty ? '.dirty' : ''}[aria-selected="true"][aria-label="${fileName}, tab"]`); + await this.code.waitForElement(`.tabs-container div.tab.active${isDirty ? '.dirty' : ''}[aria-selected="true"][aria-label="${fileName}, tab"]`); } async waitForTab(fileName: string, isDirty: boolean = false): Promise { - await this.api.waitForElement(`.tabs-container div.tab${isDirty ? '.dirty' : ''}[aria-label="${fileName}, tab"]`); + await this.code.waitForElement(`.tabs-container div.tab${isDirty ? '.dirty' : ''}[aria-label="${fileName}, tab"]`); } async newUntitledFile(): Promise { diff --git a/test/smoke/src/areas/editor/peek.ts b/test/smoke/src/areas/editor/peek.ts index 4f0bd9170b2d9655365cefa4a25d1fa632a8ce57..55f7f5ece06661770975ac4207476ed969fb75a2 100644 --- a/test/smoke/src/areas/editor/peek.ts +++ b/test/smoke/src/areas/editor/peek.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { API } from '../../api'; +import { Code } from '../../vscode/code'; export class References { @@ -12,29 +12,29 @@ export class References { private static readonly REFERENCES_TITLE_COUNT = `${References.REFERENCES_WIDGET} .head .peekview-title .meta`; private static readonly REFERENCES = `${References.REFERENCES_WIDGET} .body .ref-tree.inline .monaco-tree-row .reference`; - constructor(private api: API) { } + constructor(private code: Code) { } async waitUntilOpen(): Promise { - await this.api.waitForElement(References.REFERENCES_WIDGET); + await this.code.waitForElement(References.REFERENCES_WIDGET); } async waitForReferencesCountInTitle(count: number): Promise { - await this.api.waitForTextContent(References.REFERENCES_TITLE_COUNT, void 0, titleCount => { + await this.code.waitForTextContent(References.REFERENCES_TITLE_COUNT, void 0, titleCount => { const matches = titleCount.match(/\d+/); return matches ? parseInt(matches[0]) === count : false; }); } async waitForReferencesCount(count: number): Promise { - await this.api.waitForElements(References.REFERENCES, false, result => result && result.length === count); + await this.code.waitForElements(References.REFERENCES, false, result => result && result.length === count); } async waitForFile(file: string): Promise { - await this.api.waitForTextContent(References.REFERENCES_TITLE_FILE_NAME, file); + await this.code.waitForTextContent(References.REFERENCES_TITLE_FILE_NAME, file); } async close(): Promise { - await this.api.dispatchKeybinding('escape'); - await this.api.waitForElement(References.REFERENCES_WIDGET, element => !element); + await this.code.dispatchKeybinding('escape'); + await this.code.waitForElement(References.REFERENCES_WIDGET, element => !element); } } \ No newline at end of file diff --git a/test/smoke/src/areas/explorer/explorer.test.ts b/test/smoke/src/areas/explorer/explorer.test.ts index 425233c7ad7c8e75b6f1bf22d2454e43aaa70a3b..946105649b7a9873395c91f39cf262844cdc098e 100644 --- a/test/smoke/src/areas/explorer/explorer.test.ts +++ b/test/smoke/src/areas/explorer/explorer.test.ts @@ -21,7 +21,7 @@ export function setup() { await app.workbench.quickopen.openQuickOpen('.js'); await app.workbench.quickopen.waitForQuickOpenElements(names => expectedNames.every(n => names.some(m => n === m))); - await app.api.dispatchKeybinding('escape'); + await app.code.dispatchKeybinding('escape'); }); it('quick open respects fuzzy matching', async function () { @@ -34,7 +34,7 @@ export function setup() { await app.workbench.quickopen.openQuickOpen('a.s'); await app.workbench.quickopen.waitForQuickOpenElements(names => expectedNames.every(n => names.some(m => n === m))); - await app.api.dispatchKeybinding('escape'); + await app.code.dispatchKeybinding('escape'); }); }); } \ No newline at end of file diff --git a/test/smoke/src/areas/explorer/explorer.ts b/test/smoke/src/areas/explorer/explorer.ts index 956b29b488ffdc0274307cf6ac5893713d0f795a..15dc32668eef12f4ad0ddbc3c8808da7b0fdc3e7 100644 --- a/test/smoke/src/areas/explorer/explorer.ts +++ b/test/smoke/src/areas/explorer/explorer.ts @@ -4,17 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import { Viewlet } from '../workbench/viewlet'; -import { API } from '../../api'; import { Editors } from '../editor/editors'; import { Commands } from '../workbench/workbench'; +import { Code } from '../../vscode/code'; export class Explorer extends Viewlet { private static readonly EXPLORER_VIEWLET = 'div[id="workbench.view.explorer"]'; private static readonly OPEN_EDITORS_VIEW = `${Explorer.EXPLORER_VIEWLET} .split-view-view:nth-child(1) .title`; - constructor(api: API, private commands: Commands, private editors: Editors) { - super(api); + constructor(code: Code, private commands: Commands, private editors: Editors) { + super(code); } openExplorerView(): Promise { @@ -22,11 +22,11 @@ export class Explorer extends Viewlet { } getOpenEditorsViewTitle(): Promise { - return this.api.waitForTextContent(Explorer.OPEN_EDITORS_VIEW); + return this.code.waitForTextContent(Explorer.OPEN_EDITORS_VIEW); } async openFile(fileName: string): Promise { - await this.api.waitAndDoubleClick(`div[class="monaco-icon-label file-icon ${fileName}-name-file-icon ${this.getExtensionSelector(fileName)} explorer-item"]`); + await this.code.waitAndDoubleClick(`div[class="monaco-icon-label file-icon ${fileName}-name-file-icon ${this.getExtensionSelector(fileName)} explorer-item"]`); await this.editors.waitForEditorFocus(fileName); } diff --git a/test/smoke/src/areas/extensions/extensions.ts b/test/smoke/src/areas/extensions/extensions.ts index bedca3d1aa12e13b0cb551f080b97507a5d8a272..386e521e5f8f7756e99af1afc00c8a925d09f71b 100644 --- a/test/smoke/src/areas/extensions/extensions.ts +++ b/test/smoke/src/areas/extensions/extensions.ts @@ -4,15 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import { Viewlet } from '../workbench/viewlet'; -import { API } from '../../api'; import { Commands } from '../workbench/workbench'; +import { Code } from '../../vscode/code'; const SEARCH_BOX = 'div.extensions-viewlet[id="workbench.view.extensions"] input.search-box'; export class Extensions extends Viewlet { - constructor(api: API, private commands: Commands) { - super(api); + constructor(code: Code, private commands: Commands) { + super(code); } async openExtensionsViewlet(): Promise { @@ -21,19 +21,19 @@ export class Extensions extends Viewlet { } async waitForExtensionsViewlet(): Promise { - await this.api.waitForActiveElement(SEARCH_BOX); + await this.code.waitForActiveElement(SEARCH_BOX); } async searchForExtension(name: string): Promise { - await this.api.waitAndClick(SEARCH_BOX); - await this.api.waitForActiveElement(SEARCH_BOX); - await this.api.setValue(SEARCH_BOX, name); + await this.code.waitAndClick(SEARCH_BOX); + await this.code.waitForActiveElement(SEARCH_BOX); + await this.code.setValue(SEARCH_BOX, name); } async installExtension(name: string): Promise { await this.searchForExtension(name); - await this.api.waitAndClick(`div.extensions-viewlet[id="workbench.view.extensions"] .monaco-list-row[aria-label="${name}"] .extension li[class='action-item'] .extension-action.install`); - await this.api.waitForElement(`div.extensions-viewlet[id="workbench.view.extensions"] .monaco-list-row[aria-label="${name}"] .extension li[class='action-item'] .extension-action.reload`); + await this.code.waitAndClick(`div.extensions-viewlet[id="workbench.view.extensions"] .monaco-list-row[aria-label="${name}"] .extension li[class='action-item'] .extension-action.install`); + await this.code.waitForElement(`div.extensions-viewlet[id="workbench.view.extensions"] .monaco-list-row[aria-label="${name}"] .extension li[class='action-item'] .extension-action.reload`); } } \ No newline at end of file diff --git a/test/smoke/src/areas/git/git.test.ts b/test/smoke/src/areas/git/git.test.ts index ab742c7ef44864d858c1fb87d7eb11c735dcd78d..e5c2669b7222c7efe8ca64c306a485dcdce099d9 100644 --- a/test/smoke/src/areas/git/git.test.ts +++ b/test/smoke/src/areas/git/git.test.ts @@ -34,7 +34,7 @@ export function setup() { await app.workbench.scm.openSCMViewlet(); await app.workbench.scm.openChange('app.js'); - await app.api.waitForElement(DIFF_EDITOR_LINE_INSERT); + await app.code.waitForElement(DIFF_EDITOR_LINE_INSERT); }); it('stages correctly', async function () { @@ -61,13 +61,13 @@ export function setup() { await app.workbench.scm.waitForChange('app.js', 'Index Modified'); await app.workbench.scm.commit('first commit'); - await app.api.waitForTextContent(SYNC_STATUSBAR, ' 0↓ 1↑'); + await app.code.waitForTextContent(SYNC_STATUSBAR, ' 0↓ 1↑'); await app.workbench.runCommand('Git: Stage All Changes'); await app.workbench.scm.waitForChange('index.jade', 'Index Modified'); await app.workbench.scm.commit('second commit'); - await app.api.waitForTextContent(SYNC_STATUSBAR, ' 0↓ 2↑'); + await app.code.waitForTextContent(SYNC_STATUSBAR, ' 0↓ 2↑'); cp.execSync('git reset --hard origin/master', { cwd: app.workspacePath }); }); diff --git a/test/smoke/src/areas/git/scm.ts b/test/smoke/src/areas/git/scm.ts index 21a692407802ae69bc3612524491d3864b587c3a..4543c6fc689ceeb50d1df55407e610ed50b84759 100644 --- a/test/smoke/src/areas/git/scm.ts +++ b/test/smoke/src/areas/git/scm.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { Viewlet } from '../workbench/viewlet'; -import { API, findElement, findElements } from '../../api'; import { Commands } from '../workbench/workbench'; import { IElement } from '../../vscode/driver'; +import { findElement, findElements, Code } from '../../vscode/code'; const VIEWLET = 'div[id="workbench.view.scm"]'; const SCM_INPUT = `${VIEWLET} .scm-editor textarea`; @@ -41,44 +41,44 @@ function toChange(element: IElement): Change { export class SCM extends Viewlet { - constructor(api: API, private commands: Commands) { - super(api); + constructor(code: Code, private commands: Commands) { + super(code); } async openSCMViewlet(): Promise { await this.commands.runCommand('workbench.view.scm'); - await this.api.waitForElement(SCM_INPUT); + await this.code.waitForElement(SCM_INPUT); } async waitForChange(name: string, type?: string): Promise { const func = (change: Change) => change.name === name && (!type || change.type === type); - await this.api.waitForElements(SCM_RESOURCE, true, elements => elements.some(e => func(toChange(e)))); + await this.code.waitForElements(SCM_RESOURCE, true, elements => elements.some(e => func(toChange(e)))); } async refreshSCMViewlet(): Promise { - await this.api.waitAndClick(REFRESH_COMMAND); + await this.code.waitAndClick(REFRESH_COMMAND); } async openChange(name: string): Promise { - await this.api.waitAndClick(SCM_RESOURCE_CLICK(name)); + await this.code.waitAndClick(SCM_RESOURCE_CLICK(name)); } async stage(name: string): Promise { - await this.api.waitAndClick(SCM_RESOURCE_ACTION_CLICK(name, 'Stage Changes')); + await this.code.waitAndClick(SCM_RESOURCE_ACTION_CLICK(name, 'Stage Changes')); } async stageAll(): Promise { - await this.api.waitAndClick(SCM_RESOURCE_GROUP_COMMAND_CLICK('Stage All Changes')); + await this.code.waitAndClick(SCM_RESOURCE_GROUP_COMMAND_CLICK('Stage All Changes')); } async unstage(name: string): Promise { - await this.api.waitAndClick(SCM_RESOURCE_ACTION_CLICK(name, 'Unstage Changes')); + await this.code.waitAndClick(SCM_RESOURCE_ACTION_CLICK(name, 'Unstage Changes')); } async commit(message: string): Promise { - await this.api.waitAndClick(SCM_INPUT); - await this.api.waitForActiveElement(SCM_INPUT); - await this.api.setValue(SCM_INPUT, message); - await this.api.waitAndClick(COMMIT_COMMAND); + await this.code.waitAndClick(SCM_INPUT); + await this.code.waitForActiveElement(SCM_INPUT); + await this.code.setValue(SCM_INPUT, message); + await this.code.waitAndClick(COMMIT_COMMAND); } } \ No newline at end of file diff --git a/test/smoke/src/areas/multiroot/multiroot.test.ts b/test/smoke/src/areas/multiroot/multiroot.test.ts index a5f47f259676d997a81c9d2d36573916d11dcc92..5b479677556b760b7f1cf4be4c0e75de8ac42ab7 100644 --- a/test/smoke/src/areas/multiroot/multiroot.test.ts +++ b/test/smoke/src/areas/multiroot/multiroot.test.ts @@ -27,7 +27,7 @@ export function setup() { it('shows workspace name in title', async function () { const app = this.app as Application; - const title = await app.api.getTitle(); + const title = await app.code.getTitle(); assert.ok(title.indexOf('smoketest (Workspace)') >= 0); }); }); diff --git a/test/smoke/src/areas/preferences/keybindings.ts b/test/smoke/src/areas/preferences/keybindings.ts index 014dff290a3978eba3bf8dd6915c303e3235a3bf..98865e492c42adf3f15f9b673defa498d1aeb5c9 100644 --- a/test/smoke/src/areas/preferences/keybindings.ts +++ b/test/smoke/src/areas/preferences/keybindings.ts @@ -3,28 +3,28 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { API } from '../../api'; import { Commands } from '../workbench/workbench'; +import { Code } from '../../vscode/code'; const SEARCH_INPUT = '.settings-search-input input'; export class KeybindingsEditor { - constructor(private api: API, private commands: Commands) { } + constructor(private code: Code, private commands: Commands) { } async updateKeybinding(command: string, keybinding: string, ariaLabel: string): Promise { await this.commands.runCommand('workbench.action.openGlobalKeybindings'); - await this.api.waitForActiveElement(SEARCH_INPUT); - await this.api.setValue(SEARCH_INPUT, command); + await this.code.waitForActiveElement(SEARCH_INPUT); + await this.code.setValue(SEARCH_INPUT, command); - await this.api.waitAndClick('div[aria-label="Keybindings"] .monaco-list-row.keybinding-item'); - await this.api.waitForElement('div[aria-label="Keybindings"] .monaco-list-row.keybinding-item.focused.selected'); + await this.code.waitAndClick('div[aria-label="Keybindings"] .monaco-list-row.keybinding-item'); + await this.code.waitForElement('div[aria-label="Keybindings"] .monaco-list-row.keybinding-item.focused.selected'); - await this.api.waitAndClick('div[aria-label="Keybindings"] .monaco-list-row.keybinding-item .action-item .icon.add'); - await this.api.waitForElement('.defineKeybindingWidget .monaco-inputbox.synthetic-focus'); + await this.code.waitAndClick('div[aria-label="Keybindings"] .monaco-list-row.keybinding-item .action-item .icon.add'); + await this.code.waitForElement('.defineKeybindingWidget .monaco-inputbox.synthetic-focus'); - await this.api.dispatchKeybinding(keybinding); - await this.api.dispatchKeybinding('enter'); - await this.api.waitForElement(`div[aria-label="Keybindings"] div[aria-label="Keybinding is ${ariaLabel}."]`); + await this.code.dispatchKeybinding(keybinding); + await this.code.dispatchKeybinding('enter'); + await this.code.waitForElement(`div[aria-label="Keybindings"] div[aria-label="Keybinding is ${ariaLabel}."]`); } } \ No newline at end of file diff --git a/test/smoke/src/areas/preferences/preferences.test.ts b/test/smoke/src/areas/preferences/preferences.test.ts index 92c58ab45175a8d11c37a0c3903f48ca0bb2bf89..ecca6a4416791335d8306592dea7f1ab1958b0e9 100644 --- a/test/smoke/src/areas/preferences/preferences.test.ts +++ b/test/smoke/src/areas/preferences/preferences.test.ts @@ -14,11 +14,11 @@ export function setup() { const app = this.app as Application; await app.workbench.explorer.openFile('app.js'); - await app.api.waitForElements('.line-numbers', false, elements => !!elements.length); + await app.code.waitForElements('.line-numbers', false, elements => !!elements.length); await app.workbench.settingsEditor.addUserSetting('editor.lineNumbers', '"off"'); await app.workbench.editors.selectTab('app.js'); - await app.api.waitForElements('.line-numbers', false, result => !result || result.length === 0); + await app.code.waitForElements('.line-numbers', false, result => !result || result.length === 0); }); it(`changes 'workbench.action.toggleSidebarPosition' command key binding and verifies it`, async function () { @@ -27,7 +27,7 @@ export function setup() { await app.workbench.keybindingsEditor.updateKeybinding('workbench.action.toggleSidebarPosition', 'ctrl+u', 'Control+U'); - await app.api.dispatchKeybinding('ctrl+u'); + await app.code.dispatchKeybinding('ctrl+u'); assert.ok(await app.workbench.activitybar.getActivityBar(ActivityBarPosition.RIGHT), 'Activity bar was not moved to right after toggling its position.'); }); diff --git a/test/smoke/src/areas/preferences/settings.ts b/test/smoke/src/areas/preferences/settings.ts index 915af0873eab3a84be0218345a15decf1ebc62fb..2c64ae89b129ab10837a28c013bf1603284ef09d 100644 --- a/test/smoke/src/areas/preferences/settings.ts +++ b/test/smoke/src/areas/preferences/settings.ts @@ -5,10 +5,10 @@ import * as fs from 'fs'; import * as path from 'path'; -import { API } from '../../api'; import { Commands } from '../workbench/workbench'; import { Editor } from '../editor/editor'; import { Editors } from '../editor/editors'; +import { Code } from '../../vscode/code'; export enum ActivityBarPosition { LEFT = 0, @@ -20,17 +20,17 @@ const EDITOR = '.editable-preferences-editor-container .monaco-editor textarea'; export class SettingsEditor { - constructor(private api: API, private userDataPath: string, private commands: Commands, private editors: Editors, private editor: Editor) { } + constructor(private code: Code, private userDataPath: string, private commands: Commands, private editors: Editors, private editor: Editor) { } async addUserSetting(setting: string, value: string): Promise { await this.commands.runCommand('workbench.action.openGlobalSettings'); - await this.api.waitAndClick(SEARCH_INPUT); - await this.api.waitForActiveElement(SEARCH_INPUT); + await this.code.waitAndClick(SEARCH_INPUT); + await this.code.waitForActiveElement(SEARCH_INPUT); - await this.api.dispatchKeybinding('down'); - await this.api.waitForActiveElement(EDITOR); + await this.code.dispatchKeybinding('down'); + await this.code.waitForActiveElement(EDITOR); - await this.api.dispatchKeybinding('right'); + await this.code.dispatchKeybinding('right'); await this.editor.waitForTypeInEditor('settings.json', `"${setting}": ${value}`, '.editable-preferences-editor-container'); await this.editors.saveOpenedFile(); } diff --git a/test/smoke/src/areas/problems/problems.ts b/test/smoke/src/areas/problems/problems.ts index 84a0b92c2445e35f3fa11474846364bcff1123f3..b5c671a9e703b01bfe25ddf1a2aca6336546c1dc 100644 --- a/test/smoke/src/areas/problems/problems.ts +++ b/test/smoke/src/areas/problems/problems.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { API } from '../../api'; import { Commands } from '../workbench/workbench'; +import { Code } from '../../vscode/code'; export enum ProblemSeverity { WARNING = 0, @@ -15,7 +15,7 @@ export class Problems { static PROBLEMS_VIEW_SELECTOR = '.panel.markers-panel'; - constructor(private api: API, private commands: Commands) { + constructor(private code: Code, private commands: Commands) { // noop } @@ -29,16 +29,16 @@ export class Problems { public async hideProblemsView(): Promise { if (await this.isVisible()) { await this.commands.runCommand('workbench.actions.view.problems'); - await this.api.waitForElement(Problems.PROBLEMS_VIEW_SELECTOR, el => !el); + await this.code.waitForElement(Problems.PROBLEMS_VIEW_SELECTOR, el => !el); } } isVisible(): Promise { - return this.api.doesElementExist(Problems.PROBLEMS_VIEW_SELECTOR); + return this.code.doesElementExist(Problems.PROBLEMS_VIEW_SELECTOR); } public async waitForProblemsView(): Promise { - await this.api.waitForElement(Problems.PROBLEMS_VIEW_SELECTOR); + await this.code.waitForElement(Problems.PROBLEMS_VIEW_SELECTOR); } public static getSelectorInProblemsView(problemType: ProblemSeverity): string { diff --git a/test/smoke/src/areas/quickopen/quickopen.ts b/test/smoke/src/areas/quickopen/quickopen.ts index 9d411f60d77716a7dd4d62cd62f3fa336a8d4823..c580efdf69d22e9dbebed0420456861cb13840f6 100644 --- a/test/smoke/src/areas/quickopen/quickopen.ts +++ b/test/smoke/src/areas/quickopen/quickopen.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { API } from '../../api'; import { Editors } from '../editor/editors'; import { Commands } from '../workbench/workbench'; +import { Code } from '../../vscode/code'; export class QuickOpen { @@ -16,14 +16,14 @@ export class QuickOpen { static QUICK_OPEN_ENTRY_SELECTOR = 'div[aria-label="Quick Picker"] .monaco-tree-rows.show-twisties .monaco-tree-row .quick-open-entry'; static QUICK_OPEN_ENTRY_LABEL_SELECTOR = 'div[aria-label="Quick Picker"] .monaco-tree-rows.show-twisties .monaco-tree-row .quick-open-entry .label-name'; - constructor(private api: API, private commands: Commands, private editors: Editors) { } + constructor(private code: Code, private commands: Commands, private editors: Editors) { } async openQuickOpen(value: string): Promise { await this.commands.runCommand('workbench.action.quickOpen'); await this.waitForQuickOpenOpened(); if (value) { - await this.api.setValue(QuickOpen.QUICK_OPEN_INPUT, value); + await this.code.setValue(QuickOpen.QUICK_OPEN_INPUT, value); } } @@ -36,46 +36,46 @@ export class QuickOpen { await this.openQuickOpen(fileName); await this.waitForQuickOpenElements(names => names.some(n => n === fileName)); - await this.api.dispatchKeybinding('enter'); + await this.code.dispatchKeybinding('enter'); await this.editors.waitForActiveTab(fileName); await this.editors.waitForEditorFocus(fileName); } async waitForQuickOpenOpened(): Promise { - await this.api.waitForActiveElement(QuickOpen.QUICK_OPEN_INPUT); + await this.code.waitForActiveElement(QuickOpen.QUICK_OPEN_INPUT); } private async waitForQuickOpenClosed(): Promise { - await this.api.waitForElement(QuickOpen.QUICK_OPEN_HIDDEN); + await this.code.waitForElement(QuickOpen.QUICK_OPEN_HIDDEN); } async submit(text: string): Promise { - await this.api.setValue(QuickOpen.QUICK_OPEN_INPUT, text); - await this.api.dispatchKeybinding('enter'); + await this.code.setValue(QuickOpen.QUICK_OPEN_INPUT, text); + await this.code.dispatchKeybinding('enter'); await this.waitForQuickOpenClosed(); } async selectQuickOpenElement(index: number): Promise { await this.waitForQuickOpenOpened(); for (let from = 0; from < index; from++) { - await this.api.dispatchKeybinding('down'); + await this.code.dispatchKeybinding('down'); } - await this.api.dispatchKeybinding('enter'); + await this.code.dispatchKeybinding('enter'); await this.waitForQuickOpenClosed(); } async waitForQuickOpenElements(accept: (names: string[]) => boolean): Promise { - await this.api.waitForElements(QuickOpen.QUICK_OPEN_ENTRY_LABEL_SELECTOR, false, els => accept(els.map(e => e.textContent))); + await this.code.waitForElements(QuickOpen.QUICK_OPEN_ENTRY_LABEL_SELECTOR, false, els => accept(els.map(e => e.textContent))); } async runCommand(command: string): Promise { await this.openQuickOpen(`> ${command}`); // wait for best choice to be focused - await this.api.waitForTextContent(QuickOpen.QUICK_OPEN_FOCUSED_ELEMENT, command); + await this.code.waitForTextContent(QuickOpen.QUICK_OPEN_FOCUSED_ELEMENT, command); // wait and click on best choice - await this.api.waitAndClick(QuickOpen.QUICK_OPEN_FOCUSED_ELEMENT); + await this.code.waitAndClick(QuickOpen.QUICK_OPEN_FOCUSED_ELEMENT); } async openQuickOutline(): Promise { @@ -84,7 +84,7 @@ export class QuickOpen { while (++retries < 10) { await this.commands.runCommand('workbench.action.gotoSymbol'); - const text = await this.api.waitForTextContent('div[aria-label="Quick Picker"] .monaco-tree-rows.show-twisties div.monaco-tree-row .quick-open-entry .monaco-icon-label .label-name .monaco-highlighted-label span'); + const text = await this.code.waitForTextContent('div[aria-label="Quick Picker"] .monaco-tree-rows.show-twisties div.monaco-tree-row .quick-open-entry .monaco-icon-label .label-name .monaco-highlighted-label span'); if (text !== 'No symbol information for the file') { return; diff --git a/test/smoke/src/areas/search/search.ts b/test/smoke/src/areas/search/search.ts index 3976d4e64c67759a8624a0fa8f062237c7ecd177..8c3514ae1f35fb2eb90269898aa7a539a180933c 100644 --- a/test/smoke/src/areas/search/search.ts +++ b/test/smoke/src/areas/search/search.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { Viewlet } from '../workbench/viewlet'; -import { API } from '../../api'; import { Commands } from '../workbench/workbench'; +import { Code } from '../../vscode/code'; const VIEWLET = 'div[id="workbench.view.search"] .search-view'; const INPUT = `${VIEWLET} .search-widget .search-container .monaco-inputbox input`; @@ -13,75 +13,75 @@ const INCLUDE_INPUT = `${VIEWLET} .query-details .monaco-inputbox input[aria-lab export class Search extends Viewlet { - constructor(api: API, private commands: Commands) { - super(api); + constructor(code: Code, private commands: Commands) { + super(code); } async openSearchViewlet(): Promise { await this.commands.runCommand('workbench.view.search'); - await this.api.waitForActiveElement(INPUT); + await this.code.waitForActiveElement(INPUT); } async searchFor(text: string): Promise { - await this.api.waitAndClick(INPUT); - await this.api.waitForActiveElement(INPUT); - await this.api.setValue(INPUT, text); + await this.code.waitAndClick(INPUT); + await this.code.waitForActiveElement(INPUT); + await this.code.setValue(INPUT, text); await this.submitSearch(); } async submitSearch(): Promise { - await this.api.waitAndClick(INPUT); - await this.api.waitForActiveElement(INPUT); + await this.code.waitAndClick(INPUT); + await this.code.waitForActiveElement(INPUT); - await this.api.dispatchKeybinding('enter'); - await this.api.waitForElement(`${VIEWLET} .messages[aria-hidden="false"]`); + await this.code.dispatchKeybinding('enter'); + await this.code.waitForElement(`${VIEWLET} .messages[aria-hidden="false"]`); } async setFilesToIncludeText(text: string): Promise { - await this.api.waitAndClick(INCLUDE_INPUT); - await this.api.waitForActiveElement(INCLUDE_INPUT); - await this.api.setValue(INCLUDE_INPUT, text || ''); + await this.code.waitAndClick(INCLUDE_INPUT); + await this.code.waitForActiveElement(INCLUDE_INPUT); + await this.code.setValue(INCLUDE_INPUT, text || ''); } async showQueryDetails(): Promise { if (!await this.areDetailsVisible()) { - await this.api.waitAndClick(`${VIEWLET} .query-details .more`); + await this.code.waitAndClick(`${VIEWLET} .query-details .more`); } } async hideQueryDetails(): Promise { if (await this.areDetailsVisible()) { - await this.api.waitAndClick(`${VIEWLET} .query-details.more .more`); + await this.code.waitAndClick(`${VIEWLET} .query-details.more .more`); } } areDetailsVisible(): Promise { - return this.api.doesElementExist(`${VIEWLET} .query-details.more`); + return this.code.doesElementExist(`${VIEWLET} .query-details.more`); } async removeFileMatch(index: number): Promise { - await this.api.waitAndMove(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch`); - const file = await this.api.waitForTextContent(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch a.label-name`); - await this.api.waitAndClick(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch .action-label.icon.action-remove`); - await this.api.waitForTextContent(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch a.label-name`, void 0, result => result !== file); + await this.code.waitAndMove(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch`); + const file = await this.code.waitForTextContent(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch a.label-name`); + await this.code.waitAndClick(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch .action-label.icon.action-remove`); + await this.code.waitForTextContent(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch a.label-name`, void 0, result => result !== file); } async expandReplace(): Promise { - await this.api.waitAndClick(`${VIEWLET} .search-widget .monaco-button.toggle-replace-button.collapse`); + await this.code.waitAndClick(`${VIEWLET} .search-widget .monaco-button.toggle-replace-button.collapse`); } async setReplaceText(text: string): Promise { - await this.api.waitAndClick(`${VIEWLET} .search-widget .replace-container .monaco-inputbox input[title="Replace"]`); - await this.api.waitForElement(`${VIEWLET} .search-widget .replace-container .monaco-inputbox.synthetic-focus input[title="Replace"]`); - await this.api.setValue(`${VIEWLET} .search-widget .replace-container .monaco-inputbox.synthetic-focus input[title="Replace"]`, text); + await this.code.waitAndClick(`${VIEWLET} .search-widget .replace-container .monaco-inputbox input[title="Replace"]`); + await this.code.waitForElement(`${VIEWLET} .search-widget .replace-container .monaco-inputbox.synthetic-focus input[title="Replace"]`); + await this.code.setValue(`${VIEWLET} .search-widget .replace-container .monaco-inputbox.synthetic-focus input[title="Replace"]`, text); } async replaceFileMatch(index: number): Promise { - await this.api.waitAndMove(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch`); - await this.api.waitAndClick(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch .action-label.icon.action-replace-all`); + await this.code.waitAndMove(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch`); + await this.code.waitAndClick(`${VIEWLET} .results .monaco-tree-rows>:nth-child(${index}) .filematch .action-label.icon.action-replace-all`); } async waitForResultText(text: string): Promise { - await this.api.waitForTextContent(`${VIEWLET} .messages[aria-hidden="false"] .message>p`, text); + await this.code.waitForTextContent(`${VIEWLET} .messages[aria-hidden="false"] .message>p`, text); } } diff --git a/test/smoke/src/areas/statusbar/statusbar.test.ts b/test/smoke/src/areas/statusbar/statusbar.test.ts index 3c748bde9bb79bf20608db7dcbe60633f090723c..b5c119c05396cad9cb6d3733f068ee0833a7edd1 100644 --- a/test/smoke/src/areas/statusbar/statusbar.test.ts +++ b/test/smoke/src/areas/statusbar/statusbar.test.ts @@ -63,7 +63,7 @@ export function setup() { } await app.workbench.statusbar.clickOn(StatusBarElement.FEEDBACK_ICON); - await app.api.waitForElement('.feedback-form'); + await app.code.waitForElement('.feedback-form'); }); it(`checks if 'Go to Line' works if called from the status bar`, async function () { diff --git a/test/smoke/src/areas/statusbar/statusbar.ts b/test/smoke/src/areas/statusbar/statusbar.ts index 20ab92c96e3e718fb75fc4a64215d973813e1781..e4b8237f49bf72060efa5f42e451e495bd671be4 100644 --- a/test/smoke/src/areas/statusbar/statusbar.ts +++ b/test/smoke/src/areas/statusbar/statusbar.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { API } from '../../api'; +import { Code } from '../../vscode/code'; export enum StatusBarElement { BRANCH_STATUS = 0, @@ -23,23 +23,22 @@ export class StatusBar { private readonly leftSelector = '.statusbar-item.left'; private readonly rightSelector = '.statusbar-item.right'; - constructor(private api: API) { - } + constructor(private code: Code) { } async waitForStatusbarElement(element: StatusBarElement): Promise { - await this.api.waitForElement(this.getSelector(element)); + await this.code.waitForElement(this.getSelector(element)); } async clickOn(element: StatusBarElement): Promise { - await this.api.waitAndClick(this.getSelector(element)); + await this.code.waitAndClick(this.getSelector(element)); } async waitForEOL(eol: string): Promise { - return this.api.waitForTextContent(this.getSelector(StatusBarElement.EOL_STATUS), eol); + return this.code.waitForTextContent(this.getSelector(StatusBarElement.EOL_STATUS), eol); } async getStatusbarTextByTitle(title: string): Promise { - return await this.api.waitForTextContent(`${this.mainSelector} span[title="smoke test"]`); + return await this.code.waitForTextContent(`${this.mainSelector} span[title="smoke test"]`); } private getSelector(element: StatusBarElement): string { diff --git a/test/smoke/src/areas/workbench/viewlet.ts b/test/smoke/src/areas/workbench/viewlet.ts index c76ddb6e41ca4aa06b83415290546e082448ae3e..82978b7770d53c68a068386668e0b78e48f813b0 100644 --- a/test/smoke/src/areas/workbench/viewlet.ts +++ b/test/smoke/src/areas/workbench/viewlet.ts @@ -3,15 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { API } from '../../api'; +'use strict'; + +import { Code } from '../../vscode/code'; export abstract class Viewlet { - constructor(protected api: API) { + constructor(protected code: Code) { // noop } async getTitle(): Promise { - return this.api.waitForTextContent('.monaco-workbench-container .part.sidebar > .title > .title-label > span'); + return this.code.waitForTextContent('.monaco-workbench-container .part.sidebar > .title > .title-label > span'); } } \ No newline at end of file diff --git a/test/smoke/src/areas/workbench/workbench.ts b/test/smoke/src/areas/workbench/workbench.ts index 645466acd3bb8c6903c5cc7ac32ae09fe0c467d3..d81733bb58bdec158d78f3a8dc579d41fbd3c7ef 100644 --- a/test/smoke/src/areas/workbench/workbench.ts +++ b/test/smoke/src/areas/workbench/workbench.ts @@ -15,8 +15,8 @@ import { StatusBar } from '../statusbar/statusbar'; import { Problems } from '../problems/problems'; import { SettingsEditor } from '../preferences/settings'; import { KeybindingsEditor } from '../preferences/keybindings'; -import { API } from '../../api'; import { Editors } from '../editor/editors'; +import { Code } from '../../vscode/code'; export interface Commands { runCommand(command: string): Promise; @@ -38,20 +38,20 @@ export class Workbench implements Commands { readonly settingsEditor: SettingsEditor; readonly keybindingsEditor: KeybindingsEditor; - constructor(private api: API, private keybindings: any[], userDataPath: string) { - this.editors = new Editors(api, this); - this.quickopen = new QuickOpen(api, this, this.editors); - this.explorer = new Explorer(api, this.quickopen, this.editors); - this.activitybar = new ActivityBar(api); - this.search = new Search(api, this); - this.extensions = new Extensions(api, this); - this.editor = new Editor(api, this); - this.scm = new SCM(api, this); - this.debug = new Debug(api, this, this.editors, this.editor); - this.statusbar = new StatusBar(api); - this.problems = new Problems(api, this); - this.settingsEditor = new SettingsEditor(api, userDataPath, this, this.editors, this.editor); - this.keybindingsEditor = new KeybindingsEditor(api, this); + constructor(private code: Code, private keybindings: any[], userDataPath: string) { + this.editors = new Editors(code, this); + this.quickopen = new QuickOpen(code, this, this.editors); + this.explorer = new Explorer(code, this.quickopen, this.editors); + this.activitybar = new ActivityBar(code); + this.search = new Search(code, this); + this.extensions = new Extensions(code, this); + this.editor = new Editor(code, this); + this.scm = new SCM(code, this); + this.debug = new Debug(code, this, this.editors, this.editor); + this.statusbar = new StatusBar(code); + this.problems = new Problems(code, this); + this.settingsEditor = new SettingsEditor(code, userDataPath, this, this.editors, this.editor); + this.keybindingsEditor = new KeybindingsEditor(code, this); } /** @@ -65,7 +65,7 @@ export class Workbench implements Commands { return; } - return this.api.dispatchKeybinding(binding.key); + return this.code.dispatchKeybinding(binding.key); } } diff --git a/test/smoke/src/vscode/code.ts b/test/smoke/src/vscode/code.ts index a5e0110b796bcc05da07967ee2df44b65d6b710a..d76a45c7d80b40d106df9c06f0e2463e7ecf2f59 100644 --- a/test/smoke/src/vscode/code.ts +++ b/test/smoke/src/vscode/code.ts @@ -6,7 +6,7 @@ import * as path from 'path'; import * as cp from 'child_process'; import { tmpName } from 'tmp'; -import { IDriver, connect as connectDriver, IDisposable } from './driver'; +import { IDriver, connect as connectDriver, IDisposable, IElement } from './driver'; const repoPath = path.join(__dirname, '../../../..'); @@ -56,29 +56,13 @@ function getBuildOutPath(root: string): string { } } -export class Code { - - constructor( - private process: cp.ChildProcess, - private client: IDisposable, - readonly driver: IDriver - ) { - - } - - dispose(): void { - this.client.dispose(); - this.process.kill(); - } -} - -export async function connect(child: cp.ChildProcess, outPath: string, handlePath: string): Promise { +async function connect(child: cp.ChildProcess, outPath: string, handlePath: string, verbose: boolean): Promise { let errCount = 0; while (true) { try { const { client, driver } = await connectDriver(outPath, handlePath); - return new Code(child, client, driver); + return new Code(child, client, driver, verbose); } catch (err) { if (++errCount > 50) { child.kill(); @@ -142,5 +126,240 @@ export async function spawn(options: SpawnOptions): Promise { instances.add(child); child.once('exit', () => instances.delete(child)); - return connect(child, outPath, handlePath); + return connect(child, outPath, handlePath, options.verbose); +} + +export class Code { + + private _activeWindowId: number | undefined = undefined; + + constructor( + private process: cp.ChildProcess, + private client: IDisposable, + private driver: IDriver, + private verbose: boolean + ) { } + + async dispatchKeybinding(keybinding: string): Promise { + if (this.verbose) { + console.log('- dispatchKeybinding:', keybinding); + } + + const windowId = await this.getActiveWindowId(); + await this.driver.dispatchKeybinding(windowId, keybinding); + } + + async waitForTextContent(selector: string, textContent?: string, accept?: (result: string) => boolean): Promise { + if (this.verbose) { + console.log('- waitForTextContent:', selector); + } + + const windowId = await this.getActiveWindowId(); + accept = accept || (result => textContent !== void 0 ? textContent === result : !!result); + return await this.waitFor(() => this.driver.getElements(windowId, selector).then(els => els[0].textContent), s => accept!(typeof s === 'string' ? s : ''), `getTextContent with selector ${selector}`); + } + + async waitAndClick(selector: string, xoffset?: number, yoffset?: number): Promise { + if (this.verbose) { + console.log('- waitAndClick:', selector); + } + + const windowId = await this.getActiveWindowId(); + + // TODO should waitForClick + await this.waitForElement(selector); + await this.driver.click(windowId, selector, xoffset, yoffset); + } + + async waitAndDoubleClick(selector: string): Promise { + if (this.verbose) { + console.log('- waitAndDoubleClick:', selector); + } + + const windowId = await this.getActiveWindowId(); + + // TODO should waitForDoubleClick + await this.waitForElement(selector); + await this.driver.doubleClick(windowId, selector); + } + + async waitAndMove(selector: string): Promise { + if (this.verbose) { + console.log('- waitAndMove:', selector); + } + + const windowId = await this.getActiveWindowId(); + // TODO should waitForMove + await this.waitForElement(selector); + await this.driver.move(windowId, selector); + } + + // TODO should be waitForSetValue + async setValue(selector: string, text: string): Promise { + if (this.verbose) { + console.log('- setValue:', selector); + } + + const windowId = await this.getActiveWindowId(); + // TODO should waitForSetValue + await this.waitForElement(selector); + await this.driver.setValue(windowId, selector, text); + } + + // TODO merge with getElements + async doesElementExist(selector: string): Promise { + if (this.verbose) { + console.log('- doesElementExist:', selector); + } + + const windowId = await this.getActiveWindowId(); + const elements = await this.driver.getElements(windowId, selector); + return elements.length > 0; + } + + // TODO merge with getElements + async getElementCount(selector: string): Promise { + if (this.verbose) { + console.log('- getElementCount:', selector); + } + + const windowId = await this.getActiveWindowId(); + const elements = await this.driver.getElements(windowId, selector); + return elements.length; + } + + async waitForElements(selector: string, recursive: boolean, accept: (result: IElement[]) => boolean = result => result.length > 0): Promise { + if (this.verbose) { + console.log('- waitForElements:', selector); + } + + const windowId = await this.getActiveWindowId(); + return await this.waitFor(() => this.driver.getElements(windowId, selector, recursive), accept, `elements with selector ${selector}`); + } + + async waitForElement(selector: string, accept: (result: IElement | undefined) => boolean = result => !!result): Promise { + if (this.verbose) { + console.log('- waitForElement:', selector); + } + + const windowId = await this.getActiveWindowId(); + return await this.waitFor(() => this.driver.getElements(windowId, selector).then(els => els[0]), accept, `element with selector ${selector}`); + } + + async waitForActiveElement(selector: string): Promise { + if (this.verbose) { + console.log('- waitForActiveElement:', selector); + } + + const windowId = await this.getActiveWindowId(); + return await this.waitFor(() => this.driver.isActiveElement(windowId, selector), undefined, `wait for active element: ${selector}`); + } + + // TODO make into waitForTitle + async getTitle(): Promise { + if (this.verbose) { + console.log('- getTitle'); + } + + const windowId = await this.getActiveWindowId(); + return await this.driver.getTitle(windowId); + } + + // TODO make into waitForTypeInEditor + async typeInEditor(selector: string, text: string): Promise { + if (this.verbose) { + console.log('- typeInEditor', selector, text); + } + + const windowId = await this.getActiveWindowId(); + await this.driver.typeInEditor(windowId, selector, text); + } + + // waitFor calls should not take more than 200 * 100 = 20 seconds to complete, excluding + // the time it takes for the actual retry call to complete + private readonly retryCount: number = 200; + private readonly retryDuration = 100; // in milliseconds + + // TODO: clean function interface + private async waitFor(func: () => T | Promise, accept?: (result: T) => boolean | Promise, timeoutMessage?: string, retryCount?: number): Promise; + private async waitFor(func: () => T | Promise, accept: (result: T) => boolean | Promise = result => !!result, timeoutMessage?: string, retryCount?: number): Promise { + let trial = 1; + retryCount = typeof retryCount === 'number' ? retryCount : this.retryCount; + + while (true) { + if (trial > retryCount) { + throw new Error(`${timeoutMessage}: Timed out after ${(retryCount * this.retryDuration) / 1000} seconds.`); + } + + let result; + try { + result = await func(); + } catch (e) { + // console.warn(e); + + if (/Method not implemented/.test(e.message)) { + throw e; + } + } + + if (accept(result)) { + return result; + } + + await new Promise(resolve => setTimeout(resolve, this.retryDuration)); + trial++; + } + } + + // TODO: replace with waitForWindows + async getWindowIds(): Promise { + return await this.driver.getWindowIds(); + } + + private async getActiveWindowId(): Promise { + if (typeof this._activeWindowId !== 'number') { + const windows = await this.driver.getWindowIds(); + this._activeWindowId = windows[0]; + } + + return this._activeWindowId; + } + + dispose(): void { + this.client.dispose(); + this.process.kill(); + } +} + +export function findElement(element: IElement, fn: (element: IElement) => boolean): IElement | null { + const queue = [element]; + + while (queue.length > 0) { + const element = queue.shift()!; + + if (fn(element)) { + return element; + } + + queue.push(...element.children); + } + + return null; +} + +export function findElements(element: IElement, fn: (element: IElement) => boolean): IElement[] { + const result: IElement[] = []; + const queue = [element]; + + while (queue.length > 0) { + const element = queue.shift()!; + + if (fn(element)) { + result.push(element); + } + + queue.push(...element.children); + } + + return result; } \ No newline at end of file