Skip to content
Draft
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
88 changes: 88 additions & 0 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 @@ -560,6 +568,40 @@ export default class KubernetesClient {
}
}

async patchClusterCustomResource(
group: string,
version: string,
plural: string,
name: string,
patch: object[],
): Promise<void> {
await this.coApi.patchClusterCustomObject({
group,
name,
plural,
version,
body: patch,
contentType: 'application/json-patch+json',
} as any);
}

async getClusterCustomResource(
group: string,
version: string,
plural: string,
name: string,
): Promise<unknown | null> {
try {
const response = await this.coApi.getClusterCustomObject({ group, name, plural, version });
return response;
} catch (err) {
if (isNotFound(err)) {
return null;
}
throw err;
}
}

async getCustomResource(
group: string,
version: string,
Expand All @@ -577,6 +619,26 @@ export default class KubernetesClient {
return response;
}

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 @@ -596,6 +658,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
77 changes: 77 additions & 0 deletions frontend/e2e/pages/catalog-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,98 @@ import BasePage from './base-page';

export class CatalogPage extends BasePage {
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');
private readonly pageHeading = this.page.getByTestId('page-heading');

async navigateToCatalog(): Promise<void> {
await this.goTo('/catalog/all-namespaces');
await expect(this.filterInput).toBeVisible({ timeout: 60_000 });
}

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

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 filterCheckbox.click();
}

async clickCategoryFilter(categoryId: string): Promise<void> {
const categoryTab = this.page.locator(`[data-test="tab ${categoryId}"] > a`);
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');
}

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

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

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

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

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

catalogItemIcon(testId: string): Locator {
return this.catalogItem(testId).locator('img.catalog-tile-pf-icon');
}

/**
* Verify that catalog tiles contain expected title text
*/
async verifyTileContainsText(expectedText: string): Promise<void> {
await expect(this.getFirstCatalogTileTitle()).toHaveText(expectedText);
}

/**
* Verify that first tile title has changed from original text
*/
async verifyTileTextChanged(originalText: string): Promise<void> {
await expect(this.getFirstCatalogTileTitle()).not.toHaveText(originalText);
}
}
72 changes: 72 additions & 0 deletions frontend/e2e/pages/catalog-source-page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import type { Locator } from '@playwright/test';

import BasePage from './base-page';

export class CatalogSourcePage extends BasePage {
private readonly configurationTab = this.page.getByTestId('horizontal-link-Configuration');
private readonly sourcesTab = this.page.getByTestId('horizontal-link-Sources');
private readonly operatorsTab = this.page.getByTestId('horizontal-link-Operators');

private readonly packageManifestTable = this.page.getByTestId('PackageManifestTable');

private readonly registryPollIntervalDropdown = this.page.getByTestId(
'registry-poll-interval-dropdown',
);
private readonly registryPollIntervalModalTitle = this.page.getByTestId(
'registry-poll-interval-modal-title',
);

async navigateToOperatorHubSources(): Promise<void> {
await this.goTo('/settings/cluster');
await this.navigateToTab(this.configurationTab);
await this.waitForLoadingComplete();
const operatorHubLink = this.page.getByTestId('OperatorHub');
await operatorHubLink.scrollIntoViewIfNeeded();
await this.robustClick(operatorHubLink);
await this.navigateToTab(this.sourcesTab);
}

async openCatalogSourceDetails(name: string): Promise<void> {
await this.robustClick(this.page.getByTestId(name));
}

getSectionHeading(text: string): Locator {
return this.page.getByTestId(`section-heading-${text}`);
}

getDetailsLabel(label: string): Locator {
return this.page.getByTestId(`details-item-label__${label}`);
}

getDetailsValue(label: string): Locator {
return this.page.getByTestId(`details-item-value__${label}`);
}

getPackageManifestTable(): Locator {
return this.packageManifestTable;
}

getRegistryPollIntervalModalTitle(): Locator {
return this.registryPollIntervalModalTitle;
}

async selectOperatorsTab(): Promise<void> {
await this.navigateToTab(this.operatorsTab);
}

async clickEditRegistryPollInterval(): Promise<void> {
const editButton = this.page.getByTestId(
'Registry poll interval-details-item__edit-button',
);
await this.robustClick(editButton);
}

async selectPollInterval(interval: string): Promise<void> {
await this.robustClick(this.registryPollIntervalDropdown);
await this.robustClick(this.page.getByTestId(`dropdown-menu-${interval}`));
}

async submitPollIntervalModal(): Promise<void> {
await this.robustClick(this.page.getByTestId('confirm-action'));
}
}
13 changes: 13 additions & 0 deletions frontend/e2e/pages/details-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,17 @@ export class DetailsPage extends BasePage {
const kebabButton = resourceRow.getByTestId('kebab-button');
await this.robustClick(kebabButton);
}

getSectionHeader(text: string): Locator {
return this.page.getByTestId(`section-heading-${text}`);
}

getEmptyState(): Locator {
return this.page.getByTestId('console-empty-state');
}

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