Skip to content

Commit 17f9ab7

Browse files
committed
[tests] Stabilizing Public UI Tests
- Updated Kind: - kind v0.31.0 go1.25.5 linux/amd64 - Client Version: v1.35.0 - Tune up for Code Coverage v6.0.0 - Public UI Tests targeted: - Project test - Create Component test - Component Context Menu test - Operator Backed Service test - Log files included into the artifacts to be uploaded Signed-off-by: Victor Rubezhny <vrubezhny@redhat.com>
1 parent b121800 commit 17f9ab7

File tree

9 files changed

+829
-145
lines changed

9 files changed

+829
-145
lines changed

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ jobs:
3030
steps:
3131
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
3232
- uses: actions/checkout@v6
33+
with:
34+
fetch-depth: 1
3335

3436
# Set up Node
3537
- name: Use Node.js ${{ matrix.node }}
@@ -59,7 +61,8 @@ jobs:
5961
if: (success() || failure()) && runner.os == 'Linux'
6062
uses: helm/kind-action@v1.14.0
6163
with:
62-
version: v0.24.0
64+
version: v0.31.0
65+
node_image: kindest/node:v1.34.0
6366

6467
- name: Configure cluster
6568
if: (success() || failure()) && runner.os == 'Linux'
@@ -105,6 +108,7 @@ jobs:
105108
with:
106109
name: artifacts-${{ matrix.os }}
107110
path: |
111+
/tmp/test-resources/settings/logs/**/*.log
108112
/tmp/test-resources/screenshots/**/*.png
109113
retention-days: 2
110114

package-lock.json

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

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/conditions.ts

Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ import {
1111
NotificationType,
1212
ViewItem,
1313
ViewSection,
14+
VSBrowser,
1415
WebDriver,
1516
WelcomeContentSection,
16-
Workbench,
17+
Workbench
1718
} from 'vscode-extension-tester';
1819

1920
export async function waitForInputUpdate(
@@ -97,6 +98,110 @@ export async function itemExists(
9798
);
9899
}
99100

