Skip to content

test(boost): add catalog unit, integration, and Playwright coverage - #4501

Open
HusneShabbir wants to merge 1 commit into
redhat-developer:mainfrom
HusneShabbir:feat/boost-catalog-test-coverage
Open

test(boost): add catalog unit, integration, and Playwright coverage#4501
HusneShabbir wants to merge 1 commit into
redhat-developer:mainfrom
HusneShabbir:feat/boost-catalog-test-coverage

Conversation

@HusneShabbir

@HusneShabbir HusneShabbir commented Aug 31, 2026

Copy link
Copy Markdown
Member

Description

This PR was planned and raised using the automation-coverage MCP against the Boost workspace (workspaces/boost). That MCP reads coverage and git scope, then recommends the cheapest test layer that can catch each failure. UI locators for the plugin Playwright specs were generated with Playwright MCP (browser_snapshotbrowser_generate_locator → verify) against the live NFS app — not from a scenario paragraph, #root ARIA dumps, nth(), or generated react-aria ids.

No published plugin src/ changed, so there is no changeset. Overlay and cluster e2e were not added: this work is plugin-source coverage only.

Tests added by layer

Cheapest layer that can catch the failure wins. The same assertion is not duplicated downstream.

L1 — Unit (Jest)

File What it covers
plugins/boost/src/utils/categoryMeta.test.ts getCategoryMeta maps skill → Skills (case-insensitive) and falls back to Unknown; getAllCategories returns every known id/label.

L2 — Integration (startTestBackend)

File What it covers
plugins/boost-backend/src/plugin.integration.test.ts Boots boostPlugin with a catalog mock and asserts unauthenticated GET /api/boost/health returns 200 and { status: 'ok' }.

L3 — Component (React Testing Library)

File What it covers
plugins/boost/src/components/catalog/EmptyFilteredState.test.tsx Renders empty-filter copy and that Clear filters calls onClearFilters.
plugins/boost/src/components/catalog/AiCatalogTable.test.tsx Table headers and entity fields (name, type, owner, provider, description).
plugins/boost/src/components/catalog/ErrorBoundary.test.tsx Children render when healthy; a thrown child shows the error title, message, and Retry.
plugins/boost/src/components/catalog/entity/SummaryCard.test.tsx Description/rationale; hidden when empty; agent-only fields and available models.
plugins/boost/src/components/catalog/entity/VersionListCard.test.tsx Current-version badge; renders nothing when the version annotation is missing.
plugins/boost/src/components/catalog/AiCatalogPage.test.tsx Extended (not new): empty-filtered state for ?q=no-such-asset, and table view when ?view=table is in the URL (plus existing loading / grid / empty / error / search cases).

L4 — Plugin Playwright e2e (Playwright MCP)

Workspace config: playwright.config.ts, yarn test:e2e. Catalog API is mocked (page.route) so the spec does not need a live backend. Role locators only.

File What it covers
e2e-tests/boost.AiCatalogPage.test.ts Guest sign-in → AI Catalog heading. Mocked assets render. Catalog API failure → error + Retry. Type → Skills keeps only the skill card and sets type=skill. Search keeps only matching cards and sets q. Table view lists both assets in grid "Data table" and sets view=table. Unmatched search → empty-filtered copy; Clear filters restores both cards and drops q.

Not in this PR: overlay smoke/e2e, cluster-free e2e, cluster e2e.

Checklist

  • A changeset describing the change and affected packages. (more info)
  • Added or Updated documentation
  • Tests for new functionality and regression tests for bug fixes
  • Screenshots attached (for UI changes)

Signed-off-by: HusneShabbir <husneshabbir447@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 31, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 4:35 PM UTC · Completed 4:45 PM UTC

Commit: 5d8effc · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Cost: $4.78

@sonarqubecloud

Copy link
Copy Markdown

@codecov

codecov Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 61.72%. Comparing base (8b2019f) to head (5d8effc).
⚠️ Report is 9 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4501      +/-   ##
==========================================
+ Coverage   61.62%   61.72%   +0.10%     
==========================================
  Files        2569     2569              
  Lines      102831   102844      +13     
  Branches    28787    28787              
