Skip to content

Commit 299bf7e

Browse files
pollendMichael Pollind
andauthored
fix: improve selection of webview (#1727)
Signed-off-by: Michael Pollind <[email protected]> Co-authored-by: Michael Pollind <[email protected]>
1 parent 3840648 commit 299bf7e

File tree

9 files changed

+290
-214
lines changed

9 files changed

+290
-214
lines changed

packages/page-objects/src/components/WebviewMixin.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ type Constructor<T = object> = new (...args: any[]) => T;
2828
* The interface that a class is required to have in order to use the Webview mixin.
2929
*/
3030
interface WebviewMixable extends AbstractElement {
31-
getViewToSwitchTo(handle: string): Promise<WebElement | undefined>;
31+
getViewToSwitchTo(): Promise<WebElement | undefined>;
3232
}
3333

3434
/**
@@ -89,7 +89,7 @@ export default function <TBase extends Constructor<WebviewMixable>>(Base: TBase)
8989
this.handle = await this.getDriver().getWindowHandle();
9090
}
9191

92-
const view = await this.getViewToSwitchTo(this.handle);
92+
const view = await this.getViewToSwitchTo();
9393

9494
if (!view) {
9595
return;

packages/page-objects/src/components/bottomBar/WebviewView.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,22 @@
1616
*/
1717

1818
/* eslint-disable no-redeclare */
19-
import { WebElement } from 'selenium-webdriver';
19+
import { Locator, WebElement } from 'selenium-webdriver';
2020
import { AbstractElement } from '../AbstractElement';
2121
import WebviewMixin from '../WebviewMixin';
22+
import { findBestContainingElement } from '../../locators/locators';
2223

2324
/**
2425
* Page object representing a user-contributed panel implemented using a Webview.
2526
*/
2627
class WebviewViewBase extends AbstractElement {
27-
constructor() {
28-
super(WebviewViewBase.locators.Workbench.constructor);
28+
constructor(base: Locator | WebElement = WebviewViewBase.locators.Workbench.constructor, enclosingItem?: WebElement | Locator) {
29+
super(base, enclosingItem);
2930
}
3031

31-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
32-
async getViewToSwitchTo(handle: string): Promise<WebElement | undefined> {
33-
return await this.getDriver().findElement(WebviewViewBase.locators.WebviewView.iframe);
32+
async getViewToSwitchTo(): Promise<WebElement | undefined> {
33+
const frames = await this.getDriver().findElements(WebviewViewBase.locators.WebView.iframe);
34+
return findBestContainingElement(await this.getRect(), frames);
3435
}
3536
}
3637

packages/page-objects/src/components/editor/WebView.ts

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,15 @@
1919
import { WebElement } from 'selenium-webdriver';
2020
import WebviewMixin from '../WebviewMixin';
2121
import { Editor } from './Editor';
22+
import { findBestContainingElement } from '../../locators/locators';
2223

2324
/**
2425
* Page object representing an open editor containing a web view
2526
*/
2627
class WebViewBase extends Editor {
27-
async getViewToSwitchTo(handle: string): Promise<WebElement | undefined> {
28-
const handles = await this.getDriver().getAllWindowHandles();
29-
for (const handle of handles) {
30-
await this.getDriver().switchTo().window(handle);
31-
32-
if ((await this.getDriver().getTitle()).includes('Virtual Document')) {
33-
await this.getDriver().switchTo().frame(0);
34-
return;
35-
}
36-
}
37-
await this.getDriver().switchTo().window(handle);
38-
return await this.getDriver().findElement(WebViewBase.locators.WebView.iframe);
28+
async getViewToSwitchTo(): Promise<WebElement | undefined> {
29+
const frames = await this.getDriver().findElements(WebViewBase.locators.WebView.iframe);
30+
return findBestContainingElement(await this.getRect(), frames);
3931
}
4032
}
4133

packages/page-objects/src/locators/locators.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { By, WebElement } from 'selenium-webdriver';
18+
import { By, IRectangle, WebElement } from 'selenium-webdriver';
1919
import { PartialDeep } from 'type-fest';
2020
import { ViewSection } from '../components/sidebar/ViewSection';
2121

@@ -618,3 +618,26 @@ export function fromText(locator?: By): (el: WebElement) => Promise<string> {
618618
return await el.getText();
619619
};
620620
}
621+
622+
export async function findBestContainingElement(container: IRectangle, testElements: WebElement[]): Promise<WebElement | undefined> {
623+
const areas: number[] = await Promise.all(
624+
testElements.map(async (value) => {
625+
const rect = await value.getRect();
626+
const ax = Math.max(container.x, rect.x);
627+
const ay = Math.max(container.y, rect.y);
628+
const bx = Math.min(container.x + container.width, rect.x + rect.width);
629+
const by = Math.min(container.y + container.height, rect.y + rect.height);
630+
return (bx - ax) * (by - ay);
631+
}),
632+
);
633+
let bestIdx: number = -1;
634+
for (let i = 0; i < testElements.length; i++) {
635+
if (areas[i] < 0) {
636+
continue;
637+
}
638+
if (bestIdx === -1 || areas[i] > areas[bestIdx]) {
639+
bestIdx = i;
640+
}
641+
}
642+
return bestIdx === -1 ? undefined : testElements[bestIdx];
643+
}

tests/test-project/package.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,19 @@
7474
},
7575
{
7676
"command": "extension.webview",
77-
"title": "Webview Test"
77+
"title": "Webview Test Column 1"
78+
},
79+
{
80+
"command": "extension.webview.2",
81+
"title": "Webview Test Column 2"
82+
},
83+
{
84+
"command": "extension.webview.3",
85+
"title": "Webview Test Column 3"
86+
},
87+
{
88+
"command": "extension.webview.4",
89+
"title": "Webview Test Column 4"
7890
},
7991
{
8092
"command": "extension.notification",

tests/test-project/src/extension.ts

Lines changed: 51 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,18 @@ export function activate(context: vscode.ExtensionContext) {
2424
await vscode.window.showQuickPick([{ label: 'TestLabel', description: 'Test Description' }]);
2525
});
2626
const webViewCommand = vscode.commands.registerCommand('extension.webview', async () => {
27-
new TestView();
27+
const col: vscode.ViewColumn = (vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined) || vscode.ViewColumn.One;
28+
openTestView(col);
2829
});
30+
31+
const columns: vscode.ViewColumn[] = [vscode.ViewColumn.Two, vscode.ViewColumn.Three, vscode.ViewColumn.Four];
32+
for (const c of columns) {
33+
const webViewCmd = vscode.commands.registerCommand(`extension.webview.${c}`, async () => {
34+
openTestView(c);
35+
});
36+
context.subscriptions.push(webViewCmd);
37+
}
38+
2939
const notificationCommand = vscode.commands.registerCommand('extension.notification', async () => {
3040
await vscode.window.showInformationMessage('This is a notification', 'Yes', 'No');
3141
});
@@ -111,58 +121,49 @@ export function deactivate() {}
111121
let emptyViewNoContent: boolean = true;
112122
const emitter = new vscode.EventEmitter<undefined>();
113123

114-
class TestView {
115-
public static readonly viewType = 'testView';
116-
117-
private readonly _panel: vscode.WebviewPanel;
118-
private _disposables: vscode.Disposable[] = [];
119-
private randomWebViewTitle: string;
120-
121-
constructor() {
122-
const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined;
123-
124-
this.randomWebViewTitle = 'Test WebView ' + Math.floor(Math.random() * 100);
125-
this._panel = vscode.window.createWebviewPanel(TestView.viewType, this.randomWebViewTitle, column || vscode.ViewColumn.One);
126-
this.update(this.randomWebViewTitle);
127-
128-
this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
129-
130-
this._panel.onDidChangeViewState(
131-
() => {
132-
if (this._panel.visible) {
133-
this.update(this.randomWebViewTitle);
124+
function openTestView(viewColumn: vscode.ViewColumn) {
125+
const randomWebViewTitle = 'Test WebView ' + Math.floor(Math.random() * 100);
126+
const panel = vscode.window.createWebviewPanel('testView', randomWebViewTitle, viewColumn);
127+
const disposables: vscode.Disposable[] = [];
128+
129+
const update = (title: string) => {
130+
panel.title = title;
131+
panel.webview.html = `<!DOCTYPE html>
132+
<html lang="en">
133+
<head>
134+
<meta charset="UTF-8">
135+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
136+
<title>${title}</title>
137+
</head>
138+
<body>
139+
<h1>This is a web view with title: ${title}</h1>
140+
</body>
141+
</html>`;
142+
};
143+
update(randomWebViewTitle);
144+
145+
panel.onDidDispose(
146+
() => {
147+
while (disposables.length) {
148+
const x = disposables.pop();
149+
if (x) {
150+
x.dispose();
134151
}
135-
},
136-
null,
137-
this._disposables,
138-
);
139-
}
140-
141-
public dispose() {
142-
this._panel.dispose();
143-
144-
while (this._disposables.length) {
145-
const x = this._disposables.pop();
146-
if (x) {
147-
x.dispose();
148152
}
149-
}
150-
}
153+
},
154+
null,
155+
disposables,
156+
);
151157

152-
private update(title: string) {
153-
this._panel.title = title;
154-
this._panel.webview.html = `<!DOCTYPE html>
155-
<html lang="en">
156-
<head>
157-
<meta charset="UTF-8">
158-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
159-
<title>${title}</title>
160-
</head>
161-
<body>
162-
<h1>This is a web view with title: ${title}</h1>
163-
</body>
164-
</html>`;
165-
}
158+
panel.onDidChangeViewState(
159+
() => {
160+
if (panel.visible) {
161+
update(randomWebViewTitle);
162+
}
163+
},
164+
null,
165+
disposables,
166+
);
166167
}
167168

168169
class MyPanelView implements vscode.WebviewViewProvider {

tests/test-project/src/test/editor/editorView.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ describe('EditorView', function () {
4343
await view.closeAllEditors();
4444
await newUntitledFile('Untitled-1');
4545
await newUntitledFile('Untitled-2');
46-
await new Workbench().executeCommand('Webview Test');
46+
await new Workbench().executeCommand('Webview Test Column 1');
4747
await view.getDriver().sleep(2500);
4848
await new Workbench().executeCommand('Open Settings UI');
4949
await view.getDriver().sleep(500);

0 commit comments

Comments
 (0)