diff --git a/src/vs/platform/driver/common/driver.ts b/src/vs/platform/driver/common/driver.ts index 8a64621227bc233129a807ed8e4aa9af3f2e569c..61b98ed77148964d42385aa70f6fadb4b620853a 100644 --- a/src/vs/platform/driver/common/driver.ts +++ b/src/vs/platform/driver/common/driver.ts @@ -34,7 +34,7 @@ export interface IDriver { isActiveElement(windowId: number, selector: string): TPromise; getElements(windowId: number, selector: string, recursive: boolean): TPromise; typeInEditor(windowId: number, selector: string, text: string): TPromise; - selectorExecute

(windowId: number, selector: string, script: (elements: HTMLElement[], ...args: any[]) => P, ...args: any[]): TPromise

; + getTerminalBuffer(windowId: number, selector: string): TPromise; } //*END @@ -49,7 +49,7 @@ export interface IDriverChannel extends IChannel { call(command: 'isActiveElement', arg: [number, string]): TPromise; call(command: 'getElements', arg: [number, string, boolean]): TPromise; call(command: 'typeInEditor', arg: [number, string, string]): TPromise; - call(command: 'selectorExecute', arg: [number, string, string, any[]]): TPromise; + call(command: 'getTerminalBuffer', arg: [number, string]): TPromise; call(command: string, arg: any): TPromise; } @@ -69,9 +69,7 @@ export class DriverChannel implements IDriverChannel { case 'isActiveElement': return this.driver.isActiveElement(arg[0], arg[1]); case 'getElements': return this.driver.getElements(arg[0], arg[1], arg[2]); case 'typeInEditor': return this.driver.typeInEditor(arg[0], arg[1], arg[2]); - - // TODO@joao - case 'selectorExecute': return this.driver.selectorExecute(arg[0], arg[1], arg[1], ...arg[2]); + case 'getTerminalBuffer': return this.driver.getTerminalBuffer(arg[0], arg[1]); } return undefined; @@ -124,9 +122,8 @@ export class DriverChannelClient implements IDriver { return this.channel.call('typeInEditor', [windowId, selector, text]); } - selectorExecute

(windowId: number, selector: string, script: (elements: HTMLElement[], ...args: any[]) => P, ...args: any[]): TPromise

