Skip to content

Commit f090d0b

Browse files
committed
[tests] Stabilizing Public UI Tests
Signed-off-by: Victor Rubezhny <vrubezhny@redhat.com>
1 parent 054073a commit f090d0b

8 files changed

Lines changed: 143 additions & 46 deletions

File tree

.github/workflows/continuous-integration-workflow.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ jobs:
5959
if: (success() || failure()) && runner.os == 'Linux'
6060
uses: helm/kind-action@v1.14.0
6161
with:
62-
version: v0.24.0
62+
version: v0.31.0
63+
node_image: kindest/node:v1.34.0
6364

6465
- name: Configure cluster
6566
if: (success() || failure()) && runner.os == 'Linux'
@@ -105,6 +106,7 @@ jobs:
105106
with:
106107
name: artifacts-${{ matrix.os }}
107108
path: |
109+
/tmp/test-resources/settings/logs/**/*.log
108110
/tmp/test-resources/screenshots/**/*.png
109111
retention-days: 2
110112

package-lock.json

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@
129129
"dompurify": "^3.3.2",
130130
"dpdm": "^4.0.1",
131131
"esbuild": "^0.27.3",
132-
"esbuild-node-externals": "^1.20.1",
132+
"esbuild-node-externals": "^1.21.0",
133133
"esbuild-sass-plugin": "^3.6.0",
134134
"eslint": "^9.39.2",
135135
"eslint-config-prettier": "^10.1.8",

src/explorer.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,26 @@ export class OpenShiftExplorer implements TreeDataProvider<ExplorerItem>, Dispos
183183
.then(result => (allTrue(result) ? `${contextValue}.can-delete` : contextValue));
184184
}
185185

186+
186187
// eslint-disable-next-line class-methods-use-this
187188
async getTreeItem(element: ExplorerItem): Promise<TreeItem> {
189+
try {
190+
return await this._getTreeItem(element);
191+
} catch (err) {
192+
// eslint-disable-next-line no-console
193+
console.error('getTreeItem failed:', err, element);
194+
195+
return {
196+
label: '⚠️ Broken item',
197+
tooltip: `${err}`,
198+
iconPath: new ThemeIcon('error'),
199+
collapsibleState: TreeItemCollapsibleState.None
200+
};
201+
}
202+
}
203+
204+
// eslint-disable-next-line class-methods-use-this
205+
async _getTreeItem(element: ExplorerItem): Promise<TreeItem> {
188206
if ('command' in element || ('label' in element && 'iconPath' in element)) {
189207
return element;
190208
}
@@ -474,7 +492,23 @@ export class OpenShiftExplorer implements TreeDataProvider<ExplorerItem>, Dispos
474492
}
475493

