-
Notifications
You must be signed in to change notification settings - Fork 72
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixes #804 Signed-off-by: David Thompson <[email protected]>
- Loading branch information
Showing
9 changed files
with
210 additions
and
70 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(): 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(): 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), | ||
5000 | ||
); | ||
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>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { WebElement, until } 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); | ||
} | ||
|
||
getViewToSwitchTo(handle: string): Promise<WebElement | undefined> { | ||
return this.getDriver().wait(until.elementLocated(WebviewViewBase.locators.WebView.iframe)); | ||
} | ||
|
||
} | ||
|
||
export const WebviewView = WebviewMixin(WebviewViewBase); | ||
export type WebviewView = InstanceType<typeof WebviewView>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { expect } from 'chai'; | ||
import { BottomBarPanel, By, InputBox, WebviewView, Workbench } from 'vscode-extension-tester'; | ||
|
||
describe('WebviewView', function () { | ||
|
||
let webviewView: InstanceType<typeof WebviewView>; | ||
|
||
before(async function () { | ||
const prompt = await new Workbench().openCommandPrompt() as InputBox; | ||
await prompt.setText('>My Panel: Focus on My Panel View View'); | ||
await prompt.confirm(); | ||
}); | ||
|
||
after(async function () { | ||
if (webviewView) { | ||
await webviewView.switchBack(); | ||
webviewView = undefined; | ||
} | ||
await new BottomBarPanel().toggle(false); | ||
}); | ||
|
||
it('contains apple and banana', async () => { | ||
webviewView = new WebviewView(); | ||
await webviewView.switchToFrame(); | ||
const elts = await webviewView.findWebElements(By.xpath('//div/ul/li')); | ||
const listContent: string[] = []; | ||
await Promise.all(elts.map(async elt => { | ||
listContent.push(await elt.getText()); | ||
})); | ||
expect(listContent).to.have.length(2); | ||
expect(listContent).to.contain('Apple'); | ||
expect(listContent).to.contain('Banana'); | ||
}); | ||
|
||
}); |