==========================================
+ Hits        63367    63478     +111     
+ Misses      38888    38790      -98     
  Partials      576      576              
Flag Coverage Δ *Carryforward flag
adoption-insights 84.55% <ø> (ø) Carriedforward from 8b2019f
ai-integrations 69.09% <ø> (ø) Carriedforward from 8b2019f
app-defaults 55.55% <ø> (ø) Carriedforward from 8b2019f
augment 46.67% <ø> (ø) Carriedforward from 8b2019f
boost 82.37% <ø> (+2.52%) ⬆️
bulk-import 72.79% <ø> (ø) Carriedforward from 8b2019f
cost-management 13.55% <ø> (ø) Carriedforward from 8b2019f
dcm 72.09% <ø> (ø) Carriedforward from 8b2019f
e2e-adoption-insights 60.00% <ø> (ø) Carriedforward from 8b2019f
e2e-extensions 62.13% <ø> (ø) Carriedforward from 8b2019f
e2e-global-header 49.45% <ø> (ø) Carriedforward from 8b2019f
e2e-homepage 61.11% <ø> (ø) Carriedforward from 8b2019f
e2e-intelligent-assistant 46.65% <ø> (ø) Carriedforward from 8b2019f
e2e-orchestrator 49.52% <ø> (ø) Carriedforward from 8b2019f
e2e-orchestrator-plugin 49.51% <ø> (ø) Carriedforward from 8b2019f
e2e-quickstart 55.21% <ø> (ø) Carriedforward from 8b2019f
e2e-scorecard 50.21% <ø> (ø) Carriedforward from 8b2019f
e2e-theme 16.36% <ø> (ø) Carriedforward from 8b2019f
extensions 56.59% <ø> (ø) Carriedforward from 8b2019f
global-floating-action-button 71.18% <ø> (ø) Carriedforward from 8b2019f
global-header 66.50% <ø> (ø) Carriedforward from 8b2019f
homepage 48.39% <ø> (ø) Carriedforward from 8b2019f
install-dynamic-plugins 58.57% <ø> (ø) Carriedforward from 8b2019f
intelligent-assistant 75.35% <ø> (ø) Carriedforward from 8b2019f
konflux 91.98% <ø> (ø) Carriedforward from 8b2019f
lightspeed 69.02% <ø> (ø) Carriedforward from 8b2019f
mcp-integrations 84.14% <ø> (ø) Carriedforward from 8b2019f
orchestrator 70.87% <ø> (ø) Carriedforward from 8b2019f
quickstart 63.74% <ø> (ø) Carriedforward from 8b2019f
sandbox 79.56% <ø> (ø) Carriedforward from 8b2019f
scorecard 87.41% <ø> (ø) Carriedforward from 8b2019f
theme 88.91% <ø> (ø) Carriedforward from 8b2019f
translations 5.12% <ø> (ø) Carriedforward from 8b2019f
x2a 77.10% <ø> (ø) Carriedforward from 8b2019f

*This pull request uses carry forward flags. Click here to find out more.


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 8b2019f...5d8effc. Read the comment docs.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@HusneShabbir
HusneShabbir requested a review from jrichter1 August 31, 2026 16:41
@fullsend-ai-review

Copy link
Copy Markdown

Review — Approve

Clean test-coverage PR that adds unit, integration, component, and Playwright e2e tests to the Boost workspace. No production code is modified. All test files were verified against their source implementations — assertions are correct, mock data shapes match real types, and the Playwright e2e locator strategy uses semantic ARIA roles.

Findings

All findings are low severity. None block merge.

Style & conventions

# Severity File Finding
1 low e2e-tests/boost.AiCatalogPage.test.ts E2e filename uses PascalCase. Every other e2e test across the repo uses all-lowercase or camelCase after the separator (e.g., lightspeed.ui.test.ts, globalHeader.test.ts). Consider boost.aiCatalogPage.test.ts for consistency.
2 low plugins/boost/src/components/catalog/AiCatalogPage.test.tsx New tests duplicate render boilerplate. The two new test cases inline the full renderInTestApp + TestApiProvider + AiCatalogPage setup instead of reusing the existing renderPage() helper at line 76. The helper could accept an optional routeEntries parameter.
3 low playwright.config.ts testDir placed per-project instead of top-level. Other workspace Playwright configs place testDir: 'e2e-tests' at the defineConfig top level, not inside individual project entries. Functionally equivalent but deviates from the established structural pattern.

