Skip to content
Open
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
100 changes: 75 additions & 25 deletions frontend/e2e/clients/kubernetes-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,14 @@ export default class KubernetesClient {
}
}

getCurrentUser(): any {
try {
return this.kubeConfig.getCurrentUser();
} catch {
return { name: 'idk' };
}
}

async verifyAuthentication(): Promise<boolean> {
await this.k8sApi.listNamespace({ limit: 1 });
return true;
Expand Down Expand Up @@ -569,47 +577,43 @@ export default class KubernetesClient {
}
}

async getCustomResource(
async patchClusterCustomResource(
group: string,
version: string,
namespace: string,
plural: string,
name: string,
): Promise<unknown> {
const response = await this.coApi.getNamespacedCustomObject({
group,
name,
namespace,
plural,
version,
});
return response;
}
patch: object | object[],
): Promise<void> {
if (Array.isArray(patch)) {
await this.coApi.patchClusterCustomObject({
group,
name,
plural,
version,
body: patch,
contentType: k8s.PatchStrategy.JsonPatch,
} as any);
return;
}

async getClusterCustomResource(
group: string,
version: string,
plural: string,
name: string,
): Promise<unknown> {
return this.coApi.getClusterCustomObject({ group, name, plural, version });
await this.mergePatchResource(`/apis/${group}/${version}/${plural}/${name}`, patch);
}

async patchClusterCustomResource(
async getCustomResource(
group: string,
version: string,
namespace: string,
plural: string,
name: string,
patch: object,
): Promise<unknown> {
return this.coApi.patchClusterCustomObject({
body: patch,
const response = await this.coApi.getNamespacedCustomObject({
group,
name,
namespace,
plural,
version,
contentType: k8s.PatchStrategy.MergePatch,
} as any);
});
return response;
}

async createPVC(namespace: string, body: k8s.V1PersistentVolumeClaim): Promise<unknown> {
Expand Down Expand Up @@ -653,6 +657,26 @@ export default class KubernetesClient {
} as any);
}

async patchCustomResource(
group: string,
version: string,
namespace: string,
plural: string,
name: string,
patch: object[],
): Promise<unknown> {
const response = await this.coApi.patchNamespacedCustomObject({
body: patch,
group,
name,
namespace,
plural,
version,
contentType: k8s.PatchStrategy.JsonPatch,
} as any);
return response;
}

async listCustomResources(
group: string,
version: string,
Expand All @@ -672,6 +696,32 @@ export default class KubernetesClient {
}
}

async listClusterCustomResources(
group: string,
version: string,
plural: string,
): Promise<unknown[]> {
try {
const response = await this.coApi.listClusterCustomObject({
group,
plural,
version,
});
return (response as any)?.items || [];
} catch {
return [];
}
}

async listNamespaces(): Promise<unknown[]> {
try {
const response = await this.k8sApi.listNamespace();
return (response?.items || []);
} catch {
return [];
}
}

async getPods(namespace: string): Promise<k8s.V1Pod[]> {
const response = await this.k8sApi.listNamespacedPod({ namespace });
return response.items || [];
Expand Down
10 changes: 7 additions & 3 deletions frontend/e2e/pages/base-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,15 @@ export async function setEditorContent(page: Page, content: string): Promise<voi
}, content);
}

export async function warmupSPA(page: Page): Promise<void> {
export async function gotoAuthenticated(page: Page, url: string): Promise<void> {
await expect(async () => {
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 60_000 });
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60_000 });
await expect(page.locator('#page-sidebar')).toBeVisible({ timeout: 30_000 });
}).toPass({ intervals: [1_000, 2_000, 5_000], timeout: 90_000 });
}

export async function warmupSPA(page: Page): Promise<void> {
await gotoAuthenticated(page, '/');
await dismissQuickStartDrawer(page);
}

Expand Down Expand Up @@ -112,7 +116,7 @@ export default abstract class BasePage {
}

protected async goTo(url: string): Promise<void> {
await this.page.goto(url, { timeout: 90_000 });
await gotoAuthenticated(this.page, url);
await this.waitForLoadingComplete();
}

