From 354093b900b1a270b77f3c31452edad7ba9f1d29 Mon Sep 17 00:00:00 2001 From: Gandalf the Greybeard Date: Thu, 23 Apr 2026 10:58:22 +0000 Subject: [PATCH 1/8] fix: override lodash >=4.18.0 to patch code injection vulnerability GHSA-r5fr-rjxr-66jc is a code injection vulnerability in lodash below 4.18.0. The vulnerable transitive dependency comes through @kinvolk/headlamp-plugin. Co-Authored-By: Claude Opus 4.7 --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 797aba3..2817334 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,8 @@ "overrides": { "tar": "^7.5.11", "undici": "^7.24.3", - "flatted": "^3.4.2" + "flatted": "^3.4.2", + "lodash": ">=4.18.0" } }, "devDependencies": { -- 2.52.0 From e5737f8b7f5921036f7b12ad8d204f1c3d1aedd3 Mon Sep 17 00:00:00 2001 From: Gandalf the Greybeard Date: Sun, 26 Apr 2026 21:33:05 +0000 Subject: [PATCH 2/8] fix: update pnpm-lock.yaml to satisfy lodash override The package.json pnpm.overrides requires lodash >=4.18.0, but the lockfile had an older version. Regenerated lockfile with pnpm install. Co-Authored-By: Paperclip --- pnpm-lock.yaml | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 62256ab..4f60e04 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,7 @@ overrides: tar: ^7.5.11 undici: ^7.24.3 flatted: ^3.4.2 + lodash: '>=4.18.0' importers: @@ -3553,8 +3554,8 @@ packages: lodash.truncate@4.4.2: resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} - lodash@4.17.23: - resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} @@ -5898,11 +5899,11 @@ snapshots: '@iconify/react': 3.2.2(react@18.3.1) '@monaco-editor/react': 4.7.0(monaco-editor@0.52.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mui/icons-material': 5.18.0(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@19.2.14)(react@18.3.1) - '@mui/lab': 5.0.0-alpha.177(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mui/lab': 5.0.0-alpha.177(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mui/material': 5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mui/system': 5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1) '@mui/x-date-pickers': 7.29.4(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@mui/x-tree-view': 6.17.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mui/x-tree-view': 6.17.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@reduxjs/toolkit': 2.11.2(react-redux@9.2.0(@types/react@18.3.28)(react@18.3.1)(redux@5.0.1))(react@18.3.1) '@storybook/addon-docs': 9.1.20(@types/react@18.3.28)(storybook@9.1.20(@testing-library/dom@10.4.1)(msw@2.4.9(typescript@5.6.2))(prettier@2.8.8)(vite@6.4.1(@types/node@20.19.37)(terser@5.46.0)(yaml@2.8.2))) '@storybook/addon-links': 9.1.20(react@18.3.1)(storybook@9.1.20(@testing-library/dom@10.4.1)(msw@2.4.9(typescript@5.6.2))(prettier@2.8.8)(vite@6.4.1(@types/node@20.19.37)(terser@5.46.0)(yaml@2.8.2))) @@ -5955,8 +5956,8 @@ snapshots: js-yaml: 4.1.1 jsdom: 24.1.3 jsonpath-plus: 10.4.0 - lodash: 4.17.23 - material-react-table: 2.13.3(6e12a7d949eb369c0813bc8d1756414b) + lodash: 4.18.1 + material-react-table: 2.13.3(330725fe5432f245d076f0c0dda1a7a7) monaco-editor: 0.52.2 msw: 2.4.9(typescript@5.6.2) msw-storybook-addon: 2.0.3(msw@2.4.9(typescript@5.6.2)) @@ -6102,7 +6103,7 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 - '@mui/lab@5.0.0-alpha.177(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@mui/lab@5.0.0-alpha.177(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.28.6 '@mui/base': 5.0.0-beta.40-1(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -6312,7 +6313,7 @@ snapshots: transitivePeerDependencies: - '@types/react' - '@mui/x-tree-view@6.17.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@mui/x-tree-view@6.17.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.28.6 '@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1) @@ -9052,7 +9053,7 @@ snapshots: dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 - lodash: 4.17.23 + lodash: 4.18.1 pretty-error: 4.0.0 tapable: 2.3.0 optionalDependencies: @@ -9507,7 +9508,7 @@ snapshots: lodash.truncate@4.4.2: {} - lodash@4.17.23: {} + lodash@4.18.1: {} longest-streak@3.1.0: {} @@ -9552,7 +9553,7 @@ snapshots: '@types/minimatch': 3.0.5 minimatch: 3.1.5 - material-react-table@2.13.3(6e12a7d949eb369c0813bc8d1756414b): + material-react-table@2.13.3(330725fe5432f245d076f0c0dda1a7a7): dependencies: '@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1) '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1) @@ -10236,7 +10237,7 @@ snapshots: pretty-error@4.0.0: dependencies: - lodash: 4.17.23 + lodash: 4.18.1 renderkid: 3.0.0 pretty-format@27.5.1: @@ -10497,7 +10498,7 @@ snapshots: dependencies: clsx: 2.1.1 eventemitter3: 4.0.7 - lodash: 4.17.23 + lodash: 4.18.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) react-is: 18.3.1 @@ -10573,7 +10574,7 @@ snapshots: css-select: 4.3.0 dom-converter: 0.2.0 htmlparser2: 6.1.0 - lodash: 4.17.23 + lodash: 4.18.1 strip-ansi: 6.0.1 replace-ext@2.0.0: {} -- 2.52.0 From 8f4abcf97537aa885f1928dbf97cfab448d7f14f Mon Sep 17 00:00:00 2001 From: Gandalf the Greybeard Date: Sun, 26 Apr 2026 23:58:24 +0000 Subject: [PATCH 3/8] fix(e2e): scope heading locators to main content area Fix E2E test failures by scoping heading locators to the main content area instead of searching the entire page. This prevents matching headings in the sidebar or other non-content areas. Co-Authored-By: Paperclip --- e2e/polaris.spec.ts | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/e2e/polaris.spec.ts b/e2e/polaris.spec.ts index 1d6df99..037e015 100644 --- a/e2e/polaris.spec.ts +++ b/e2e/polaris.spec.ts @@ -13,7 +13,9 @@ test.describe('Polaris plugin smoke tests', () => { await page.goto('/c/main/polaris'); // SectionHeader renders a heading - await expect(page.getByRole('heading', { name: 'Polaris \u2014 Overview' })).toBeVisible(); + await expect( + page.locator('main').getByRole('heading', { name: 'Polaris \u2014 Overview' }) + ).toBeVisible(); // "Cluster Score" section exists with a percentage await expect(page.getByText('Cluster Score')).toBeVisible(); @@ -23,7 +25,9 @@ test.describe('Polaris plugin smoke tests', () => { test('namespaces page renders table with namespace buttons', async ({ page }) => { await page.goto('/c/main/polaris/namespaces'); - await expect(page.getByRole('heading', { name: 'Polaris \u2014 Namespaces' })).toBeVisible(); + await expect( + page.locator('main').getByRole('heading', { name: 'Polaris \u2014 Namespaces' }) + ).toBeVisible(); // Table should have at least one row with a namespace button const table = page.locator('table'); @@ -48,14 +52,14 @@ test.describe('Polaris plugin smoke tests', () => { // Drawer should open and show the namespace name in the heading await expect( - page.getByRole('heading', { name: `Polaris \u2014 ${namespaceName}` }) + page.locator('main').getByRole('heading', { name: `Polaris \u2014 ${namespaceName}` }) ).toBeVisible(); // "Namespace Score" section should be present in drawer - await expect(page.getByText('Namespace Score')).toBeVisible(); + await expect(page.locator('main').getByText('Namespace Score')).toBeVisible(); // Resources table should exist in drawer - await expect(page.getByRole('heading', { name: 'Resources' })).toBeVisible(); + await expect(page.locator('main').getByRole('heading', { name: 'Resources' })).toBeVisible(); // URL hash should be updated with namespace name await expect(page).toHaveURL(/\/polaris\/namespaces#/); @@ -73,7 +77,7 @@ test.describe('Polaris plugin smoke tests', () => { // Verify drawer is open await expect( - page.getByRole('heading', { name: `Polaris \u2014 ${namespaceName}` }) + page.locator('main').getByRole('heading', { name: `Polaris \u2014 ${namespaceName}` }) ).toBeVisible(); // Press Escape key @@ -81,7 +85,7 @@ test.describe('Polaris plugin smoke tests', () => { // Drawer should close (heading should not be visible anymore) await expect( - page.getByRole('heading', { name: `Polaris \u2014 ${namespaceName}` }) + page.locator('main').getByRole('heading', { name: `Polaris \u2014 ${namespaceName}` }) ).not.toBeVisible(); // URL hash should be cleared @@ -101,10 +105,10 @@ test.describe('Polaris plugin smoke tests', () => { // Drawer should automatically open with the namespace details await expect( - page.getByRole('heading', { name: `Polaris \u2014 ${namespaceName}` }) + page.locator('main').getByRole('heading', { name: `Polaris \u2014 ${namespaceName}` }) ).toBeVisible(); // "Namespace Score" section should be present - await expect(page.getByText('Namespace Score')).toBeVisible(); + await expect(page.locator('main').getByText('Namespace Score')).toBeVisible(); }); }); -- 2.52.0 From 82f79357dca053f62c0c182387408308fd31cfa6 Mon Sep 17 00:00:00 2001 From: Gandalf the Greybeard Date: Mon, 27 Apr 2026 00:10:59 +0000 Subject: [PATCH 4/8] fix(e2e): scope remaining getByText to main element The 'Cluster Score' text matcher was still searching the entire page instead of being scoped to the main content area. This could cause false positives if the same text appears in the sidebar. Co-Authored-By: Paperclip --- e2e/polaris.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/polaris.spec.ts b/e2e/polaris.spec.ts index 037e015..cbd3244 100644 --- a/e2e/polaris.spec.ts +++ b/e2e/polaris.spec.ts @@ -18,7 +18,7 @@ test.describe('Polaris plugin smoke tests', () => { ).toBeVisible(); // "Cluster Score" section exists with a percentage - await expect(page.getByText('Cluster Score')).toBeVisible(); + await expect(page.locator('main').getByText('Cluster Score')).toBeVisible(); await expect(page.locator('main').getByText(/%/).first()).toBeVisible(); }); -- 2.52.0 From a9793e3c2d173d2cb87850a8e50c96a23a0db16b Mon Sep 17 00:00:00 2001 From: Gandalf the Greybeard Date: Mon, 27 Apr 2026 00:29:34 +0000 Subject: [PATCH 5/8] ci: trigger fresh E2E run Re-pushing to trigger a new CI run since the last E2E was cancelled. Co-Authored-By: Paperclip -- 2.52.0 From 10b6f8e7e0973acce0f89a7feebf8947d11d72ff Mon Sep 17 00:00:00 2001 From: Gandalf the Greybeard Date: Mon, 27 Apr 2026 00:36:29 +0000 Subject: [PATCH 6/8] fix(e2e): use [role=main] instead of main element Switch from 'main' element selector to '[role="main"]' attribute selector for better compatibility with Headlamp's app structure. Co-Authored-By: Paperclip --- e2e/polaris.spec.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/e2e/polaris.spec.ts b/e2e/polaris.spec.ts index cbd3244..608705c 100644 --- a/e2e/polaris.spec.ts +++ b/e2e/polaris.spec.ts @@ -14,19 +14,19 @@ test.describe('Polaris plugin smoke tests', () => { // SectionHeader renders a heading await expect( - page.locator('main').getByRole('heading', { name: 'Polaris \u2014 Overview' }) + page.locator('[role="main"]').getByRole('heading', { name: 'Polaris \u2014 Overview' }) ).toBeVisible(); // "Cluster Score" section exists with a percentage - await expect(page.locator('main').getByText('Cluster Score')).toBeVisible(); - await expect(page.locator('main').getByText(/%/).first()).toBeVisible(); + await expect(page.locator('[role="main"]').getByText('Cluster Score')).toBeVisible(); + await expect(page.locator('[role="main"]').getByText(/%/).first()).toBeVisible(); }); test('namespaces page renders table with namespace buttons', async ({ page }) => { await page.goto('/c/main/polaris/namespaces'); await expect( - page.locator('main').getByRole('heading', { name: 'Polaris \u2014 Namespaces' }) + page.locator('[role="main"]').getByRole('heading', { name: 'Polaris \u2014 Namespaces' }) ).toBeVisible(); // Table should have at least one row with a namespace button @@ -52,14 +52,14 @@ test.describe('Polaris plugin smoke tests', () => { // Drawer should open and show the namespace name in the heading await expect( - page.locator('main').getByRole('heading', { name: `Polaris \u2014 ${namespaceName}` }) + page.locator('[role="main"]').getByRole('heading', { name: `Polaris \u2014 ${namespaceName}` }) ).toBeVisible(); // "Namespace Score" section should be present in drawer - await expect(page.locator('main').getByText('Namespace Score')).toBeVisible(); + await expect(page.locator('[role="main"]').getByText('Namespace Score')).toBeVisible(); // Resources table should exist in drawer - await expect(page.locator('main').getByRole('heading', { name: 'Resources' })).toBeVisible(); + await expect(page.locator('[role="main"]').getByRole('heading', { name: 'Resources' })).toBeVisible(); // URL hash should be updated with namespace name await expect(page).toHaveURL(/\/polaris\/namespaces#/); @@ -77,7 +77,7 @@ test.describe('Polaris plugin smoke tests', () => { // Verify drawer is open await expect( - page.locator('main').getByRole('heading', { name: `Polaris \u2014 ${namespaceName}` }) + page.locator('[role="main"]').getByRole('heading', { name: `Polaris \u2014 ${namespaceName}` }) ).toBeVisible(); // Press Escape key @@ -85,7 +85,7 @@ test.describe('Polaris plugin smoke tests', () => { // Drawer should close (heading should not be visible anymore) await expect( - page.locator('main').getByRole('heading', { name: `Polaris \u2014 ${namespaceName}` }) + page.locator('[role="main"]').getByRole('heading', { name: `Polaris \u2014 ${namespaceName}` }) ).not.toBeVisible(); // URL hash should be cleared @@ -105,10 +105,10 @@ test.describe('Polaris plugin smoke tests', () => { // Drawer should automatically open with the namespace details await expect( - page.locator('main').getByRole('heading', { name: `Polaris \u2014 ${namespaceName}` }) + page.locator('[role="main"]').getByRole('heading', { name: `Polaris \u2014 ${namespaceName}` }) ).toBeVisible(); // "Namespace Score" section should be present - await expect(page.locator('main').getByText('Namespace Score')).toBeVisible(); + await expect(page.locator('[role="main"]').getByText('Namespace Score')).toBeVisible(); }); }); -- 2.52.0 From 690723317ecb52617de00a8690e4349472a9743d Mon Sep 17 00:00:00 2001 From: Gandalf the Greybeard Date: Mon, 27 Apr 2026 00:46:30 +0000 Subject: [PATCH 7/8] fix(e2e): hybrid approach - unscoped headings, main-scoped text Use broader heading selectors matching intel-gpu pattern, but keep text checks scoped to main element to avoid sidebar conflicts. Co-Authored-By: Paperclip --- e2e/polaris.spec.ts | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/e2e/polaris.spec.ts b/e2e/polaris.spec.ts index 608705c..8c88b16 100644 --- a/e2e/polaris.spec.ts +++ b/e2e/polaris.spec.ts @@ -13,21 +13,17 @@ test.describe('Polaris plugin smoke tests', () => { await page.goto('/c/main/polaris'); // SectionHeader renders a heading - await expect( - page.locator('[role="main"]').getByRole('heading', { name: 'Polaris \u2014 Overview' }) - ).toBeVisible(); + await expect(page.getByRole('heading', { name: /Polaris \u2014 Overview/i })).toBeVisible(); // "Cluster Score" section exists with a percentage - await expect(page.locator('[role="main"]').getByText('Cluster Score')).toBeVisible(); - await expect(page.locator('[role="main"]').getByText(/%/).first()).toBeVisible(); + await expect(page.locator('main').getByText('Cluster Score')).toBeVisible(); + await expect(page.locator('main').getByText(/%/).first()).toBeVisible(); }); test('namespaces page renders table with namespace buttons', async ({ page }) => { await page.goto('/c/main/polaris/namespaces'); - await expect( - page.locator('[role="main"]').getByRole('heading', { name: 'Polaris \u2014 Namespaces' }) - ).toBeVisible(); + await expect(page.getByRole('heading', { name: /Polaris \u2014 Namespaces/i })).toBeVisible(); // Table should have at least one row with a namespace button const table = page.locator('table'); @@ -52,14 +48,14 @@ test.describe('Polaris plugin smoke tests', () => { // Drawer should open and show the namespace name in the heading await expect( - page.locator('[role="main"]').getByRole('heading', { name: `Polaris \u2014 ${namespaceName}` }) + page.getByRole('heading', { name: `Polaris \u2014 ${namespaceName}` }) ).toBeVisible(); // "Namespace Score" section should be present in drawer - await expect(page.locator('[role="main"]').getByText('Namespace Score')).toBeVisible(); + await expect(page.locator('main').getByText('Namespace Score')).toBeVisible(); // Resources table should exist in drawer - await expect(page.locator('[role="main"]').getByRole('heading', { name: 'Resources' })).toBeVisible(); + await expect(page.getByRole('heading', { name: 'Resources' })).toBeVisible(); // URL hash should be updated with namespace name await expect(page).toHaveURL(/\/polaris\/namespaces#/); @@ -77,7 +73,7 @@ test.describe('Polaris plugin smoke tests', () => { // Verify drawer is open await expect( - page.locator('[role="main"]').getByRole('heading', { name: `Polaris \u2014 ${namespaceName}` }) + page.getByRole('heading', { name: `Polaris \u2014 ${namespaceName}` }) ).toBeVisible(); // Press Escape key @@ -85,7 +81,7 @@ test.describe('Polaris plugin smoke tests', () => { // Drawer should close (heading should not be visible anymore) await expect( - page.locator('[role="main"]').getByRole('heading', { name: `Polaris \u2014 ${namespaceName}` }) + page.getByRole('heading', { name: `Polaris \u2014 ${namespaceName}` }) ).not.toBeVisible(); // URL hash should be cleared @@ -105,10 +101,10 @@ test.describe('Polaris plugin smoke tests', () => { // Drawer should automatically open with the namespace details await expect( - page.locator('[role="main"]').getByRole('heading', { name: `Polaris \u2014 ${namespaceName}` }) + page.getByRole('heading', { name: `Polaris \u2014 ${namespaceName}` }) ).toBeVisible(); // "Namespace Score" section should be present - await expect(page.locator('[role="main"]').getByText('Namespace Score')).toBeVisible(); + await expect(page.locator('main').getByText('Namespace Score')).toBeVisible(); }); }); -- 2.52.0 From 270c918833f5406f20b28cc69287ee08b75b2000 Mon Sep 17 00:00:00 2001 From: Gandalf the Greybeard Date: Mon, 27 Apr 2026 00:52:07 +0000 Subject: [PATCH 8/8] ci: re-test original code to verify baseline --- e2e/polaris.spec.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/e2e/polaris.spec.ts b/e2e/polaris.spec.ts index 8c88b16..1d6df99 100644 --- a/e2e/polaris.spec.ts +++ b/e2e/polaris.spec.ts @@ -13,17 +13,17 @@ test.describe('Polaris plugin smoke tests', () => { await page.goto('/c/main/polaris'); // SectionHeader renders a heading - await expect(page.getByRole('heading', { name: /Polaris \u2014 Overview/i })).toBeVisible(); + await expect(page.getByRole('heading', { name: 'Polaris \u2014 Overview' })).toBeVisible(); // "Cluster Score" section exists with a percentage - await expect(page.locator('main').getByText('Cluster Score')).toBeVisible(); + await expect(page.getByText('Cluster Score')).toBeVisible(); await expect(page.locator('main').getByText(/%/).first()).toBeVisible(); }); test('namespaces page renders table with namespace buttons', async ({ page }) => { await page.goto('/c/main/polaris/namespaces'); - await expect(page.getByRole('heading', { name: /Polaris \u2014 Namespaces/i })).toBeVisible(); + await expect(page.getByRole('heading', { name: 'Polaris \u2014 Namespaces' })).toBeVisible(); // Table should have at least one row with a namespace button const table = page.locator('table'); @@ -52,7 +52,7 @@ test.describe('Polaris plugin smoke tests', () => { ).toBeVisible(); // "Namespace Score" section should be present in drawer - await expect(page.locator('main').getByText('Namespace Score')).toBeVisible(); + await expect(page.getByText('Namespace Score')).toBeVisible(); // Resources table should exist in drawer await expect(page.getByRole('heading', { name: 'Resources' })).toBeVisible(); @@ -105,6 +105,6 @@ test.describe('Polaris plugin smoke tests', () => { ).toBeVisible(); // "Namespace Score" section should be present - await expect(page.locator('main').getByText('Namespace Score')).toBeVisible(); + await expect(page.getByText('Namespace Score')).toBeVisible(); }); }); -- 2.52.0