476494
// eslint-disable-next-line class-methods-use-this
495+
477496
async getChildren(element?: ExplorerItem): Promise<ExplorerItem[]> {
497+
try {
498+
return await this._getChildren(element);
499+
} catch (err) {
500+
// eslint-disable-next-line no-console
501+
console.error('getChildren failed:', err);
502+
503+
return [{
504+
label: '⚠️ Error loading tree',
505+
tooltip: String(err),
506+
iconPath: new ThemeIcon('error')
507+
}];
508+
}
509+
}
510+
511+
async _getChildren(element?: ExplorerItem): Promise<ExplorerItem[]> {
478512
let result: ExplorerItem[] = [];
479513
// don't show Open In Developer Dashboard if not openshift cluster
480514
const isOpenshiftCluster = await isOpenShiftCluster(this.executionContext);

test/ui/common/ui/webview/newComponentWebViewEditor.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Licensed under the MIT License. See LICENSE file in the project root for license information.
44
*-----------------------------------------------------------------------------------------------*/
55
import { By, Key, WebElement, WebView } from 'vscode-extension-tester';
6+
import { debugClick } from '../../utils';
67
import { WebViewForm } from './WebViewForm';
78

89
//TODO: Add support for create from git page and from local codebase page
@@ -160,7 +161,8 @@ export class GitProjectPage extends Page {
160161
public async clickContinueButton(): Promise<void> {
161162
await this.enterWebView(async (webView) => {
162163
const button = await this.continueButtonExists(webView);
163-
await button.click();
164+
// await button.click();
165+
await debugClick(button, 'Continue');
164166
});
165167
}
166168

@@ -202,14 +204,16 @@ export class LocalCodeBasePage extends Page {
202204
public async clickSelectFolderButton(): Promise<void> {
203205
await this.enterWebView(async (webView) => {
204206
const button = await this.getSelectFolderButton(webView);
205-
await button.click();
207+
// await button.click();
208+
await debugClick(button, 'Select Folder');
206209
});
207210
}
208211

209212
public async clickCreateComponent(): Promise<void> {
210213
await this.enterWebView(async (webView) => {
211214
const button = await this.createButtonExists(webView);
212-
await button.click();
215+
// await button.click();
216+
await debugClick(button, 'Create Component');
213217
});
214218
}
215219

test/ui/common/utils.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*-----------------------------------------------------------------------------------------------
2+
* Copyright (c) Red Hat, Inc. All rights reserved.
3+
* Licensed under the MIT License. See LICENSE file in the project root for license information.
4+
*-----------------------------------------------------------------------------------------------*/
5+
6+
import { VSBrowser, ViewItem, ViewSection } from 'vscode-extension-tester';
7+
8+
export async function waitForItem(
9+
getSection: () => Promise<ViewSection>,
10+
label: string,
11+
timeout = 15000
12+
) {
13+
return VSBrowser.instance.driver.wait(async () => {
14+
try {
15+
const section = await getSection();
16+
const item = await section.findItem(label);
17+
return item ?? false;
18+
} catch {
19+
return false;
20+
}
21+
}, timeout, `Item "${label}" not found within ${timeout}ms`) as Promise<ViewItem>;
22+
}
23+
24+
export async function step<T>(name: string, fn: () => Promise<T>): Promise<T> {
25+
const stack = new Error().stack;
26+
27+
try {
28+
return await fn();
29+
} catch (err) {
30+
const error = err as Error;
31+
error.message = `Step failed: ${name}\n${error.message}`;
32+
error.stack = stack;
33+
throw error;
34+
}
35+
}
36+
37+
export async function debugClick(element, name: string) {
38+
try {
39+
await element.click();
40+
} catch (e) {
41+
/* eslint-disable no-console */
42+
console.error(`Failed to click: ${name}`);
43+
console.error(await element.getAttribute('outerHTML'));
44+
/* eslint-enable no-console */
45+
throw e;
46+
}
47+
}

test/ui/suite/createComponent.ts

Lines changed: 40 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
InputBox,
1414
NotificationType,
1515
SideBarView,
16+
ViewSection,
1617
VSBrowser,
1718
WelcomeContentButton,
1819
Workbench
@@ -30,6 +31,7 @@ import {
3031
RegistryWebViewDevfileWindow,
3132
RegistryWebViewEditor,
3233
} from '../common/ui/webview/registryWebViewEditor';
34+
import { step, waitForItem } from '../common/utils';
3335

3436
//TODO: Add more checks for different elements
3537
export function testCreateComponent(path: string) {
@@ -82,15 +84,15 @@ export function testCreateComponent(path: string) {
8284

8385
componentName = 'node-js-runtime';
8486

85-
const item = await waitForItem(componentName);
87+
const item = await waitForItem(getSection, componentName);
8688
expect(item).to.be.not.undefined;
8789

8890
dlt = false;
8991
});
9092

9193
it('Create component from local folder', async function test() {
9294
this.timeout(25_000);
93-
const component = await waitForItem(componentName);
95+
const component = await waitForItem(getSection, componentName);
9496
const contextMenu = await component.openContextMenu();
9597
await contextMenu.select(MENUS.deleteConfiguration);
9698

@@ -115,20 +117,42 @@ export function testCreateComponent(path: string) {
115117

116118
const localCodeBasePage = new LocalCodeBasePage();
117119
await localCodeBasePage.initializeEditor();
118-
await localCodeBasePage.insertComponentName(componentName);
119-
await localCodeBasePage.clickSelectFolderButton();
120120

121-
const input = await InputBox.create();
122-
await input.setText(pth.join(path, componentName));
123-
await input.confirm();
121+
await step('Insert component name', async () => {
122+
await localCodeBasePage.insertComponentName(componentName);
123+
});
124124

125-
await localCodeBasePage.clickNextButton();
126-
await new Promise((res) => {
127-
setTimeout(res, 500);
125+
await step('Click Select Folder button', async () => {
126+
await localCodeBasePage.clickSelectFolderButton();
127+
});
128+
129+
await step('Input component path', async () => {
130+
const input = await InputBox.create();
131+
await input.setText(pth.join(path, componentName));
132+
await input.confirm();
133+
// critical: wait until InputBox is gone
134+
await VSBrowser.instance.driver.wait(async () => {
135+
try {
136+
await InputBox.create();
137+
return false;
138+
} catch {
139+
return true;
140+
}
141+
}, 10000);
142+
});
143+
144+
await step('Click Next button', async () => {
145+
await localCodeBasePage.clickNextButton();
146+
await new Promise((res) => {
147+
setTimeout(res, 500);
148+
});
149+
});
150+
151+
await step('Click Create Component button', async () => {
152+
await localCodeBasePage.clickCreateComponent();
128153
});
129-
await localCodeBasePage.clickCreateComponent();
130154

131-
const item = await waitForItem(componentName);
155+
const item = await waitForItem(getSection, componentName);
132156
expect(item).to.be.not.undefined;
133157

134158
dlt = true;
@@ -162,7 +186,7 @@ export function testCreateComponent(path: string) {
162186

163187
//check if component is in component view
164188
componentName = 'nodejs-starter';
165-
const item = await waitForItem(componentName);
189+
const item = await waitForItem(getSection, componentName);
166190
expect(item).to.be.not.undefined;
167191

168192
dlt = false;
@@ -172,7 +196,7 @@ export function testCreateComponent(path: string) {
172196
afterEach(async function context() {
173197
this.timeout(30_000);
174198
if (componentName && dlt) {
175-
const component = await waitForItem(componentName);
199+
const component = await waitForItem(getSection, componentName);
176200
const contextMenu = await component.openContextMenu();
177201
await contextMenu.select(MENUS.deleteSourceCodeFolder);
178202
const notification = await notificationExists(
@@ -253,25 +277,8 @@ export function testCreateComponent(path: string) {
253277
}, 10_000);
254278
}
255279

256-
async function waitForItem(label, timeout = 15000) {
257-
const section = await view.getContent().getSection(VIEWS.components);
258-
259-
const start = Date.now();
260-
261-
while (Date.now() - start < timeout) {
262-
try {
263-
const item = await section.findItem(label);
264-
if (item) {
265-
return item;
266-
}
267-
} catch {
268-
// ignore transient errors
269-
}
270-
271-
await new Promise(res => setTimeout(res, 500));
272-
}
273-
274-
throw new Error(`Item "${label}" not found in section within ${timeout}ms`);
280+
async function getSection(): Promise<ViewSection> {
281+
return await view.getContent().getSection(VIEWS.components);
275282
}
276283
});
277284
}

test/ui/suite/project.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
import { activateCommand } from '../common/command-activator';
2020
import { itemExists, notificationExists } from '../common/conditions';
2121
import { INPUTS, MENUS, NOTIFICATIONS, VIEWS } from '../common/constants';
22+
import { waitForItem } from '../common/utils';
2223

2324
export function projectTest(isOpenshiftCluster: boolean) {
2425
describe('Work with project', function () {
@@ -70,7 +71,8 @@ export function projectTest(isOpenshiftCluster: boolean) {
7071

7172
it('Create a new project', async function () {
7273
this.timeout(30_000);
73-
const clusterItem = (await (await getExplorer()).findItem(clusterName)) as TreeItem;
74+
75+
const clusterItem = await waitForItem(getExplorer, clusterName) as TreeItem;
7476
await clusterItem.expand();
7577
const contextMenu = await clusterItem.openContextMenu();
7678
await contextMenu.select(newProject);
@@ -90,9 +92,10 @@ export function projectTest(isOpenshiftCluster: boolean) {
9092

9193
it('Project can be changed', async function () {
9294
this.timeout(30_000);
95+
9396
anotherProjectName = getProjectName();
9497

95-
const clusterItem = (await (await getExplorer()).findItem(clusterName)) as TreeItem;
98+
const clusterItem = await waitForItem(getExplorer, clusterName) as TreeItem;
9699
await clusterItem.expand();
97100
const contextMenu = await clusterItem.openContextMenu();
98101
await contextMenu.select(newProject);
@@ -121,7 +124,7 @@ export function projectTest(isOpenshiftCluster: boolean) {
121124
it('Delete a project', async function () {
122125
this.timeout(30_000);
123126

124-
const projectItem = await (await getExplorer()).findItem(projectName);
127+
const projectItem = await waitForItem(getExplorer, projectName) as TreeItem;
125128
const contextMenu = await projectItem.openContextMenu();
126129

127130
await contextMenu.select(deleteProject);

0 commit comments

Comments
 (0)