101+
export async function stabilizeComponentsView(getSection: () => Promise<ViewSection>, timeout = 20000): Promise<ViewSection> {
102+
const result = await VSBrowser.instance.driver.wait(async () => {
103+
try {
104+
let section = await getSection();
105+
106+
await section.expand();
107+
await new Promise(res => setTimeout(res, 300));
108+
109+
section = await getSection();
110+
111+
// ---- try tree mode ----
112+
try {
113+
const items = await section.getVisibleItems();
114+
await Promise.all(items.map(i => i.getText()));
115+
return section;
116+
} catch {
117+
// ---- fallback: welcome content mode ----
118+
try {
119+
const welcome = await section.findWelcomeContent();
120+
const buttons = await welcome.getButtons();
121+
if (buttons) {
122+
return section;
123+
}
124+
} catch {
125+
return false;
126+
}
127+
}
128+
129+
return false;
130+
131+
} catch {
132+
return false;
133+
}
134+
}, timeout, 'Components view did not stabilize');
135+
136+
return result as ViewSection;
137+
}
138+
139+
export async function findItemFuzzy(section: ViewSection, label: string): Promise<ViewItem | undefined> {
140+
const items = await section.getVisibleItems();
141+
for (const item of items) {
142+
try {
143+
const text = await item.getText();
144+
if (text.includes(label)) {
145+
return item;
146+
}
147+
} catch {
148+
// ignore
149+
}
150+
}
151+
152+
return undefined;
153+
}
154+
155+
export async function waitForItem(getSection: () => Promise<ViewSection>, label: string, strict: boolean = true, timeout = 15000) {
156+
return VSBrowser.instance.driver.wait(async () => {
157+
try {
158+
const section = await getSection();
159+
const item = strict ? await section.findItem(label) : await findItemFuzzy(section, label);
160+
return item ?? false;
161+
} catch {
162+
return false;
163+
}
164+
}, timeout, `Item "${label}" not found within ${timeout}ms`) as Promise<ViewItem>;
165+
}
166+
167+
export async function waitForItemStable(getSection: () => Promise<ViewSection>, label: string, strict: boolean = true, timeout = 15000) {
168+
await stabilizeComponentsView(getSection);
169+
return waitForItem(getSection, label, strict, timeout);
170+
}
171+
172+
173+
export async function waitForItemToDisappear(getSection: () => Promise<ViewSection>, label: string, timeout = 15000): Promise<boolean> {
174+
return VSBrowser.instance.driver.wait(async () => {
175+
try {
176+
const section = await getSection();
177+
const item = await section.findItem(label);
178+
return !item;
179+
} catch {
180+
return true;
181+
}
182+
}, timeout, `Item "${label}" still exists after ${timeout}ms`);
183+
}
184+
185+
export async function waitForItemToDisappearStable(getSection: () => Promise<ViewSection>, label: string, timeout = 15000): Promise<boolean> {
186+
await stabilizeComponentsView(getSection);
187+
return waitForItemToDisappear(getSection, label, timeout);
188+
}
189+
190+
export async function withStableItem(getSection: () => Promise<ViewSection>, label: string, action: (item: ViewItem) => Promise<void>, timeout = 10_000) {
191+
await stabilizeComponentsView(getSection);
192+
return VSBrowser.instance.driver.wait(async () => {
193+
try {
194+
const section = await getSection();
195+
const item = await section.findItem(label);
196+
if (!item) return false;
197+
await action(item);
198+
return true;
199+
} catch {
200+
return false;
201+
}
202+
}, timeout, `Action on "${label}" could not be performed`);
203+
}
204+
100205
export async function itemDoesNotExist(
101206
title: string,
102207
view: ViewSection,
@@ -191,3 +296,30 @@ export async function webViewIsOpened(name: string, driver: WebDriver, timeout =
191296
}
192297
}, timeout);
193298
}
299+
300+
export async function step<T>(name: string, fn: () => Promise<T>): Promise<T> {
301+
const callSiteStack = new Error(`Step: ${name}`).stack;
302+
try {
303+
return await fn();
304+
} catch (err) {
305+
const error = err as Error;
306+
error.message = `Step failed: ${name}\n${error.message}`;
307+
if (error.stack && callSiteStack) {
308+
error.stack =
309+
`${error.stack}\n\n--- Step call site ---\n${callSiteStack}`;
310+
}
311+
throw error;
312+
}
313+
}
314+
315+
export async function debugClick(element, name: string) {
316+
try {
317+
await element.click();
318+
} catch (e) {
319+
/* eslint-disable no-console */
320+
console.error(`Failed to click: ${name}`);
321+
console.error(await element.getAttribute('outerHTML'));
322+
/* eslint-enable no-console */
323+
throw e;
324+
}
325+
}

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

Lines changed: 79 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
* Copyright (c) Red Hat, Inc. All rights reserved.
33
* Licensed under the MIT License. See LICENSE file in the project root for license information.
44
*-----------------------------------------------------------------------------------------------*/
5-
import { By, Key, WebElement, WebView } from 'vscode-extension-tester';
5+
import { By, EditorView, Key, VSBrowser, WebElement, WebView } from 'vscode-extension-tester';
6+
import { debugClick } from '../../conditions';
67
import { WebViewForm } from './WebViewForm';
78

89
//TODO: Add support for create from git page and from local codebase page
@@ -31,24 +32,83 @@ export class CreateComponentWebView extends WebViewForm {
3132
}
3233

