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
6 changes: 5 additions & 1 deletion .github/workflows/continuous-integration-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ jobs:
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v6
with:
fetch-depth: 1

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

- name: Configure cluster
if: (success() || failure()) && runner.os == 'Linux'
Expand Down Expand Up @@ -105,6 +108,7 @@ jobs:
with:
name: artifacts-${{ matrix.os }}
path: |
/tmp/test-resources/settings/logs/**/*.log
/tmp/test-resources/screenshots/**/*.png
retention-days: 2

Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 34 additions & 0 deletions src/explorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,26 @@ export class OpenShiftExplorer implements TreeDataProvider<ExplorerItem>, Dispos
.then(result => (allTrue(result) ? `${contextValue}.can-delete` : contextValue));
}


// eslint-disable-next-line class-methods-use-this
async getTreeItem(element: ExplorerItem): Promise<TreeItem> {
try {
return await this._getTreeItem(element);
} catch (err) {
// eslint-disable-next-line no-console
console.error('getTreeItem failed:', err, element);

return {
label: '⚠️ Broken item',
tooltip: `${err}`,
iconPath: new ThemeIcon('error'),
collapsibleState: TreeItemCollapsibleState.None
};
}
}

// eslint-disable-next-line class-methods-use-this
async _getTreeItem(element: ExplorerItem): Promise<TreeItem> {
if ('command' in element || ('label' in element && 'iconPath' in element)) {
return element;
}
Expand Down Expand Up @@ -474,7 +492,23 @@ export class OpenShiftExplorer implements TreeDataProvider<ExplorerItem>, Dispos
}

// eslint-disable-next-line class-methods-use-this

async getChildren(element?: ExplorerItem): Promise<ExplorerItem[]> {
try {
return await this._getChildren(element);
} catch (err) {
// eslint-disable-next-line no-console
console.error('getChildren failed:', err);

return [{
label: '⚠️ Error loading tree',
tooltip: String(err),
iconPath: new ThemeIcon('error')
}];
}
}

async _getChildren(element?: ExplorerItem): Promise<ExplorerItem[]> {
let result: ExplorerItem[] = [];
// don't show Open In Developer Dashboard if not openshift cluster
const isOpenshiftCluster = await isOpenShiftCluster(this.executionContext);
Expand Down
204 changes: 203 additions & 1 deletion test/ui/common/conditions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ import {
NotificationType,
ViewItem,
ViewSection,
VSBrowser,
WebDriver,
WelcomeContentSection,
Workbench,
Workbench
} from 'vscode-extension-tester';

export async function waitForInputUpdate(
Expand Down Expand Up @@ -97,6 +98,110 @@ export async function itemExists(
);
}

export async function stabilizeComponentsView(getSection: () => Promise<ViewSection>, timeout = 20000): Promise<ViewSection> {
const result = await VSBrowser.instance.driver.wait(async () => {
try {
let section = await getSection();

await section.expand();
await new Promise(res => setTimeout(res, 300));

section = await getSection();

// ---- try tree mode ----
try {
const items = await section.getVisibleItems();
await Promise.all(items.map(i => i.getText()));
return section;
} catch {
// ---- fallback: welcome content mode ----
try {
const welcome = await section.findWelcomeContent();
const buttons = await welcome.getButtons();
if (buttons) {
return section;
}
} catch {
return false;
}
}

return false;

} catch {
return false;
}
}, timeout, 'Components view did not stabilize');

return result as ViewSection;
}

export async function findItemFuzzy(section: ViewSection, label: string): Promise<ViewItem | undefined> {
const items = await section.getVisibleItems();
for (const item of items) {
try {
const text = await item.getText();
if (text.includes(label)) {
return item;
}
} catch {
// ignore
}
}

return undefined;
}

export async function waitForItem(getSection: () => Promise<ViewSection>, label: string, strict: boolean = true, timeout = 15000) {
return VSBrowser.instance.driver.wait(async () => {
try {
const section = await getSection();
const item = strict ? await section.findItem(label) : await findItemFuzzy(section, label);
return item ?? false;
} catch {
return false;
}
}, timeout, `Item "${label}" not found within ${timeout}ms`) as Promise<ViewItem>;
}

export async function waitForItemStable(getSection: () => Promise<ViewSection>, label: string, strict: boolean = true, timeout = 15000) {
await stabilizeComponentsView(getSection);
return waitForItem(getSection, label, strict, timeout);
}


export async function waitForItemToDisappear(getSection: () => Promise<ViewSection>, label: string, timeout = 15000): Promise<boolean> {
return VSBrowser.instance.driver.wait(async () => {
try {
const section = await getSection();
const item = await section.findItem(label);
return !item;
} catch {
return true;
}
}, timeout, `Item "${label}" still exists after ${timeout}ms`);
}

export async function waitForItemToDisappearStable(getSection: () => Promise<ViewSection>, label: string, timeout = 15000): Promise<boolean> {
await stabilizeComponentsView(getSection);
return waitForItemToDisappear(getSection, label, timeout);
}

export async function withStableItem(getSection: () => Promise<ViewSection>, label: string, action: (item: ViewItem) => Promise<void>, timeout = 10_000) {
await stabilizeComponentsView(getSection);
return VSBrowser.instance.driver.wait(async () => {
try {
const section = await getSection();
const item = await section.findItem(label);
if (!item) return false;
await action(item);
return true;
} catch {
return false;
}
}, timeout, `Action on "${label}" could not be performed`);
}

export async function itemDoesNotExist(
title: string,
view: ViewSection,
Expand Down Expand Up @@ -191,3 +296,100 @@ export async function webViewIsOpened(name: string, driver: WebDriver, timeout =
}
}, timeout);
}

