From 3095bc3459f46994bcb1dc5ee12381c26955416e Mon Sep 17 00:00:00 2001 From: Conduction Release Bot Date: Tue, 1 Sep 2026 10:12:07 +0200 Subject: [PATCH] fix(e2e): assert the dashboard that exists, and stop pinning English on a Dutch instance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit development E2E has been red for a day with six failures in two clusters. Both are the suite describing a world the app left behind. Neither was introduced by the icon work merged earlier today; the same six fail on the 08-31 21:06 run, checked. ## dashboard.spec.ts, three tests They assert an info box: the sentence "Overzicht van uw softwarecatalogus", a heading "Beheer van Organisaties", a "Vernieuwen" button on the surface and a "Ga naar Organisaties" quick-nav. None of that string set exists anywhere in src/ or l10n/nl.json any more. The dashboard is now four stat tiles plus the catalog-panels widget, since the KPI tiles landed in #892, and the spec was never updated. - the overview test now asserts the four tiles and the object-statistics panel; - the refresh test now opens the PAGE ACTIONS menu, where CnDashboardPage puts Refresh (showRefresh defaults true), instead of looking for a button on the surface; - the "Ga naar Organisaties" test is REMOVED rather than retargeted. Its own comment already recorded that the button was a no-op in the shared shell and that the user real path is the Organisations nav entry, which is the very next test in the file. Rewriting it would have tested that path twice while pretending to cover a control that no longer exists. ⚠️ The tile labels are asserted in DUTCH, and they are not all the manifest source. The manifest says "Services"; nl.json maps it to "Diensten". An English assertion would pass only on an English instance. ## sbom-import.spec.ts, two tests await page.getByRole("tab", { name: "Components" }).click() The manifest labels that tab "Components" and nl.json maps it to "Componenten". The e2e instance runs Dutch, so the tab never matched and both tests died on a 60s click timeout that reads like a missing feature rather than a wrong string. The matcher now accepts either spelling, as does the "Open sidebar" button above it. 🔑 The underlying debt is that this suite mixes locales: the dashboard tests asserted Dutch, the sbom tests asserted English, against one Dutch instance. Locale-tolerant matchers are the cheap fix; a decided convention is the real one. --- tests/e2e/sbom-import.spec.ts | 11 ++- tests/e2e/spec-coverage/dashboard.spec.ts | 103 +++++++++++----------- 2 files changed, 61 insertions(+), 53 deletions(-) diff --git a/tests/e2e/sbom-import.spec.ts b/tests/e2e/sbom-import.spec.ts index ebf912d8..5e357e78 100644 --- a/tests/e2e/sbom-import.spec.ts +++ b/tests/e2e/sbom-import.spec.ts @@ -122,11 +122,18 @@ async function openComponentsTab(page: Page): Promise { // The detail page mounts with its right-hand sidebar collapsed, and the // Components / History tabs live INSIDE that sidebar — present in the DOM but // not visible, so a direct tab click waits forever. Open the sidebar first. - const openSidebar = page.getByRole('button', { name: 'Open sidebar' }) + const openSidebar = page.getByRole('button', { + name: /Open sidebar|Zijbalk openen/i, + }) if (await openSidebar.isVisible().catch(() => false)) { await openSidebar.click() } - await page.getByRole('tab', { name: 'Components' }).click() + // The e2e instance runs DUTCH. The manifest labels this tab "Components" + // and l10n/nl.json maps it to "Componenten", so pinning the English source + // waits for a tab that never appears and the test dies on a 60s timeout + // that reads like a missing feature. Matching either spelling keeps the + // spec honest on both locales. + await page.getByRole('tab', { name: /Components|Componenten/i }).click() } // --------------------------------------------------------------------------- diff --git a/tests/e2e/spec-coverage/dashboard.spec.ts b/tests/e2e/spec-coverage/dashboard.spec.ts index a765c0b2..847993b2 100644 --- a/tests/e2e/spec-coverage/dashboard.spec.ts +++ b/tests/e2e/spec-coverage/dashboard.spec.ts @@ -19,83 +19,84 @@ import { APP_MAIN, } from './_helpers' -test('dashboard: renders the overview surface (info box, refresh, statistics tables)', async ({ +test('dashboard: renders the overview surface (stat tiles and the object statistics panel)', async ({ page, }) => { const bag = collectAppErrors(page) await gotoAppRoute(page, '/') const main = page.locator(APP_MAIN).first() - // Page intro / info-box widget content. + // The dashboard is four stat tiles plus the catalog-panels widget. It used + // to be an info box with an intro sentence and a "Beheer van Organisaties" + // heading; that surface was replaced when the KPI tiles landed, and this + // spec kept asserting the old one. + // + // Labels are asserted in DUTCH because the e2e instance runs Dutch, and + // they are NOT all identical to the manifest source: the manifest says + // "Services" and nl.json maps it to "Diensten". Asserting the English + // source here would pass only on an English instance. + for (const label of ['Organisaties', 'Modules', 'Diensten', 'Contracten']) { + await expect( + main.getByText(label, { exact: false }).first(), + `the ${label} stat tile must render`, + ).toBeVisible({ timeout: 30000 }) + } + await expect( - main - .getByText('Overzicht van uw softwarecatalogus', { exact: false }) - .first(), + main.getByText('Object statistieken', { exact: false }).first(), + 'the object statistics panel must render', ).toBeVisible({ timeout: 30000 }) - await expect( - main.getByRole('heading', { name: 'Beheer van Organisaties' }).first(), - ).toBeVisible() - - // Refresh action is present and clickable (drives refreshAllData). - const refresh = main.getByRole('button', { name: 'Vernieuwen' }).first() - await expect(refresh).toBeVisible() - await expect(refresh).toBeEnabled() - - // The statistics overview renders as object-type count tables (two grids). - // At least one statistics table is present in the rendered dashboard. - await expect(main.locator('table').first()).toBeVisible({ timeout: 30000 }) expectNoAppErrors(bag) }) -test('dashboard: "Vernieuwen" refresh re-runs the data load without error', async ({ +test('dashboard: the refresh action re-runs the data load without error', async ({ page, }) => { const bag = collectAppErrors(page) await gotoAppRoute(page, '/') const main = page.locator(APP_MAIN).first() - const refresh = main.getByRole('button', { name: 'Vernieuwen' }).first() - await expect(refresh).toBeEnabled() + // Refresh is NOT a button on the surface. CnDashboardPage puts it in the + // page actions menu, where showRefresh defaults to true, so it is reached + // by opening that menu and clicking the item. + // + // The label is matched loosely on purpose: it comes from the nextcloud-vue + // catalogue rather than this app's, so pinning one spelling would couple + // this spec to the library's translations. + const actions = main + .getByRole('button', { name: /Acties|Actions|Meer|More/i }) + .first() + await expect(actions, 'the page actions menu must be offered').toBeVisible({ + timeout: 30000, + }) + await actions.click() + + const refresh = page + .locator('.v-popper__popper--shown') + .last() + .getByRole('menuitem', { name: /Vernieuwen|Refresh/i }) + .first() + await expect(refresh, 'the actions menu must offer Refresh').toBeVisible({ + timeout: 15000, + }) await refresh.click() - // After refresh the dashboard surface is still intact (info-box heading). + // The surface is still intact after the reload. await expect( - main.getByRole('heading', { name: 'Beheer van Organisaties' }).first(), + main.getByText('Organisaties', { exact: false }).first(), ).toBeVisible({ timeout: 30000 }) expectNoAppErrors(bag) }) -// The info-box "Ga naar Organisaties" button calls -// navigationStore.setSelected('organisaties'). In the deployed manifest shell -// this updates the navigation store but does NOT swap the page URL or the main -// content region (see BUG LIST: the dashboard quick-nav button is a no-op in the -// shared CnAppRoot shell — the user's working path is the "Organisations" nav -// entry, covered separately). We therefore assert the button is a real, -// clickable control and that clicking it leaves the app in a healthy state with -// no stackiq-origin error — rather than asserting a navigation the -// deployed shell does not perform. -test('dashboard: "Ga naar Organisaties" quick-nav button is clickable and error-free', async ({ - page, -}) => { - const bag = collectAppErrors(page) - await gotoAppRoute(page, '/') - const main = page.locator(APP_MAIN).first() - - const goButton = main - .getByRole('button', { name: 'Ga naar Organisaties' }) - .first() - await expect(goButton).toBeVisible({ timeout: 30000 }) - await expect(goButton).toBeEnabled() - await goButton.click() - - // App stays healthy after the click (dashboard surface still rendered). - await expect( - main.getByRole('heading', { name: 'Beheer van Organisaties' }).first(), - ).toBeVisible({ timeout: 30000 }) - expectNoAppErrors(bag) -}) +// The "Ga naar Organisaties" quick-nav test is deliberately gone rather than +// retargeted. It asserted a button inside the old info box, and that whole +// surface was replaced by the KPI tiles. Its own comment already recorded that +// the button was a no-op in the shared shell and that "the user's working path +// is the Organisations nav entry, covered separately" — which is the test +// immediately below. Keeping a rewritten version would re-test that same path +// twice while pretending to cover a control that no longer exists. // The organisaties index is genuinely reachable via the real app nav entry // "Organisations" — this is the user's actual navigation path and lands on the