Documentation currency

# Severity File Finding
4 low workspaces/boost/AGENTS.md Build & verify table omits test:e2e. The table lists unit test commands but not the new Playwright e2e script. Agents working in this workspace may not discover e2e tests exist. Note: other workspaces also omit test:e2e from their docs, so this is consistent with repo norms.

Dimension summaries

  • Correctness: All 10 test files verified against source. Assertions match real component behavior, mock shapes match actual data types, edge cases (empty entity, missing annotations, unrecognized categories) are covered. No existing assertions were weakened in the modified AiCatalogPage.test.tsx. The backend integration test correctly uses startTestBackend with proper cleanup.
  • Security: No secrets, credentials, injection patterns, or permission changes. The development-only-no-auth config is scoped to the test harness.
  • Intent & coherence: Cleanly scoped to test coverage. Follows established repo patterns for Playwright setup, test file naming, and directory structure. No linked issue, but test-infrastructure work from a MEMBER author does not require explicit issue authorization per project norms.
  • Style & conventions: Tests follow established patterns (copyright headers, import style, describe/it nesting). Minor deviations noted above.
  • Documentation currency: test:e2e undocumented in workspace AGENTS.md, consistent with other workspaces.
  • Cross-repo contracts: Skipped — no exported interfaces modified.

Labels: Test-only PR adding coverage to the Boost workspace.

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Note: The following review comments could not be posted on the diff (GitHub returned 422) and are included here instead:

  • workspaces/boost/plugins/boost/src/components/catalog/AiCatalogPage.test.tsx (file-level): Line 412 · [low] code-organization

The two new test cases inline the full renderInTestApp + TestApiProvider + AiCatalogPage boilerplate instead of reusing the existing renderPage() helper at line 76. The helper could be extended to accept an optional routeEntries parameter.

baseURL: process.env.PLAYWRIGHT_URL ?? 'http://localhost:3000',
screenshot: 'only-on-failure',
trace: 'on-first-retry',
navigationTimeout: 60_000,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] config-convention

testDir is specified per-project inside the projects array rather than at the top level of defineConfig. Other workspace Playwright configs in the repo place testDir at the top level.

@fullsend-ai-review fullsend-ai-review Bot added ready-for-merge All reviewers approved — ready to merge workspace/boost Boost workspace (Backstage AI plugin) Tests Review effort 2/5 labels Aug 31, 2026
address && typeof address === 'object' ? address.port : undefined;
expect(port).toBeDefined();

const response = await fetch(`http://127.0.0.1:${port}/api/boost/health`);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This won't catch a regression in the auth policy. startTestBackend wires mockServices.httpAuth, and MockHttpAuthService falls back to mockCredentials.user() when the request carries no Authorization header — so createCredentialsBarrier sees a valid user principal and calls next() regardless. Delete httpRouter.addAuthPolicy({ path: '/health', allow: 'unauthenticated' }) from plugin.ts and this test still gets a 200.

Sending none-credentials is what pins the policy down:

import { mockCredentials } from '@backstage/backend-test-utils';

const response = await fetch(`http://127.0.0.1:${port}/api/boost/health`, {
  headers: { Authorization: mockCredentials.none.header() },
});

Separately, boost.security.mode: 'development-only-no-auth' above is a no-op here — validateSecurityMode returns exactly that when the key is unset, and the plugin only logs the result.