3334
public async createComponentFromTemplate(): Promise<void> {
34-
await this.enterWebView(async (webView) => {
35-
const button = await this.getCreateFromTemplateButton(webView);
36-
await button.click();
37-
});
35+
await VSBrowser.instance.driver.wait(async () => {
36+
try {
37+
return await this.enterWebView(async (webView) => {
38+
const button = await this.getCreateFromTemplateButton(webView);
39+
if (!button) return false;
40+
41+
const visible = await button.isDisplayed();
42+
const enabled = await button.isEnabled();
43+
44+
if (!visible || !enabled) return false;
45+
46+
await button.click();
47+
return true;
48+
});
49+
} catch {
50+
return false;
51+
}
52+
}, 10000, 'Create Component from Template button not clickable');
53+
54+
await VSBrowser.instance.driver.wait(async () => {
55+
const editors = await new EditorView().getOpenEditorTitles();
56+
return editors.some(title =>
57+
title.includes('Devfile Registry') || title.includes('Create Component')
58+
);
59+
}, 10000, 'Create Component from Template editor did not open');
3860
}
3961

4062
public async createComponentFromGit(): Promise<void> {
41-
await this.enterWebView(async (webView) => {
42-
const button = await this.getCreateFromGitButton(webView);
43-
await button.click();
44-
});
63+
await VSBrowser.instance.driver.wait(async () => {
64+
try {
65+
return await this.enterWebView(async (webView) => {
66+
const button = await this.getCreateFromGitButton(webView);
67+
if (!button) return false;
68+
69+
const visible = await button.isDisplayed();
70+
const enabled = await button.isEnabled();
71+
72+
if (!visible || !enabled) return false;
73+
74+
await button.click();
75+
return true;
76+
});
77+
} catch {
78+
return false;
79+
}
80+
}, 10000, 'Create Component from Git button not clickable');
81+
82+
await VSBrowser.instance.driver.wait(async () => {
83+
const editors = await new EditorView().getOpenEditorTitles();
84+
return editors.some(title => title.includes('Create Component'));
85+
}, 10000, 'Create Component from Git editor did not open');
4586
}
4687

4788
public async createComponentFromLocalCodebase(): Promise<void> {
48-
await this.enterWebView(async (webView) => {
49-
const button = await this.getCreateFromLocalButton(webView);
50-
await button.click();
51-
});
89+
await VSBrowser.instance.driver.wait(async () => {
90+
try {
91+
return await this.enterWebView(async (webView) => {
92+
const button = await this.getCreateFromLocalButton(webView);
93+
if (!button) return false;
94+
95+
const visible = await button.isDisplayed();
96+
const enabled = await button.isEnabled();
97+
98+
if (!visible || !enabled) return false;
99+
100+
await button.click();
101+
return true;
102+
});
103+
} catch {
104+
return false;
105+
}
106+
}, 10000, 'Create Component from Local Folder button not clickable');
107+
108+
await VSBrowser.instance.driver.wait(async () => {
109+
const editors = await new EditorView().getOpenEditorTitles();
110+
return editors.some(title => title.includes('Create Component'));
111+
}, 10000, 'Create Component from Local Folder editor did not open');
52112
}
53113
}
54114

@@ -160,7 +220,8 @@ export class GitProjectPage extends Page {
160220
public async clickContinueButton(): Promise<void> {
161221
await this.enterWebView(async (webView) => {
162222
const button = await this.continueButtonExists(webView);
163-
await button.click();
223+
// await button.click();
224+
await debugClick(button, 'Continue');
164225
});
165226
}
166227

@@ -202,14 +263,16 @@ export class LocalCodeBasePage extends Page {
202263
public async clickSelectFolderButton(): Promise<void> {
203264
await this.enterWebView(async (webView) => {
204265
const button = await this.getSelectFolderButton(webView);
205-
await button.click();
266+
// await button.click();
267+
await debugClick(button, 'Select Folder');
206268
});
207269
}
208270

209271
public async clickCreateComponent(): Promise<void> {
210272
await this.enterWebView(async (webView) => {
211273
const button = await this.createButtonExists(webView);
212-
await button.click();
274+
// await button.click();
275+
await debugClick(button, 'Create Component');
213276
});
214277
}
215278

0 commit comments

Comments
 (0)