export async function step<T>(name: string, fn: () => Promise<T>): Promise<T> {
const callSiteStack = new Error(`Step: ${name}`).stack;
try {
return await fn();
} catch (err) {
const error = err as Error;
error.message = `Step failed: ${name}\n${error.message}`;
if (error.stack && callSiteStack) {
error.stack =
`${error.stack}\n\n--- Step call site ---\n${callSiteStack}`;
}
throw error;
}
}

export async function debugClick(element, name: string) {
try {
await element.click();
} catch (e) {
/* eslint-disable no-console */
error(`Failed to click: ${name}`);
error(await element.getAttribute('outerHTML'));
/* eslint-enable no-console */
throw e;
}
}

/* eslint-disable no-console */
export async function listItems(getSection: () => Promise<ViewSection>, title = undefined): Promise<void> {
const t = title ? `[${title}]: ` : '';
log(`${t}Current components in view: >>>`);

try {
await VSBrowser.instance.driver.wait(async () => {
try {
const section = await getSection();

// Expand the section
await section.expand();

// First try: list actual components
try {
const items = await VSBrowser.instance.driver.wait(async () => {
try {
return await section.getVisibleItems();
} catch {
return null;
}
}, 10000);

if (items && items.length) {
const labels = [];
for (const item of items) {
try {
labels.push(await item.getText());
} catch {
labels.push('<error>');
}
}
log(`[${labels.join(', ')}]`);
return true;
}
} catch {
// fallback to welcome content
}

// Second try: welcome content (empty section)
try {
const welcome = await section.findWelcomeContent();
const buttons = await welcome.getButtons();
if (buttons && buttons.length) {
log('[<empty: welcome content>]');
return true;
}
} catch {
// still nothing visible, retry
}

return false;

} catch {
return false;
}
}, 10000, 'Could not list items');

} catch (err) {
log('Error while getting the current components: ', err);
} finally {
log('<<<');
}
}

export const log = console.log;
export const warn = console.warn;
export const error = console.error;
/* eslint-enable no-console */
Loading
Loading