{ - // TODO@joao - return this.channel.call('selectorExecute', [windowId, selector, script.toString(), args]); + getTerminalBuffer(windowId: number, selector: string): TPromise { + return this.channel.call('getTerminalBuffer', [windowId, selector]); } } @@ -172,7 +169,7 @@ export interface IWindowDriver { isActiveElement(selector: string): TPromise; getElements(selector: string, recursive: boolean): TPromise; typeInEditor(selector: string, text: string): TPromise; - selectorExecute

(selector: string, script: (elements: HTMLElement[], ...args: any[]) => P, ...args: any[]): TPromise

; + getTerminalBuffer(selector: string): TPromise; } export interface IWindowDriverChannel extends IChannel { @@ -184,7 +181,7 @@ export interface IWindowDriverChannel extends IChannel { call(command: 'isActiveElement', arg: string): TPromise; call(command: 'getElements', arg: [string, boolean]): TPromise; call(command: 'typeInEditor', arg: [string, string]): TPromise; - call(command: 'selectorExecute', arg: [string, string, any[]]): TPromise; + call(command: 'getTerminalBuffer', arg: string): TPromise; call(command: string, arg: any): TPromise; } @@ -202,8 +199,7 @@ export class WindowDriverChannel implements IWindowDriverChannel { case 'isActiveElement': return this.driver.isActiveElement(arg); case 'getElements': return this.driver.getElements(arg[0], arg[1]); case 'typeInEditor': return this.driver.typeInEditor(arg[0], arg[1]); - // TODO@joao - case 'selectorExecute': return this.driver.selectorExecute(arg[0], arg[1], ...arg[2]); + case 'getTerminalBuffer': return this.driver.getTerminalBuffer(arg); } return undefined; @@ -248,8 +244,7 @@ export class WindowDriverChannelClient implements IWindowDriver { return this.channel.call('typeInEditor', [selector, text]); } - selectorExecute

(selector: string, script: (elements: HTMLElement[], ...args: any[]) => P, ...args: any[]): TPromise

{ - // TODO@joao - return this.channel.call('selectorExecute', [selector, script.toString(), args]); + getTerminalBuffer(selector: string): TPromise { + return this.channel.call('getTerminalBuffer', selector); } } \ No newline at end of file diff --git a/src/vs/platform/driver/electron-browser/driver.ts b/src/vs/platform/driver/electron-browser/driver.ts index 7fb3fb6133bc96343a089999cd23d614c849399c..8075f851c6baca272686f636dacb4e4d969564f1 100644 --- a/src/vs/platform/driver/electron-browser/driver.ts +++ b/src/vs/platform/driver/electron-browser/driver.ts @@ -95,9 +95,6 @@ class WindowDriver implements IWindowDriver { } const textarea = element as HTMLTextAreaElement; - - console.log(textarea); - const start = textarea.selectionStart; const newStart = start + text.length; const value = textarea.value; @@ -110,8 +107,22 @@ class WindowDriver implements IWindowDriver { textarea.dispatchEvent(event); } - selectorExecute

(selector: string, script: (elements: HTMLElement[], ...args: any[]) => P, ...args: any[]): TPromise

{ - return TPromise.wrapError(new Error('not implemented')); + async getTerminalBuffer(selector: string): TPromise { + const element = document.querySelector(selector); + + if (!element) { + throw new Error('Terminal not found: ' + selector); + } + + const buffer = (element as any).xterm.buffer; + + const lines: string[] = []; + + for (let i = 0; i < buffer.lines.length; i++) { + lines.push(buffer.translateBufferLineToString(i, true)); + } + + return lines; } } diff --git a/src/vs/platform/driver/electron-main/driver.ts b/src/vs/platform/driver/electron-main/driver.ts index ad32690ab27a90aa53205beffd6dbc7ff8025ba7..9aa152f8437dcfbe413a02263d3ccfb58df9f054 100644 --- a/src/vs/platform/driver/electron-main/driver.ts +++ b/src/vs/platform/driver/electron-main/driver.ts @@ -132,14 +132,14 @@ export class Driver implements IDriver, IWindowDriverRegistry { return windowDriver.getElements(selector, recursive); } - selectorExecute

(windowId: number, selector: string, script: (elements: HTMLElement[], ...args: any[]) => P, ...args: any[]): TPromise

{ + typeInEditor(windowId: number, selector: string, text: string): TPromise { const windowDriver = this.getWindowDriver(windowId); - return windowDriver.selectorExecute(selector, script, ...args); + return windowDriver.typeInEditor(selector, text); } - typeInEditor(windowId: number, selector: string, text: string): TPromise { + getTerminalBuffer(windowId: number, selector: string): TPromise { const windowDriver = this.getWindowDriver(windowId); - return windowDriver.typeInEditor(selector, text); + return windowDriver.getTerminalBuffer(selector); } private getWindowDriver(windowId: number): IWindowDriver { diff --git a/test/smoke/src/api.ts b/test/smoke/src/api.ts index 356583997fbef3287a923438a3609eb67b8b8f44..6fa23dc7862da789479470ea4d8b1aa2df0a3534 100644 --- a/test/smoke/src/api.ts +++ b/test/smoke/src/api.ts @@ -76,14 +76,14 @@ export class API { return this.driver.getTitle(); } - selectorExecute

(selector: string, script: (elements: HTMLElement[], ...args: any[]) => P, ...args: any[]): Promise

{ - return this.driver.selectorExecute(selector, script, ...args); - } - 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 { diff --git a/test/smoke/src/areas/debug/debug.ts b/test/smoke/src/areas/debug/debug.ts index 630c5c95340860967f461a0c1a962deb9c6d3f6d..013892ed9312d546c21827fe71ae67a9e7f3fcb5 100644 --- a/test/smoke/src/areas/debug/debug.ts +++ b/test/smoke/src/areas/debug/debug.ts @@ -8,6 +8,7 @@ import { Commands } from '../workbench/workbench'; import { API } from '../../api'; import { Editors } from '../editor/editors'; import { Editor } from '../editor/editor'; +import { findElement, Element } from '../../driver'; const VIEWLET = 'div[id="workbench.view.debug"]'; const DEBUG_VIEW = `${VIEWLET} .debug-view-content`; @@ -27,7 +28,7 @@ const TOOLBAR_HIDDEN = `.debug-actions-widget.monaco-builder-hidden`; const STACK_FRAME = `${VIEWLET} .monaco-tree-row .stack-frame`; const SPECIFIC_STACK_FRAME = filename => `${STACK_FRAME} .file[title$="${filename}"]`; const VARIABLE = `${VIEWLET} .debug-variables .monaco-tree-row .expression`; -const CONSOLE_OUTPUT = `.repl .output.expression`; +const CONSOLE_OUTPUT = `.repl .output.expression .value`; const CONSOLE_INPUT_OUTPUT = `.repl .input-output-pair .output.expression .value`; const REPL_FOCUSED = '.repl-input-wrapper .monaco-editor textarea'; @@ -37,6 +38,17 @@ export interface IStackFrame { lineNumber: number; } +function toStackFrame(element: Element): IStackFrame { + const name = findElement(element, e => /\bfile-name\b/.test(e.className))!; + const line = findElement(element, e => /\bline-number\b/.test(e.className))!; + const lineNumber = line.textContent ? parseInt(line.textContent.split(':').shift() || '0') : 0; + + return { + name: name.textContent || '', + lineNumber + }; +} + export class Debug extends Viewlet { constructor(api: API, private commands: Commands, private editors: Editors, private editor: Editor) { @@ -64,12 +76,9 @@ export class Debug extends Viewlet { await this.api.waitForElement(PAUSE); await this.api.waitForElement(DEBUG_STATUS_BAR); const portPrefix = 'Port: '; - await this.api.waitFor(async () => { - const output = await this.getConsoleOutput(); - return output.join(''); - }, text => !!text && text.indexOf(portPrefix) >= 0); - const output = await this.getConsoleOutput(); - const lastOutput = output.pop(); + + const output = await this.waitForOutput(output => output.some(line => line.indexOf(portPrefix) >= 0)); + const lastOutput = output.filter(line => line.indexOf(portPrefix) >= 0)[0]; return lastOutput ? parseInt(lastOutput.substr(portPrefix.length)) : 3000; } @@ -98,10 +107,8 @@ export class Debug extends Viewlet { } async waitForStackFrame(func: (stackFrame: IStackFrame) => boolean, message: string): Promise { - return await this.api.waitFor(async () => { - const stackFrames = await this.getStackFrames(); - return stackFrames.filter(func)[0]; - }, void 0, `Waiting for Stack Frame: ${message}`); + const elements = await this.api.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 { @@ -122,45 +129,15 @@ export class Debug extends Viewlet { 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.api.waitFor(async () => { - const result = await this.getConsoleOutput(); - return result[result.length - 1] || ''; - }, accept); + await this.waitForOutput(output => accept(output[output.length - 1] || '')); } async getLocalVariableCount(): Promise { return await this.api.getElementCount(VARIABLE); } - private async getStackFrames(): Promise { - const result = await this.api.selectorExecute(STACK_FRAME, - div => (Array.isArray(div) ? div : [div]).map(element => { - const name = element.querySelector('.file-name') as HTMLElement; - const line = element.querySelector('.line-number') as HTMLElement; - const lineNumber = line.textContent ? parseInt(line.textContent.split(':').shift() || '0') : 0; - - return { - name: name.textContent || '', - lineNumber - }; - }) - ); - - if (!Array.isArray(result)) { - return []; - } - - return result.map(({ name, lineNumber }) => ({ name, lineNumber })); - } - - private async getConsoleOutput(): Promise { - const result = await this.api.selectorExecute(CONSOLE_OUTPUT, - div => (Array.isArray(div) ? div : [div]).map(element => { - const value = element.querySelector('.value') as HTMLElement; - return value && value.textContent; - }).filter(line => !!line) as string[] - ); - - return result; + private async waitForOutput(fn: (output: string[]) => boolean): Promise { + const elements = await this.api.waitForElements(CONSOLE_OUTPUT, false, elements => fn(elements.map(e => e.textContent))); + return elements.map(e => e.textContent); } } diff --git a/test/smoke/src/areas/git/scm.ts b/test/smoke/src/areas/git/scm.ts index ce5e6a6bb4c700ed783835de44c9c5dfdfaa7d29..b2cc5fc8baf0a107122750f746133f4f931248e9 100644 --- a/test/smoke/src/areas/git/scm.ts +++ b/test/smoke/src/areas/git/scm.ts @@ -6,6 +6,7 @@ import { Viewlet } from '../workbench/viewlet'; import { API } from '../../api'; import { Commands } from '../workbench/workbench'; +import { Element, findElement, findElements } from '../../driver'; const VIEWLET = 'div[id="workbench.view.scm"]'; const SCM_INPUT = `${VIEWLET} .scm-editor textarea`; @@ -23,6 +24,21 @@ interface Change { actions: string[]; } +function toChange(element: Element): Change { + const name = findElement(element, e => /\blabel-name\b/.test(e.className))!; + const type = element.attributes['data-tooltip'] || ''; + + const actionElementList = findElements(element, e => /\baction-label\b/.test(e.className)); + const actions = actionElementList.map(e => e.attributes['title']); + + return { + name: name.textContent || '', + type, + actions + }; +} + + export class SCM extends Viewlet { constructor(api: API, private commands: Commands) { @@ -34,53 +50,15 @@ export class SCM extends Viewlet { await this.api.waitForElement(SCM_INPUT); } - waitForChange(name: string, type?: string): Promise { - return this.api.waitFor(async () => { - const changes = await this.queryChanges(name, type); - return changes.length; - }, l => l > 0, 'Getting SCM changes') as Promise as Promise; + 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)))); } async refreshSCMViewlet(): Promise { await this.api.waitAndClick(REFRESH_COMMAND); } - private async queryChanges(name: string, type?: string): Promise { - const result = await this.api.selectorExecute(SCM_RESOURCE, (div, name, type) => { - return (Array.isArray(div) ? div : [div]) - .map(element => { - const name = element.querySelector('.label-name') as HTMLElement; - const type = element.getAttribute('data-tooltip') || ''; - const actionElementList = element.querySelectorAll('.actions .action-label'); - const actions: string[] = []; - - for (let i = 0; i < actionElementList.length; i++) { - const element = actionElementList.item(i) as HTMLElement; - actions.push(element.title); - } - - return { - name: name.textContent || '', - type, - actions - }; - }) - .filter(change => { - if (change.name !== name) { - return false; - } - - if (type && (change.type !== type)) { - return false; - } - - return true; - }); - }, name, type); - - return result; - } - async openChange(name: string): Promise { await this.api.waitAndClick(SCM_RESOURCE_CLICK(name)); } diff --git a/test/smoke/src/areas/terminal/terminal.ts b/test/smoke/src/areas/terminal/terminal.ts index 231a90b5bfa64371ab4a52d91e0a4ae9296e73ab..e69916b475f67775a7074ac87392039e9e5a2f81 100644 --- a/test/smoke/src/areas/terminal/terminal.ts +++ b/test/smoke/src/areas/terminal/terminal.ts @@ -31,31 +31,15 @@ export class Terminal { await this.api.dispatchKeybinding('enter'); } - async waitForTerminalText(fn: (text: string[]) => boolean, timeOutDescription: string = 'Getting Terminal Text'): Promise { - return this.api.waitFor(async () => { - const terminalText = await this.getTerminalText(); - if (fn(terminalText)) { - return terminalText; - } - return undefined; + async waitForTerminalText(fn: (text: string[]) => boolean, timeOutDescription: string = 'Getting Terminal Text'): Promise { + await this.api.waitFor(async () => { + const terminalText = await this.api.getTerminalBuffer(XTERM_SELECTOR); + return fn(terminalText); }, void 0, timeOutDescription); } - getCurrentLineNumber(): Promise { - return this.getTerminalText().then(text => text.length); - } - - private async getTerminalText(): Promise { - return await this.api.selectorExecute(XTERM_SELECTOR, - div => { - const xterm = ((Array.isArray(div) ? div[0] : div)).xterm; - const buffer = xterm.buffer; - const lines: string[] = []; - for (let i = 0; i < buffer.lines.length; i++) { - lines.push(buffer.translateBufferLineToString(i, true)); - } - return lines; - } - ); + async getCurrentLineNumber(): Promise { + const terminalText = await this.api.getTerminalBuffer(XTERM_SELECTOR); + return terminalText.length; } } \ No newline at end of file diff --git a/test/smoke/src/driver.ts b/test/smoke/src/driver.ts index fa661be932eada12fc6661d8d60e8c0e7337cda8..3906cf2ad6c8d3f5e67f942fed05720a7c46ebd6 100644 --- a/test/smoke/src/driver.ts +++ b/test/smoke/src/driver.ts @@ -11,6 +11,7 @@ export interface Element { className: string; textContent: string; attributes: { [name: string]: string }; + children: Element[]; } export interface Driver { @@ -24,7 +25,7 @@ export interface Driver { isActiveElement(selector: string): Promise; getElements(selector: string, recursive?: boolean): Promise; typeInEditor(selector: string, text: string): Promise; - selectorExecute

(selector: string, script: (elements: HTMLElement[], ...args: any[]) => P, ...args: any[]): Promise

; + getTerminalBuffer(selector: string): Promise; } export class SpectronDriver implements Driver { @@ -107,7 +108,8 @@ export class SpectronDriver implements Driver { tagName: element.tagName, className: element.className, textContent: element.textContent || '', - attributes: {} + attributes: {}, + children: [] }); } @@ -121,13 +123,8 @@ export class SpectronDriver implements Driver { throw new Error('Method not implemented.'); } - async selectorExecute

(selector: string, script: (elements: HTMLElement[], ...args: any[]) => P, ...args: any[]): Promise

{ - if (this.verbose) { - console.log('- selectorExecute:', selector); - } - - let _script = (element, script, ...args) => script(Array.isArray(element) ? element : [element], ...args); - return this.spectronClient.selectorExecute(selector, _script, script, ...args); + getTerminalBuffer(selector: string): Promise { + throw new Error('Method not implemented.'); } } @@ -209,26 +206,25 @@ export class CodeDriver implements Driver { } const windowId = await this.getWindowId(); - const result = await this.driver.getElements(windowId, selector, recursive); - return result; + return await this.driver.getElements(windowId, selector, recursive); } - async selectorExecute

(selector: string, script: (elements: HTMLElement[], ...args: any[]) => P, ...args: any[]): Promise

{ + async typeInEditor(selector: string, text: string): Promise { if (this.verbose) { - console.log('- selectorExecute:', selector); + console.log('- typeInEditor:', selector, text); } const windowId = await this.getWindowId(); - return await this.driver.selectorExecute(windowId, selector, script, ...args); + return await this.driver.typeInEditor(windowId, selector, text); } - async typeInEditor(selector: string, text: string): Promise { + async getTerminalBuffer(selector: string): Promise { if (this.verbose) { - console.log('- typeInEditor:', selector, text); + console.log('- getTerminalBuffer:', selector); } const windowId = await this.getWindowId(); - return await this.driver.typeInEditor(windowId, selector, text); + return await this.driver.getTerminalBuffer(windowId, selector); } private async getWindowId(): Promise { @@ -239,4 +235,37 @@ export class CodeDriver implements Driver { return this._activeWindowId; } +} + +export function findElement(element: Element, fn: (element: Element) => boolean): Element | 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: Element, fn: (element: Element) => boolean): Element[] { + const result: Element[] = []; + 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