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
41 changes: 37 additions & 4 deletions frontend/e2e/pages/base-page.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { type Locator, type Page, expect } from '@playwright/test';

import type KubernetesClient from '../clients/kubernetes-client';

export async function getEditorContent(page: Page): Promise<string> {
await page.waitForFunction(
() => {
Expand All @@ -23,8 +25,39 @@ export async function setEditorContent(page: Page, content: string): Promise<voi
}

export async function warmupSPA(page: Page): Promise<void> {
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 60_000 });
await expect(page.getByTestId('page-heading')).toBeVisible({ timeout: 30_000 });
await expect(async () => {
await page.goto('/', { 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 ensureDeveloperPerspective(
page: Page,
k8sClient: KubernetesClient,
): Promise<void> {
const toggle = page.getByTestId('perspective-switcher-toggle');
await expect(toggle).toBeVisible();
const isSinglePerspective =
(await toggle.getAttribute('id')) === 'only-one-perspective';
if (isSinglePerspective) {
await k8sClient.customObjectsApi.patchClusterCustomObject({
group: 'operator.openshift.io',
version: 'v1',
plural: 'consoles',
name: 'cluster',
body: [
{
op: 'add',
path: '/spec/customization/perspectives',
value: [{ id: 'dev', visibility: { state: 'Enabled' } }],
},
],
});
Comment on lines +43 to +55

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

ast-grep outline frontend/e2e/pages/base-page.ts --match ensureDeveloperPerspective --view expanded

rg -n -C 8 \
  'ensureDeveloperPerspective\s*\(|patchClusterCustomObject|customization/perspectives|afterAll|afterEach' \
  frontend/e2e

Repository: openshift/console

Length of output: 49633


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- base-page.ts ---'
cat -n frontend/e2e/pages/base-page.ts | sed -n '1,80p'

printf '%s\n' '--- all ensureDeveloperPerspective call sites ---'
rg -l 'ensureDeveloperPerspective\s*\(' frontend/e2e --glob '*.ts' | sort | while read -r file; do
  printf '\n## %s\n' "$file"
  rg -n -C 12 'ensureDeveloperPerspective\s*\(' "$file"
done

printf '%s\n' '--- Kubernetes client read and patch APIs ---'
rg -n -C 6 \
  'readClusterCustomObject|patchClusterCustomObject|customObjectsApi' \
  frontend/e2e/fixtures frontend/e2e/pages frontend/e2e/tests --glob '*.ts' | head -n 400

printf '%s\n' '--- fixture and Playwright configuration ---'
fd -i -t f 'fixture|playwright|config' frontend/e2e . | sort | head -n 120
rg -n -C 8 \
  'k8sClient|test\.extend|fullyParallel|workers|forbidOnly|afterAll|afterEach' \
  frontend/e2e --glob '*.{ts,js}' | head -n 500

Repository: openshift/console

Length of output: 50373


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

root = Path("frontend/e2e")
pattern = re.compile(r"ensureDeveloperPerspective\s*\(")
for path in sorted(root.rglob("*.ts")):
    text = path.read_text(errors="replace")
    matches = list(pattern.finditer(text))
    if not matches:
        continue
    print(f"{path}: {len(matches)} occurrence(s)")
    for m in matches:
        line = text.count("\n", 0, m.start()) + 1
        start = max(0, m.start() - 500)
        end = min(len(text), m.end() + 500)
        context = text[start:end].replace("\n", " ")
        print(f"  line {line}: {context}")
PY

Repository: openshift/console

Length of output: 4140


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- e2e files relevant to fixture scope and runner settings ---'
git ls-files frontend/e2e | grep -Ei '(^|/)(fixtures?/index|playwright.*config|.*config.*playwright|kubernetes-client)\.(ts|js|mjs|cjs)$' | sort

printf '%s\n' '--- fixture definition ---'
cat -n frontend/e2e/fixtures/index.ts | sed -n '1,120p'

printf '%s\n' '--- runner configuration candidates ---'
for file in $(git ls-files | grep -Ei '(^|/)(playwright.*config|.*config.*playwright)\.(ts|js|mjs|cjs)$' | head -n 20); do
  printf '\n## %s\n' "$file"
  cat -n "$file" | sed -n '1,180p'
done

printf '%s\n' '--- KubernetesClient API declarations and custom-object methods ---'
client_files=$(git ls-files frontend/e2e | grep -E '/kubernetes-client\.(ts|js)$' || true)
for file in $client_files; do
  printf '\n## %s\n' "$file"
  rg -n -C 5 \
    'class KubernetesClient|getClusterCustomObject|patchClusterCustomObject|customObjectsApi|constructor' \
    "$file"
done

Repository: openshift/console

Length of output: 10107


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

root = Path("frontend/e2e/tests")
call = re.compile(r"ensureDeveloperPerspective\s*\(")
for path in sorted(root.rglob("*.ts")):
    text = path.read_text(errors="replace")
    for match in call.finditer(text):
        line = text.count("\n", 0, match.start()) + 1
        before = text[:match.start()]
        hooks = [(m.start(), m.group(0), before.count("\n", 0, m.start()) + 1)
                 for m in re.finditer(r"test\.(beforeAll|beforeEach|afterAll|afterEach)\s*\(", before)]
        nearest = hooks[-1] if hooks else None
        print(f"{path}:{line}: nearest preceding hook={nearest}")
PY

Repository: openshift/console

Length of output: 467


Restore the original Console perspective configuration.

When ensureDeveloperPerspective changes Console/cluster, preserve spec.customization.perspectives and restore it in suite teardown, including failure paths. The helper runs from multiple test hooks and can leave the shared cluster resource modified for later suites.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/e2e/pages/base-page.ts` around lines 43 - 55, Update
ensureDeveloperPerspective in the Console/cluster setup flow to capture the
existing spec.customization.perspectives before patching it, then restore that
exact configuration during suite teardown. Ensure restoration is registered for
all invocation paths and still runs when setup or tests fail, preserving the
shared cluster resource for later suites.

await expect(async () => {
await page.reload();
await expect(toggle).not.toHaveAttribute('id', 'only-one-perspective');
}).toPass({ timeout: 60_000 });
}
}

export default abstract class BasePage {
Expand Down Expand Up @@ -166,14 +199,14 @@ export default abstract class BasePage {
Administrator: ['Administrator', 'Core platform'],
Developer: ['Developer'],
};
const toggle = this.page.locator('[data-test-id="perspective-switcher-toggle"]');
const toggle = this.page.getByTestId('perspective-switcher-toggle');
const labels = labelMap[target] || [target];
const currentText = (await toggle.textContent()) || '';
if (labels.some((label) => currentText.includes(label))) {
return;
}
await this.robustClick(toggle);
const menuOption = this.page.locator('[data-test-id="perspective-switcher-menu-option"]');
const menuOption = this.page.getByTestId('perspective-switcher-menu-option');
for (const label of labels) {
const option = menuOption.filter({ hasText: label });
if ((await option.count()) > 0) {
Expand Down
8 changes: 7 additions & 1 deletion frontend/e2e/pages/topology-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,13 @@ export class TopologyPage extends BasePage {
}

async fillApplicationName(appName: string): Promise<void> {
await expect(this.applicationNameField).toBeVisible();
// eslint-disable-next-line no-restricted-syntax
const hasDropdown = await this.applicationDropdown.waitFor({ state: 'visible', timeout: 2_000 }).then(() => true).catch(() => false);
if (hasDropdown) {
await this.applicationDropdown.click();
await this.page.getByRole('option', { name: 'Create application' }).click();
}
await expect(this.applicationNameField).toBeVisible({ timeout: 10_000 });
await this.applicationNameField.fill(appName);
await expect(this.applicationNameField).toHaveValue(appName);
}
Expand Down
6 changes: 6 additions & 0 deletions frontend/e2e/tests/console/app/poll-console-updates.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ test.describe('PollConsoleUpdates', { tag: ['@admin'] }, () => {
await navigateAndWaitForInit(page);

updates.setHandler((route) => route.fulfill({ json: UPDATES_NEW_COMMIT }));
await page.waitForResponse((resp) => resp.url().includes('/api/check-updates'), {
timeout: 30_000,
});

await expect(page.getByTestId('refresh-web-console')).toBeVisible({ timeout: 60_000 });
});
Expand All @@ -102,6 +105,9 @@ test.describe('PollConsoleUpdates', { tag: ['@admin'] }, () => {
await navigateAndWaitForInit(page);

updates.setHandler((route) => route.fulfill({ json: UPDATES_NEW_PLUGIN }));
await page.waitForResponse((resp) => resp.url().includes('/api/check-updates'), {
timeout: 30_000,
});

await expect(page.getByTestId('refresh-web-console')).not.toBeAttached({
timeout: 30_000,
Expand Down
Loading