Expand Down
120 changes: 111 additions & 9 deletions frontend/e2e/pages/catalog-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import BasePage from './base-page';
export class CatalogPage extends BasePage {
private readonly pageHeading: Locator = this.page.getByTestId('page-heading');
private readonly filterInput: Locator = this.page.getByPlaceholder('Filter by keyword');
private readonly searchCatalogInput = this.page.getByTestId('search-catalog').locator('input');
private readonly operatorTab = this.page.getByTestId('tab operator');
private readonly clearFiltersButton = this.page.getByTestId('catalog-clear-filters');

async navigateToCatalog(namespace?: string): Promise<void> {
const url = namespace ? `/catalog/ns/${namespace}` : '/catalog/all-namespaces';
Expand All @@ -23,10 +26,110 @@ export class CatalogPage extends BasePage {
await expect(this.pageHeading).toBeVisible({ timeout: 60_000 });
}

async navigateToSoftwareCatalog(namespace: string): Promise<void> {
await this.goTo(`/catalog/ns/${namespace}`);
await expect(this.pageHeading).toBeVisible({ timeout: 60_000 });
}

async navigateToOperatorCatalog(namespace: string): Promise<void> {
await this.goTo(`/catalog/ns/${namespace}?catalogType=operator`);
await expect(this.pageHeading).toBeVisible({ timeout: 60_000 });
}

async navigateToPath(url: string): Promise<void> {
await this.goTo(url);
}

async filterByKeyword(keyword: string): Promise<void> {
await this.filterInput.fill(keyword);
}

async searchOperators(operatorName: string): Promise<void> {
await this.searchCatalogInput.fill(operatorName);
}

async clearSearchFilter(): Promise<void> {
await this.searchCatalogInput.fill('');
}

async clickOperatorTab(): Promise<void> {
await this.robustClick(this.operatorTab);
}

async clickClearAllFilters(): Promise<void> {
await this.robustClick(this.clearFiltersButton);
}

async toggleSourceFilter(filterType: string): Promise<void> {
const filterCheckbox = this.page.getByTestId(`source-${filterType}`);
await this.robustClick(filterCheckbox);
}

async toggleSourceFilterByLabel(label: string): Promise<void> {
await this.robustClick(this.page.getByRole('checkbox', { name: label }), {
timeout: 60_000,
});
}

getOperatorCard(operatorName: string): Locator {
return this.page
.getByTestId(`operator-${operatorName}`)
.filter({ hasNotText: 'testing deprecation' });
}

async clickOperatorCard(operatorName: string): Promise<void> {
await this.robustClick(this.getOperatorCard(operatorName), { timeout: 60_000 });
}

getDeprecatedWarningBadge(): Locator {
return this.page.getByTestId('deprecated-operator-warning-badge');
}

// Software Catalog tiles and the catalog details drawer render deprecation via the generic
// CatalogBadges component, which derives data-test from the badge text (`${text}-badge`) —
// not the OLM-specific `deprecated-operator-warning-badge` used on CSV/installed-operator pages.
// Scoped to the details dialog since the background tile grid renders the same badge testid.
getCatalogDeprecatedBadge(): Locator {
return this.page.getByRole('dialog').getByTestId('Deprecated-badge');
}

getDeprecatedWarning(testId: string): Locator {
return this.page.getByTestId(testId);
}

async clickCategoryFilter(categoryId: string): Promise<void> {
const categoryTab = this.page.getByTestId(`tab ${categoryId}`);
await this.robustClick(categoryTab);
}

getCatalogTiles(): Locator {
return this.page.locator('.co-catalog-tile');
}

getFirstCatalogTile(): Locator {
return this.getCatalogTiles().first();
}

getFirstCatalogTileTitle(): Locator {
return this.getFirstCatalogTile().locator('.catalog-tile-pf-title');
}

async getFirstCatalogTileTitleText(): Promise<string> {
return this.getFirstCatalogTileTitle().innerText();
}

getClearFiltersButton(): Locator {
return this.clearFiltersButton;
}

getPageHeading(): Locator {
return this.pageHeading;
}

getSearchInput(): Locator {
return this.searchCatalogInput;
}

catalogItem(testId: string): Locator {
return this.page.getByTestId(testId);
}
Expand Down Expand Up @@ -83,10 +186,6 @@ export class CatalogPage extends BasePage {
await this.robustClick(this.page.getByRole('link', { name: /create application/i }));
}

getPageHeading(): Locator {
return this.pageHeading;
}

getFilterInput(): Locator {
return this.filterInput;
}
Expand All @@ -95,16 +194,19 @@ export class CatalogPage extends BasePage {
return this.page.getByText(text);
}

getCatalogTiles(): Locator {
// co-catalog-tile: Console's catalog tile class from CatalogTile.tsx
return this.page.locator('.co-catalog-tile');
}

getFormSubmitButton(): Locator {
return this.page.getByRole('button', { name: 'Create', exact: true });
}

getProjectSelectionMessage(): Locator {
return this.page.getByText('Select a Project to view the software catalog');
}

async verifyTileContainsText(expectedText: string): Promise<void> {
await expect(this.getFirstCatalogTileTitle()).toContainText(expectedText);
}

async verifyTileTextChanged(originalText: string): Promise<void> {
await expect(this.getFirstCatalogTileTitle()).not.toHaveText(originalText);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
Loading