await page.goto('/ai-catalog');
const enter = page.getByRole('button', { name: 'Enter' });
const heading = page.getByRole('heading', { name: 'AI Catalog' });
await Promise.race([

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Promise.race doesn't cancel the loser. With a fresh context there's no provider in localStorage, so the Guest card renders and enter wins — which leaves heading.waitFor({ timeout: 20_000 }) pending with its deadline counted from goto. The explicit expect(heading).toBeVisible({ timeout: 20_000 }) further down starts counting from the click instead, so on a cold first run where the app needs ~25s to reach the catalog, that assertion passes while the orphaned waitFor still rejects at 20s. Playwright's worker turns an unhandled rejection into a test failure, so it lands as a random red on whatever assertion happened to be running.

locator.or() avoids the dangling promise:

await expect(enter.or(heading).first()).toBeVisible({ timeout: 30_000 });

Worth getting right because this PR is what switches Playwright on for boost in CI — the step is gated on playwright.config.ts existing, and it runs on both node 22 and 24.

it('renders nothing when there is no summary content', async () => {
const { container } = await renderWithEntity(emptyEntity);
expect(screen.queryByText(msg.card.summaryTitle)).toBeNull();
expect(container.querySelector('[class*="card"]')).toBeNull();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This assertion can't fail. @backstage/ui renders Card with class="bui-Card Card_bui-Card__<hash>", and CSS attribute-value matching is case-sensitive, so [class*="card"] never matches bui-Card and querySelector returns null whatever the component did.

That leaves line 89 carrying the whole test — if SummaryCard ever regressed to returning an empty <Card> shell instead of null, both assertions would still pass. expect(container).toBeEmptyDOMElement() says what you actually mean.

expect(container.querySelector('[class*="card"]')).toBeNull();
});

it('renders agent-only fields and available models', async () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The fixtures make the isAgent gate untestable by construction: skillEntity carries none of instructions/handoffDescription/enableRAG and agentEntity carries all three. You could delete all three isAgent ? ... : undefined guards in SummaryCard.tsx and every test in this file still passes.

Since the test is named "agent-only fields", a third fixture would pin down the "only" part — a type: 'skill' entity carrying instructions and enableRAG: true, asserting queryByText(msg.card.instructionsTitle) comes back null.


expect(screen.getByText(msg.table.name)).toBeInTheDocument();
expect(screen.getByText(msg.table.type)).toBeInTheDocument();
expect(screen.getByText('Code Review Skill')).toBeInTheDocument();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You're already rendering the link here — worth asserting where it points. entityHref builds /catalog/${namespace}/${kind.toLowerCase()}/${name} and has no test anywhere in the plugin; entityHelpers.test.ts covers entityRefHref, getAdoptionAction and applyEntityFilters, but not this one. It feeds every card link and every table row href, so a slip in the kind lowercasing or the namespace default would 404 every entity page with nothing going red.

expect(screen.getByRole('link', { name: 'Code Review Skill' })).toHaveAttribute(
  'href',
  '/catalog/default/airesource/code-review-skill',
);


expect(screen.getByText('Failed to load AI assets')).toBeInTheDocument();
expect(screen.getByText('catalog render exploded')).toBeInTheDocument();
expect(screen.getByText('Retry')).toBeInTheDocument();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This checks the button is there but never that it does anything — handleRetry and its onPress wiring could both be removed and both tests stay green. Recovering from the error is the only stateful behaviour the component has.

A child that throws on its first render and succeeds afterwards would let you click Retry and assert the children come back.

expect(screen.getByText('Code Review Skill')).toBeInTheDocument();
});
expect(screen.getByText(msg.table.name)).toBeInTheDocument();
expect(screen.getByLabelText(msg.toolbar.viewTable)).toBeInTheDocument();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: this one is true in grid view too — the ToggleButtonGroup renders both buttons unconditionally, so the label is in the document either way. msg.table.name on the line above is the only assertion that actually distinguishes table from grid. Asserting the toggle's selected state, or that the card links are gone, would cover the URL to view-mode contract.

* limitations under the License.
*/

import { expect, test, type Page } from '@playwright/test';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: nothing type-checks this file. The workspace tsconfig.json only includes packages/*/src, plugins/*/src, plugins/*/dev and plugins/*/migrations, so yarn tsc:full never sees e2e-tests/, and Playwright transpiles without type-checking. global-header, homepage and quickstart all add "e2e-tests" to their include — a one-line change would put these 241 new lines under the compiler.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-merge All reviewers approved — ready to merge Review effort 2/5 Tests workspace/boost Boost workspace (Backstage AI plugin)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants