提交 5d48d471 编写于 作者: J Joao Moreno

remove API

上级 1ebb0dd2
......@@ -34,7 +34,7 @@ export interface IDriver {
setValue(windowId: number, selector: string, text: string): TPromise<void>;
getTitle(windowId: number): TPromise<string>;
isActiveElement(windowId: number, selector: string): TPromise<boolean>;
getElements(windowId: number, selector: string, recursive: boolean): TPromise<IElement[]>;
getElements(windowId: number, selector: string, recursive?: boolean): TPromise<IElement[]>;
typeInEditor(windowId: number, selector: string, text: string): TPromise<void>;
getTerminalBuffer(windowId: number, selector: string): TPromise<string[]>;
}
......
/*---------------------------------------------------------------------------------------------
* 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<void> {
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<any> {
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<any> {
if (this.verbose) {
console.log('- doubleClick:', selector);
}
const windowId = await this.getWindowId();
await this.driver.doubleClick(windowId, selector);
}
async move(selector: string): Promise<any> {
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<void> {
if (this.verbose) {
console.log('- setValue:', selector, text);
}
const windowId = await this.getWindowId();
await this.driver.setValue(windowId, selector, text);
}
async getTitle(): Promise<string> {
if (this.verbose) {
console.log('- getTitle:');
}
const windowId = await this.getWindowId();
return await this.driver.getTitle(windowId);
}
async isActiveElement(selector: string): Promise<boolean> {
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<IElement[]> {
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<void> {
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<string[]> {
if (this.verbose) {
console.log('- getTerminalBuffer:', selector);
}
const windowId = await this.getWindowId();
return await this.driver.getTerminalBuffer(windowId, selector);
}
private async getWindowId(): Promise<number> {
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<void> {
return this.driver.dispatchKeybinding(keybinding);
}
waitForTextContent(selector: string, textContent?: string, accept?: (result: string) => boolean): Promise<string> {
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<any> {
await this.waitForElement(selector);
return await this.driver.click(selector, xoffset, yoffset);
}
async waitAndDoubleClick(selector: string): Promise<any> {
await this.waitForElement(selector);
return await this.driver.doubleClick(selector);
}
async waitAndMove(selector: string): Promise<any> {
await this.waitForElement(selector);
return await this.driver.move(selector);
}
async setValue(selector: string, text: string): Promise<any> {
await this.waitForElement(selector);
return await this.driver.setValue(selector, text);
}
async doesElementExist(selector: string): Promise<boolean> {
const elements = await this.driver.getElements(selector);
return elements.length > 0;
}
async getElementCount(selector: string): Promise<number> {
const elements = await this.driver.getElements(selector);
return elements.length;
}
waitForElements(selector: string, recursive: boolean, accept: (result: IElement[]) => boolean = result => result.length > 0): Promise<IElement[]> {
return this.waitFor(() => this.driver.getElements(selector, recursive), accept, `elements with selector ${selector}`) as Promise<any>;
}
waitForElement(selector: string, accept: (result: IElement | undefined) => boolean = result => !!result): Promise<void> {
return this.waitFor(() => this.driver.getElements(selector).then(els => els[0]), accept, `element with selector ${selector}`) as Promise<any>;
}
waitForActiveElement(selector: string): Promise<any> {
return this.waitFor(() => this.driver.isActiveElement(selector), undefined, `wait for active element: ${selector}`);
}
getTitle(): Promise<string> {
return this.driver.getTitle();
}
typeInEditor(selector: string, text: string): Promise<void> {
return this.driver.typeInEditor(selector, text);
}
getTerminalBuffer(selector: string): Promise<string[]> {
return this.driver.getTerminalBuffer(selector);
}
private running = false;
async waitFor<T>(func: () => T | Promise<T | undefined>, accept?: (result: T) => boolean | Promise<boolean>, timeoutMessage?: string, retryCount?: number): Promise<T>;
async waitFor<T>(func: () => T | Promise<T>, accept: (result: T) => boolean | Promise<boolean> = result => !!result, timeoutMessage?: string, retryCount?: number): Promise<T> {
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
......@@ -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<void>) | 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<any> {
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<any> {
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<any> {
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<void> {
......
......@@ -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<void> {
async getActivityBar(position: ActivityBarPosition): Promise<void> {
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
......@@ -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();
});
});
......
......@@ -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 () {
......
......@@ -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<any> {
await this.commands.runCommand('workbench.view.debug');
await this.api.waitForElement(DEBUG_VIEW);
await this.code.waitForElement(DEBUG_VIEW);
}
async configure(): Promise<any> {
await this.api.waitAndClick(CONFIGURE);
await this.code.waitAndClick(CONFIGURE);
await this.editors.waitForEditorFocus('launch.json');
}
async setBreakpointOnLine(lineNumber: number): Promise<any> {
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<number> {
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<any> {
await this.api.waitAndClick(STEP_OVER);
await this.code.waitAndClick(STEP_OVER);
}
async stepIn(): Promise<any> {
await this.api.waitAndClick(STEP_IN);
await this.code.waitAndClick(STEP_IN);
}
async stepOut(): Promise<any> {
await this.api.waitAndClick(STEP_OUT);
await this.code.waitAndClick(STEP_OUT);
}
async continue(): Promise<any> {
await this.api.waitAndClick(CONTINUE);
await this.code.waitAndClick(CONTINUE);
await this.waitForStackFrameLength(0);
}
async stopDebugging(): Promise<any> {
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<IStackFrame> {
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<any> {
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<any> {
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<void> {
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<number> {
return await this.api.getElementCount(VARIABLE);
async waitForVariableCount(count: number): Promise<void> {
await this.code.waitForElements(VARIABLE, false, els => els.length === count);
}
private async waitForOutput(fn: (output: string[]) => boolean): Promise<string[]> {
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);
}
}
......@@ -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<References> {
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<void> {
......@@ -46,7 +46,7 @@ export class Editor {
async peekDefinition(term: string, line: number): Promise<References> {
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<void> {
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<any> {
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<any> {
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<void> {
......@@ -84,18 +84,18 @@ export class Editor {
async clickOnTerm(term: string, line: number): Promise<void> {
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<any> {
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<any> {
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<string[]> {
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<number> {
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}`);
});
......
......@@ -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<any> {
await this.commands.runCommand('workbench.action.files.save');
}
async selectTab(tabName: string, untitled: boolean = false): Promise<void> {
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<any> {
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<void> {
......@@ -30,11 +30,11 @@ export class Editors {
}
async waitForActiveTab(fileName: string, isDirty: boolean = false): Promise<void> {
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<void> {
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<void> {
......
......@@ -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<void> {
await this.api.waitForElement(References.REFERENCES_WIDGET);
await this.code.waitForElement(References.REFERENCES_WIDGET);
}
async waitForReferencesCountInTitle(count: number): Promise<void> {
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<void> {
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<void> {
await this.api.waitForTextContent(References.REFERENCES_TITLE_FILE_NAME, file);
await this.code.waitForTextContent(References.REFERENCES_TITLE_FILE_NAME, file);
}
async close(): Promise<void> {
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
......@@ -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
......@@ -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<any> {
......@@ -22,11 +22,11 @@ export class Explorer extends Viewlet {
}
getOpenEditorsViewTitle(): Promise<string> {
return this.api.waitForTextContent(Explorer.OPEN_EDITORS_VIEW);
return this.code.waitForTextContent(Explorer.OPEN_EDITORS_VIEW);
}
async openFile(fileName: string): Promise<any> {
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);
}
......
......@@ -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<any> {
......@@ -21,19 +21,19 @@ export class Extensions extends Viewlet {
}
async waitForExtensionsViewlet(): Promise<any> {
await this.api.waitForActiveElement(SEARCH_BOX);
await this.code.waitForActiveElement(SEARCH_BOX);
}
async searchForExtension(name: string): Promise<any> {
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<void> {
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
......@@ -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 });
});
......
......@@ -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<any> {
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<void> {
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<any> {
await this.api.waitAndClick(REFRESH_COMMAND);
await this.code.waitAndClick(REFRESH_COMMAND);
}
async openChange(name: string): Promise<void> {
await this.api.waitAndClick(SCM_RESOURCE_CLICK(name));
await this.code.waitAndClick(SCM_RESOURCE_CLICK(name));
}
async stage(name: string): Promise<void> {
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<void> {
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<void> {
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<void> {
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
......@@ -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);
});
});
......
......@@ -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<any> {
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
......@@ -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.');
});
......
......@@ -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<void> {
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();
}
......
......@@ -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<any> {
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<boolean> {
return this.api.doesElementExist(Problems.PROBLEMS_VIEW_SELECTOR);
return this.code.doesElementExist(Problems.PROBLEMS_VIEW_SELECTOR);
}
public async waitForProblemsView(): Promise<void> {
await this.api.waitForElement(Problems.PROBLEMS_VIEW_SELECTOR);
await this.code.waitForElement(Problems.PROBLEMS_VIEW_SELECTOR);
}
public static getSelectorInProblemsView(problemType: ProblemSeverity): string {
......
......@@ -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<void> {
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<void> {
await this.api.waitForActiveElement(QuickOpen.QUICK_OPEN_INPUT);
await this.code.waitForActiveElement(QuickOpen.QUICK_OPEN_INPUT);
}
private async waitForQuickOpenClosed(): Promise<void> {
await this.api.waitForElement(QuickOpen.QUICK_OPEN_HIDDEN);
await this.code.waitForElement(QuickOpen.QUICK_OPEN_HIDDEN);
}
async submit(text: string): Promise<void> {
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<void> {
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<void> {
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<void> {
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<void> {
......@@ -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;
......
......@@ -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<any> {
await this.commands.runCommand('workbench.view.search');
await this.api.waitForActiveElement(INPUT);
await this.code.waitForActiveElement(INPUT);
}
async searchFor(text: string): Promise<void> {
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<void> {
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<void> {
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<void> {
if (!await this.areDetailsVisible()) {
await this.api.waitAndClick(`${VIEWLET} .query-details .more`);
await this.code.waitAndClick(`${VIEWLET} .query-details .more`);
}
}
async hideQueryDetails(): Promise<void> {
if (await this.areDetailsVisible()) {
await this.api.waitAndClick(`${VIEWLET} .query-details.more .more`);
await this.code.waitAndClick(`${VIEWLET} .query-details.more .more`);
}
}
areDetailsVisible(): Promise<boolean> {
return this.api.doesElementExist(`${VIEWLET} .query-details.more`);
return this.code.doesElementExist(`${VIEWLET} .query-details.more`);
}
async removeFileMatch(index: number): Promise<void> {
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<void> {
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<void> {
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<void> {
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<void> {
await this.api.waitForTextContent(`${VIEWLET} .messages[aria-hidden="false"] .message>p`, text);
await this.code.waitForTextContent(`${VIEWLET} .messages[aria-hidden="false"] .message>p`, text);
}
}
......@@ -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 () {
......
......@@ -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<void> {
await this.api.waitForElement(this.getSelector(element));
await this.code.waitForElement(this.getSelector(element));
}
async clickOn(element: StatusBarElement): Promise<void> {
await this.api.waitAndClick(this.getSelector(element));
await this.code.waitAndClick(this.getSelector(element));
}
async waitForEOL(eol: string): Promise<string> {
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<string> {
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 {
......
......@@ -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<string> {
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
......@@ -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<any>;
......@@ -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);
}
}
......@@ -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<Code> {
async function connect(child: cp.ChildProcess, outPath: string, handlePath: string, verbose: boolean): Promise<Code> {
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<Code> {
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<void> {
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<string> {
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<void> {
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<void> {
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<void> {
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<void> {
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<boolean> {
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<number> {
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<IElement[]> {
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<IElement> {
if (this.verbose) {
console.log('- waitForElement:', selector);
}
const windowId = await this.getActiveWindowId();
return await this.waitFor<IElement>(() => this.driver.getElements(windowId, selector).then(els => els[0]), accept, `element with selector ${selector}`);
}
async waitForActiveElement(selector: string): Promise<any> {
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<string> {
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<void> {
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<T>(func: () => T | Promise<T | undefined>, accept?: (result: T) => boolean | Promise<boolean>, timeoutMessage?: string, retryCount?: number): Promise<T>;
private async waitFor<T>(func: () => T | Promise<T>, accept: (result: T) => boolean | Promise<boolean> = result => !!result, timeoutMessage?: string, retryCount?: number): Promise<T> {
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<number[]> {
return await this.driver.getWindowIds();
}
private async getActiveWindowId(): Promise<number> {
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
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册