Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion locators/lib/1.37.0.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { By, WebElement } from "selenium-webdriver";
import { Locators, ViewSection, fromAttribute, fromText, hasAttribute, hasClass, hasElement, hasNotClass } from "monaco-page-objects";
import { By, WebElement } from "selenium-webdriver";

const abstractElement = {
AbstractElement: {
Expand Down Expand Up @@ -79,6 +79,9 @@ const bottomBar = {
OutputView: {
constructor: By.id('workbench.panel.output'),
actionsLabel: 'Output actions'
},
WebviewView: {
iframe: By.xpath(`//div[not(@class)]/iframe[@class='webview ready' and not(@data-parent-flow-to-element-id)]`)
}
}

Expand Down
105 changes: 105 additions & 0 deletions page-objects/src/components/WebviewMixin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { Locator, WebElement, until } from "selenium-webdriver";
import { AbstractElement } from "./AbstractElement";

/**
* Heavily inspired by https://stackoverflow.com/a/65418734
*/

type Constructor<T = {}> = new (...args: any[]) => T;

/**
* The interface that a class is required to have in order to use the Webview mixin.
*/
interface WebviewMixable extends AbstractElement {
getViewToSwitchTo(handle: string): Promise<WebElement | undefined>;
}

/**
* The interface that is exposed by applying this mixin.
*/
export interface WebviewMixinType {
findWebElement(locator: Locator): Promise<WebElement>;
findWebElements(locator: Locator): Promise<WebElement[]>;
switchToFrame(timeout?: number): Promise<void>;
switchBack(): Promise<void>;
}

/**
* Returns a class that has the ability to access a webview.
*
* @param Base the class to mixin
* @returns a class that has the ability to access a webview
*/
export default function <TBase extends Constructor<WebviewMixable>>(
Base: TBase
): Constructor<InstanceType<TBase> & WebviewMixinType> {
return class extends Base implements WebviewMixinType {
/**
* Cannot use static element, since this class is unnamed.
*/
private handle: string | undefined;

/**
* Search for an element inside the webview iframe.
* Requires webdriver being switched to the webview iframe first.
* (Will attempt to search from the main DOM root otherwise)
*
* @param locator webdriver locator to search by
* @returns promise resolving to WebElement when found
*/
async findWebElement(locator: Locator): Promise<WebElement> {
return await this.getDriver().findElement(locator);
}

/**
* Search for all element inside the webview iframe by a given locator
* Requires webdriver being switched to the webview iframe first.
* (Will attempt to search from the main DOM root otherwise)
*
* @param locator webdriver locator to search by
* @returns promise resolving to a list of WebElement objects
*/
async findWebElements(locator: Locator): Promise<WebElement[]> {
return await this.getDriver().findElements(locator);
}

/**
* Switch the underlying webdriver context to the webview iframe.
* This allows using the findWebElement methods.
* Note that only elements inside the webview iframe will be accessible.
* Use the switchBack method to switch to the original context.
*/
async switchToFrame(timeout: number = 5000): Promise<void> {
if (!this.handle) {
this.handle = await this.getDriver().getWindowHandle();
}

const view = await this.getViewToSwitchTo(this.handle);

if (!view) {
return;
}

await this.getDriver().switchTo().frame(view);

await this.getDriver().wait(
until.elementLocated(AbstractElement.locators.WebView.activeFrame),
timeout
);
const frame = await this.getDriver().findElement(
AbstractElement.locators.WebView.activeFrame
);
await this.getDriver().switchTo().frame(frame);
}

/**
* Switch the underlying webdriver back to the original window
*/
async switchBack(): Promise<void> {
if (!this.handle) {
this.handle = await this.getDriver().getWindowHandle();
}
return await this.getDriver().switchTo().window(this.handle);
}
} as unknown as Constructor<InstanceType<TBase> & WebviewMixinType>;
}
21 changes: 21 additions & 0 deletions page-objects/src/components/bottomBar/WebviewView.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { WebElement } from "selenium-webdriver";
import { AbstractElement } from "../AbstractElement";
import WebviewMixin from "../WebviewMixin";

/**
* Page object representing a user-contributed panel implemented using a Webview.
*/
class WebviewViewBase extends AbstractElement {

constructor() {
super(WebviewViewBase.locators.Workbench.constructor);
}

async getViewToSwitchTo(handle: string): Promise<WebElement | undefined> {
return await this.getDriver().findElement(WebviewViewBase.locators.WebviewView.iframe);
}

}

export const WebviewView = WebviewMixin(WebviewViewBase);
export type WebviewView = InstanceType<typeof WebviewView>;
2 changes: 1 addition & 1 deletion page-objects/src/components/editor/CustomEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class CustomEditor extends Editor {

/**
* Open the Save as prompt
*
*
* @returns InputBox serving as a simple file dialog
*/
async saveAs(): Promise<InputBox> {
Expand Down
10 changes: 5 additions & 5 deletions page-objects/src/components/editor/EditorView.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { AbstractElement } from "../AbstractElement";
import { TextEditor } from "../..";
import { error, WebElement } from "selenium-webdriver";
import { TextEditor } from "../..";
import { AbstractElement } from "../AbstractElement";
import { ElementWithContexMenu } from "../ElementWithContextMenu";
import { DiffEditor } from './DiffEditor';
import { Editor } from "./Editor";
import { EditorAction } from "./EditorAction";
import { SettingsEditor } from "./SettingsEditor";
import { WebView } from "./WebView";
import { DiffEditor } from './DiffEditor';
import { ElementWithContexMenu } from "../ElementWithContextMenu";
import { EditorAction } from "./EditorAction";

export class EditorTabNotFound extends Error {
constructor(title: string, group: number) {
Expand Down
73 changes: 13 additions & 60 deletions page-objects/src/components/editor/WebView.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,13 @@
import { until, WebElement } from "selenium-webdriver";
import WebviewMixin from "../WebviewMixin";
import { Editor } from "./Editor";
import { Locator, until, WebElement } from "selenium-webdriver";

/**
* Page object representing an open editor containing a web view
*/
export class WebView extends Editor {

private static handle: string | undefined;

/**
* Search for an element inside the webview iframe.
* Requires webdriver being switched to the webview iframe first.
* (Will attempt to search from the main DOM root otherwise)
*
* @param locator webdriver locator to search by
* @returns promise resolving to WebElement when found
*/
async findWebElement(locator: Locator): Promise<WebElement> {
return await this.getDriver().findElement(locator);
}

/**
* Search for all element inside the webview iframe by a given locator
* Requires webdriver being switched to the webview iframe first.
* (Will attempt to search from the main DOM root otherwise)
*
* @param locator webdriver locator to search by
* @returns promise resolving to a list of WebElement objects
*/
async findWebElements(locator: Locator): Promise<WebElement[]> {
return await this.getDriver().findElements(locator);
}

/**
* Switch the underlying webdriver context to the webview iframe.
* This allows using the findWebElement methods.
* Note that only elements inside the webview iframe will be accessible.
* Use the switchBack method to switch to the original context.
*/
async switchToFrame(): Promise<void> {
if (!WebView.handle) {
WebView.handle = await this.getDriver().getWindowHandle();
}
class WebViewBase extends Editor {

async getViewToSwitchTo(handle: string): Promise<WebElement | undefined> {
const handles = await this.getDriver().getAllWindowHandles();
for (const handle of handles) {
await this.getDriver().switchTo().window(handle);
Expand All @@ -52,35 +17,23 @@ export class WebView extends Editor {
return;
}
}
await this.getDriver().switchTo().window(WebView.handle);
await this.getDriver().switchTo().window(handle);

const reference = await this.findElement(WebView.locators.EditorView.webView);
const containers = await this.getDriver().wait(until.elementsLocated(WebView.locators.WebView.container(await reference.getAttribute(WebView.locators.WebView.attribute))), 5000);
const reference = await this.findElement(WebViewBase.locators.EditorView.webView);
const containers = await this.getDriver().wait(until.elementsLocated(WebViewBase.locators.WebView.container(await reference.getAttribute(WebViewBase.locators.WebView.attribute))), 5000);

const view = await containers[0].getDriver().wait(async () => {
return await containers[0].getDriver().wait(async () => {
for (let index = 0; index < containers.length; index++) {
const tries = await containers[index].findElements(WebView.locators.WebView.iframe);
const tries = await containers[index].findElements(WebViewBase.locators.WebView.iframe);
if (tries.length > 0) {
return tries[0];
}
}
return undefined;
}, 5000) as WebElement;

await this.getDriver().switchTo().frame(view);

await this.getDriver().wait(until.elementLocated(WebView.locators.WebView.activeFrame), 5000);
const frame = await this.getDriver().findElement(WebView.locators.WebView.activeFrame);
await this.getDriver().switchTo().frame(frame);
}

/**
* Switch the underlying webdriver back to the original window
*/
async switchBack(): Promise<void> {
if (!WebView.handle) {
WebView.handle = await this.getDriver().getWindowHandle();
}
return await this.getDriver().switchTo().window(WebView.handle);
}
}
}

export const WebView = WebviewMixin(WebViewBase);
export type WebView = InstanceType<typeof WebView>;
3 changes: 2 additions & 1 deletion page-objects/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export * from './components/sidebar/debug/DebugView';

export * from './components/bottomBar/BottomBarPanel';
export * from './components/bottomBar/ProblemsView';
export * from './components/bottomBar/WebviewView';
export * from './components/bottomBar/Views';
export * from './components/statusBar/StatusBar';

Expand Down Expand Up @@ -69,7 +70,7 @@ export * from './conditions/WaitForAttribute';

/**
* Initialize the page objects for your tests
*
*
* @param currentVersion version of the locators to load
* @param baseVersion base version of the locators if you have multiple versions with diffs, otherwise leave the same as currentVersion
* @param locatorFolder folder that contains locator files
Expand Down
3 changes: 3 additions & 0 deletions page-objects/src/locators/locators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ export interface Locators {
constructor: By
actionsLabel: string
}
WebviewView: {
iframe: By
}

// Editors
EditorView: {
Expand Down
16 changes: 16 additions & 0 deletions test/test-project/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,15 @@
"title": "Disable Codelens"
}
],
"viewsContainers": {
"panel": [
{
"icon": "./media/paw-outline.svg",
"id": "myPanel",
"title": "My Panel"
}
]
},
"views": {
"explorer": [
{
Expand All @@ -93,6 +102,13 @@
"id": "emptyView",
"name": "Empty View"
}
],
"myPanel": [
{
"id": "myPanelView",
"name": "My Panel View",
"type": "webview"
}
]
},
"viewsWelcome": [
Expand Down
15 changes: 12 additions & 3 deletions test/test-project/src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as vscode from 'vscode';
import * as path from 'path';
import { TreeView } from './treeView';
import * as vscode from 'vscode';
import { CatScratchEditorProvider } from './catScratchEditor';
import { CodelensProvider } from './codelensProvider';
import { TreeView } from './treeView';

export const ERROR_MESSAGE_COMMAND = 'extension.errorMsg';

Expand Down Expand Up @@ -60,7 +60,7 @@ export function activate(context: vscode.ExtensionContext) {
"extension.populateTestView",
() => { emptyViewNoContent = false; emitter.fire(undefined); }
));

const codelensProvider = new CodelensProvider();
context.subscriptions.push(vscode.languages.registerCodeLensProvider("*", codelensProvider));
context.subscriptions.push(
Expand All @@ -75,6 +75,9 @@ export function activate(context: vscode.ExtensionContext) {
vscode.commands.registerCommand("extension.codelensAction", (args: any) => {
vscode.window.showInformationMessage(`CodeLens action clicked with args=${args}`);
}));
context.subscriptions.push(
vscode.window.registerWebviewViewProvider("myPanelView", new MyPanelView())
);
}

export function deactivate() {}
Expand Down Expand Up @@ -135,4 +138,10 @@ class TestView {
</body>
</html>`;
}
}

class MyPanelView implements vscode.WebviewViewProvider {
resolveWebviewView(webviewView: vscode.WebviewView, context: vscode.WebviewViewResolveContext<unknown>, token: vscode.CancellationToken): void | Thenable<void> {
webviewView.webview.html = "<!DOCTYPE html><html><head><title>My Panel View</title></head><body><div><h1>Shopping List</h1><ul><li>Apple</li><li>Banana</li></ul></div></body></html>";
}
}
Loading