Skip to content

Conversation

@ochosi
Copy link
Collaborator

@ochosi ochosi commented Dec 22, 2025

TL;DR:
What's currently failing is the subscription/registration of the image against stage, despite it being built with an activation key. I could reproduce this manually with stage, not with production.
Otherwise, I verified all the steps in this test manually against prod, and it all worked as expected.

This test

  1. ✅ logs into consoledot
  2. ✅ creates a custom repo
  3. ✅ creates a content template
  4. ✅ creates a blueprint with the content template
  5. ✅ installs a package from the content template
  6. ✅ builds the image
  7. ✅ downloads and uploads it to openstack
  8. ✅ launches it in openstack
  9. ✅ verifies the package from the custom repo was installed
  10. 🔴 verifies the content is coming from a template
  11. 🔴 verifies in consoledot that the instance is connected to the content template

FIXME:

  1. Up to step 7 this works locally. I suspect my openstack auth/config isn't good because I cannot see any meaningful differences to other boot tests.
  2. For some reason, the repositories step in the wizard needs to be opened for the package to appear in the search.

@ochosi ochosi changed the title WIP: playwright: Add boot test for content-templates WIP: playwright: Add boot test for content-templates (HMS-9499) Dec 22, 2025
@codecov
Copy link

codecov bot commented Dec 22, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 74.17%. Comparing base (ee754f4) to head (dffde0b).
⚠️ Report is 3 commits behind head on main.

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #3947      +/-   ##
==========================================
- Coverage   75.08%   74.17%   -0.92%     
==========================================
  Files         202      205       +3     
  Lines        7446     7555     +109     
  Branches     2559     2584      +25     
==========================================
+ Hits         5591     5604      +13     
- Misses       1626     1709      +83     
- Partials      229      242      +13     

see 4 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

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

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ochosi ochosi force-pushed the content-templates-boot-test branch from 9ec96da to 5c0216c Compare January 4, 2026 23:48
@ochosi ochosi changed the title WIP: playwright: Add boot test for content-templates (HMS-9499) playwright: Add boot test for content-templates (HMS-9499) Jan 6, 2026
@ochosi ochosi marked this pull request as ready for review January 6, 2026 00:33
@ochosi ochosi requested a review from a team as a code owner January 6, 2026 00:33
@ochosi ochosi requested review from ksiekl, mgold1234 and regexowl and removed request for a team January 6, 2026 00:33
Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 3 issues, and left some high level feedback:

  • In registerWithActivationKey, using getByText('Enable predictive analytics') / getByText('Enable remote remediations') combined with isChecked() assumes those text locators resolve to a checkbox/radio; if these are PatternFly switches or labels, this will likely be brittle or fail at runtime—consider targeting the underlying input/switch role directly (e.g. by role or data-test-id) instead of the text node.
  • The new registerWithActivationKey helper hardcodes selection of an option with name: 'activation-key-', which seems very loose and may match multiple or no options depending on environment; consider making the activation key name configurable or matching a more specific label to avoid flakiness.
  • Multiple existing boot tests are now individually test.skip-ed with a temporary comment; if these are meant to be disabled only while debugging, consider grouping them in a describe.skip or guarding via an environment flag so they can be re-enabled or toggled more easily and are less likely to stay skipped unintentionally.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `registerWithActivationKey`, using `getByText('Enable predictive analytics')` / `getByText('Enable remote remediations')` combined with `isChecked()` assumes those text locators resolve to a checkbox/radio; if these are PatternFly switches or labels, this will likely be brittle or fail at runtime—consider targeting the underlying input/switch role directly (e.g. by role or data-test-id) instead of the text node.
- The new `registerWithActivationKey` helper hardcodes selection of an option with `name: 'activation-key-'`, which seems very loose and may match multiple or no options depending on environment; consider making the activation key name configurable or matching a more specific label to avoid flakiness.
- Multiple existing boot tests are now individually `test.skip`-ed with a temporary comment; if these are meant to be disabled only while debugging, consider grouping them in a `describe.skip` or guarding via an environment flag so they can be re-enabled or toggled more easily and are less likely to stay skipped unintentionally.

## Individual Comments

### Comment 1
<location> `playwright/helpers/wizardHelpers.ts:84-85` </location>
<code_context>
+    await page.getByRole('option', { name: 'activation-key-' }).click();
+
+    // Ensure Enable predictive analytics is checked
+    const insightsSwitch = page.getByText('Enable predictive analytics');
+    if (!(await insightsSwitch.isChecked())) {
+      await insightsSwitch.click();
+    }
</code_context>

<issue_to_address>
**issue (bug_risk):** Using `getByText(...).isChecked()` is fragile and may fail if the target is not the actual checkbox/switch element.

`isChecked()` only works on checkable controls (checkbox/radio/switch). `getByText('Enable predictive analytics')` may resolve to a label or wrapper instead of the input, causing runtime errors or misleading results. Please select the actual control via role, e.g. `getByRole('switch', { name: 'Enable predictive analytics' })` (or `checkbox`) and apply the same pattern to the `Enable remote remediations` toggle.
</issue_to_address>

### Comment 2
<location> `playwright/BootTests/helpers/helpers.ts:294-303` </location>
<code_context>
+export const deleteTemplate = async (page: Page, templateName: string) => {
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Broad `try/catch` causes any error (including navigation issues) to be silently swallowed when a template is missing.

Because the `try` wraps navigation, search, and the visibility `expect`, any failure in that block (network/auth issues, locator changes, etc.) is treated as “template not found” and the function just returns. If you only want to handle the “template does not exist” case, please narrow the `catch` to the specific operation that detects absence (e.g., the `expect` or a timeout-bounded `locator.count()` check) and allow unexpected errors to surface instead of being swallowed.

Suggested implementation:

```typescript
export const deleteTemplate = async (page: Page, templateName: string) => {
  await closePopupsIfExist(page);
  await test.step(
    'Delete the template with name: ' + templateName,
    async () => {
      await navigateToTemplates(page);

      const filterInput = page.getByRole('searchbox', { name: 'Filter by name' });
      await filterInput.fill(templateName);

      const templateRow = page.getByRole('row', { name: templateName });
      const rowCount = await templateRow.count();

      // If the template is not found after filtering, fail gracefully
      if (rowCount === 0) {
        return;
      }

      // Wait for the template row to appear before interacting with it
      await templateRow.first().waitFor({ state: 'visible', timeout: 10000 });

```

I only see the beginning of the `deleteTemplate` function. To fully align with this change:

1. Remove any remaining `catch` block that was paired with the `try` you previously had, since the `try` has been removed.
2. If the rest of the function re-locates the template row, you can simplify it by reusing `templateRow.first()` from above for subsequent actions (clicking menus, confirming deletion, etc.).
3. If you want a different timeout than `10000`ms, adjust the `waitFor` timeout to match your test suite’s conventions.
</issue_to_address>

### Comment 3
<location> `playwright/BootTests/helpers/helpers.ts:382-391` </location>
<code_context>
+          shouldRetry = true;
+        } else {
+          // Find the system with matching hostname
+          const system = body.data.find(
+            (sys: { attributes: { display_name: string } }) =>
+              sys.attributes.display_name === hostname,
+          );
+
+          if (!system) {
+            console.log(
+              `System '${hostname}' not found in inventory, attempt ${attempts}/${maxAttempts}`,
+            );
+            shouldRetry = true;
+          } else {
+            // Check if system has a template_uuid assigned
+            const hasTemplate = !!system.attributes?.template_uuid;
+
+            if (hasTemplate) {
</code_context>

<issue_to_address>
**suggestion:** The inferred `system` shape is partially typed and could lead to unnoticed shape changes in the API response.

`system` is typed as `{ attributes: { display_name: string } }`, but later you access `attributes.template_uuid` and `attributes.template_name`. With this mismatch, TypeScript won’t catch backend changes or missing fields. Consider defining a `System` interface that includes all used fields (`display_name`, `template_uuid`, `template_name`, etc.) and using that type for `body.data` / `system` for stronger type safety.

Suggested implementation:

```typescript
      } else {
        interface System {
          attributes: {
            display_name: string;
            template_uuid?: string;
            template_name?: string;
          };
          // Include any other fields returned by the API that we don't use directly
          [key: string]: unknown;
        }

        const body: { data?: System[] } = await response.json();

```

```typescript
          // Find the system with matching hostname
          const system = body.data.find(
            (sys: System) => sys.attributes.display_name === hostname,
          );

```

If `System` is already defined elsewhere in your codebase (e.g., shared types for the inventory API), you should instead import that existing type at the top of this file and remove the local `interface System` declaration. Adjust the import path and remove the inline `interface System` accordingly to avoid duplicate type definitions.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +84 to +85
const insightsSwitch = page.getByText('Enable predictive analytics');
if (!(await insightsSwitch.isChecked())) {
Copy link

Choose a reason for hiding this comment

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

issue (bug_risk): Using getByText(...).isChecked() is fragile and may fail if the target is not the actual checkbox/switch element.

isChecked() only works on checkable controls (checkbox/radio/switch). getByText('Enable predictive analytics') may resolve to a label or wrapper instead of the input, causing runtime errors or misleading results. Please select the actual control via role, e.g. getByRole('switch', { name: 'Enable predictive analytics' }) (or checkbox) and apply the same pattern to the Enable remote remediations toggle.

Comment on lines +294 to +303
export const deleteTemplate = async (page: Page, templateName: string) => {
await closePopupsIfExist(page);
await test.step(
'Delete the template with name: ' + templateName,
async () => {
try {
await navigateToTemplates(page);
await page
.getByRole('searchbox', { name: 'Filter by name' })
.fill(templateName);
Copy link

Choose a reason for hiding this comment

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

suggestion (bug_risk): Broad try/catch causes any error (including navigation issues) to be silently swallowed when a template is missing.

Because the try wraps navigation, search, and the visibility expect, any failure in that block (network/auth issues, locator changes, etc.) is treated as “template not found” and the function just returns. If you only want to handle the “template does not exist” case, please narrow the catch to the specific operation that detects absence (e.g., the expect or a timeout-bounded locator.count() check) and allow unexpected errors to surface instead of being swallowed.

Suggested implementation:

export const deleteTemplate = async (page: Page, templateName: string) => {
  await closePopupsIfExist(page);
  await test.step(
    'Delete the template with name: ' + templateName,
    async () => {
      await navigateToTemplates(page);

      const filterInput = page.getByRole('searchbox', { name: 'Filter by name' });
      await filterInput.fill(templateName);

      const templateRow = page.getByRole('row', { name: templateName });
      const rowCount = await templateRow.count();

      // If the template is not found after filtering, fail gracefully
      if (rowCount === 0) {
        return;
      }

      // Wait for the template row to appear before interacting with it
      await templateRow.first().waitFor({ state: 'visible', timeout: 10000 });

I only see the beginning of the deleteTemplate function. To fully align with this change:

  1. Remove any remaining catch block that was paired with the try you previously had, since the try has been removed.
  2. If the rest of the function re-locates the template row, you can simplify it by reusing templateRow.first() from above for subsequent actions (clicking menus, confirming deletion, etc.).
  3. If you want a different timeout than 10000ms, adjust the waitFor timeout to match your test suite’s conventions.

Comment on lines 382 to 391
const system = body.data.find(
(sys: { attributes: { display_name: string } }) =>
sys.attributes.display_name === hostname,
);

if (!system) {
console.log(
`System '${hostname}' not found in inventory, attempt ${attempts}/${maxAttempts}`,
);
shouldRetry = true;
Copy link

Choose a reason for hiding this comment

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

suggestion: The inferred system shape is partially typed and could lead to unnoticed shape changes in the API response.

system is typed as { attributes: { display_name: string } }, but later you access attributes.template_uuid and attributes.template_name. With this mismatch, TypeScript won’t catch backend changes or missing fields. Consider defining a System interface that includes all used fields (display_name, template_uuid, template_name, etc.) and using that type for body.data / system for stronger type safety.

Suggested implementation:

      } else {
        interface System {
          attributes: {
            display_name: string;
            template_uuid?: string;
            template_name?: string;
          };
          // Include any other fields returned by the API that we don't use directly
          [key: string]: unknown;
        }

        const body: { data?: System[] } = await response.json();
          // Find the system with matching hostname
          const system = body.data.find(
            (sys: System) => sys.attributes.display_name === hostname,
          );

If System is already defined elsewhere in your codebase (e.g., shared types for the inventory API), you should instead import that existing type at the top of this file and remove the local interface System declaration. Adjust the import path and remove the inline interface System accordingly to avoid duplicate type definitions.

@coderabbitai
Copy link

coderabbitai bot commented Jan 6, 2026

📝 Walkthrough

Walkthrough

Multiple Playwright boot tests were converted to skipped tests (AAP, Compliance CIS, several Content non-repeatable and repeatable tests, Satellite) by wrapping them with test.skip and adding TODO/ESLint comments. A new integration test file, playwright/BootTests/Content/ContentTemplate.boot.ts, was added to exercise hosted content templates, blueprint creation, OpenStack image build/provisioning, and verification flows. New helper utilities were added in playwright/BootTests/helpers/helpers.ts (navigateToTemplates, deleteTemplate, pollForSystemTemplateAttachment, pollForSystemInInventory) and registerWithActivationKey was added to playwright/helpers/wizardHelpers.ts. No exported/public signatures were removed.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Fix all issues with AI Agents 🤖
In @playwright/BootTests/AAP/AAP.boot.ts:
- Around line 63-65: Reformat the test.skip arrow function parameters so
Prettier passes: update the test declaration for test.skip('AAP registration
boot integration test', async ({ page, cleanup }) => { ... }) to follow the
project's Prettier rules (either keep the async and the destructured params on a
single well-spaced line or break the parameter list onto its own line if the
linter requires), ensuring the destructured params { page, cleanup } are
correctly spaced and no trailing commas or extra spaces remain.

In @playwright/BootTests/Compliance/ComplianceCIS.boot.ts:
- Around line 184-186: The test.skip declaration for "Compliance alerts - lint
warnings display" has a formatting issue in the async arrow function parameter
list; reformat the function signature so it conforms to Prettier (ensure proper
spacing after the comma and no extraneous whitespace) — specifically update the
async ({ page, cleanup }) => { signature used in the test.skip call to match
project's prettier rules (e.g., proper spacing and/or place parameters on their
own line) and then run the formatter to verify the change.
- Around line 34-36: Reformat the skipped test declaration so the async
parameter list follows Prettier rules: update the line containing
test.skip('Compliance step integration test - CIS', async ({ page, cleanup }) =>
{ to ensure proper spacing/quoting (e.g., double vs single quotes per project
style) and consistent spacing around the arrow function parameters; run Prettier
or adjust to the project formatter so the async ({ page, cleanup }) => {
parameter formatting passes CI; locate the test.skip invocation in
ComplianceCIS.boot.ts to apply the change.

In @playwright/helpers/wizardHelpers.ts:
- Around line 90-93: The current code calls isChecked() on the text locator
(rhcSwitch = page.getByText('Enable remote remediations')) which targets the
label, not the input; update the locator to point at the actual checkbox/switch
input (e.g., use page.getByLabel('Enable remote remediations') or
page.getByRole('checkbox', { name: 'Enable remote remediations' }) or find the
input inside the label via locator('label:has-text("Enable remote remediations")
>> input')) and then call isChecked() and click() on that input locator (symbols
to change: rhcSwitch, page.getByText, isChecked, click).
- Around line 84-87: The code calls isChecked() on the text node returned by
getByText('Enable predictive analytics') (variable insightsSwitch); change the
locator to target the actual form control (e.g., use page.getByLabel('Enable
predictive analytics'), page.getByRole('checkbox' or 'switch', { name: 'Enable
predictive analytics' }), or find the input inside the container returned by
getByText) and then call isChecked() and click() on that input element instead
of the label text so the checked state is read and toggled correctly.
- Around line 70-95: The selector in registerWithActivationKey that clicks the
activation key option currently uses a vague prefix ('activation-key-') and can
match the wrong item; update registerWithActivationKey to select the exact
activation key by either accepting a parameter (e.g., activationKey) and using
that full name in the getByRole('option', { name: activationKey }) call, or by
targeting a stable attribute like a data-testid on the option (e.g.,
getByTestId('activation-key-<id>')), and replace the current partial-match
selector accordingly to guarantee the intended option is clicked.
🧹 Nitpick comments (3)
playwright/BootTests/helpers/helpers.ts (2)

428-431: Simplify redundant conditional.

The static analysis tool correctly identifies that shouldRetry is always truthy at this point. Every code path that reaches line 429 has already set shouldRetry = true, and the only path that sets it to false (initial state) returns early at line 410.

🔎 Proposed fix
-    // Check if we should retry with delay
-    if (shouldRetry && attempts < maxAttempts) {
+    // Wait before next retry attempt
+    if (attempts < maxAttempts) {
       await sleep(delayMs);
     }

382-385: Incomplete type annotation for system attributes.

The inline type for sys only declares display_name, but the code later accesses template_uuid and template_name (lines 394, 397). Consider extending the type to include all accessed properties for better type safety.

🔎 Proposed fix
-              const system = body.data.find(
-                (sys: { attributes: { display_name: string } }) =>
-                  sys.attributes.display_name === hostname,
-              );
+              interface SystemAttributes {
+                display_name: string;
+                template_uuid?: string;
+                template_name?: string;
+              }
+              const system = body.data.find(
+                (sys: { attributes: SystemAttributes }) =>
+                  sys.attributes.display_name === hostname,
+              );
playwright/BootTests/Content/ContentTemplate.boot.ts (1)

186-194: Consider documenting or tracking the FIXME.

The comment indicates a workaround is needed for the package search to work. If this is a bug in the application, consider opening an issue to track it, so this workaround can be removed when fixed.

Would you like me to help create an issue to track this behavior?

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ff27259 and e1dcdba.

📒 Files selected for processing (8)
  • playwright/BootTests/AAP/AAP.boot.ts
  • playwright/BootTests/Compliance/ComplianceCIS.boot.ts
  • playwright/BootTests/Content/ContentNonRepeatable.boot.ts
  • playwright/BootTests/Content/ContentRepeatable.boot.ts
  • playwright/BootTests/Content/ContentTemplate.boot.ts
  • playwright/BootTests/Satellite/Satellite.boot.ts
  • playwright/BootTests/helpers/helpers.ts
  • playwright/helpers/wizardHelpers.ts
🧰 Additional context used
🧬 Code graph analysis (3)
playwright/helpers/wizardHelpers.ts (1)
playwright/helpers/helpers.ts (1)
  • isHosted (27-29)
playwright/BootTests/helpers/helpers.ts (1)
playwright/helpers/helpers.ts (1)
  • closePopupsIfExist (31-55)
playwright/BootTests/Content/ContentTemplate.boot.ts (5)
playwright/helpers/helpers.ts (1)
  • isHosted (27-29)
playwright/BootTests/helpers/helpers.ts (5)
  • deleteRepository (75-106)
  • deleteTemplate (294-324)
  • navigateToRepositories (247-271)
  • navigateToTemplates (277-285)
  • pollForSystemTemplateAttachment (341-436)
playwright/BootTests/helpers/OpenStackWrapper.ts (1)
  • OpenStackWrapper (5-288)
playwright/helpers/login.ts (2)
  • ensureAuthenticated (43-69)
  • storeStorageStateAndToken (133-151)
playwright/helpers/navHelpers.ts (3)
  • navigateToLandingPage (63-69)
  • ibFrame (50-57)
  • fillInImageOutput (18-44)
🪛 GitHub Actions: Boot tests
playwright/BootTests/Content/ContentTemplate.boot.ts

[error] 274-276: System did not register within 300 seconds.

🪛 GitHub Actions: Development checks
playwright/BootTests/AAP/AAP.boot.ts

[error] 65-65: prettier/prettier: Replace ·page,·cleanup· with ⏎··page,⏎··cleanup,⏎

🪛 GitHub Check: ESLint
playwright/BootTests/Compliance/ComplianceCIS.boot.ts

[failure] 36-36:
Replace ·page,·cleanup· with ⏎··page,⏎··cleanup,⏎


[failure] 186-186:
Replace ·page,·cleanup· with ⏎··page,⏎··cleanup,⏎

playwright/BootTests/helpers/helpers.ts

[warning] 429-429:
Unnecessary conditional, value is always truthy

playwright/BootTests/AAP/AAP.boot.ts

[failure] 65-65:
Replace ·page,·cleanup· with ⏎··page,⏎··cleanup,⏎

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (49)
  • GitHub Check: Sourcery review
  • GitHub Check: Red Hat Konflux / image-builder-frontend-on-pull-request
  • GitHub Check: testing-farm:centos-stream-10-x86_64:self
  • GitHub Check: testing-farm:centos-stream-10-x86_64:self
  • GitHub Check: testing-farm:centos-stream-10-x86_64:self
  • GitHub Check: testing-farm:fedora-42-x86_64:self
  • GitHub Check: testing-farm:fedora-43-x86_64:self
  • GitHub Check: testing-farm:fedora-43-x86_64:self
  • GitHub Check: testing-farm:fedora-42-x86_64:self
  • GitHub Check: testing-farm:fedora-43-x86_64:self
  • GitHub Check: testing-farm:fedora-42-x86_64:self
  • GitHub Check: testing-farm:fedora-42-x86_64:self
  • GitHub Check: testing-farm:centos-stream-10-x86_64:self
  • GitHub Check: testing-farm:fedora-43-x86_64:self
  • GitHub Check: rpm-build:centos-stream-10-aarch64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-9-aarch64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:centos-stream-10-aarch64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-9-aarch64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: testing-farm:fedora-43-x86_64:self
  • GitHub Check: testing-farm:centos-stream-10-x86_64:self
  • GitHub Check: testing-farm:fedora-42-x86_64:self
  • GitHub Check: rpm-build:centos-stream-10-aarch64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-9-aarch64
  • GitHub Check: testing-farm:fedora-42-x86_64:self
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: testing-farm:centos-stream-10-x86_64:self
  • GitHub Check: testing-farm:fedora-43-x86_64:self
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:centos-stream-9-aarch64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-10-aarch64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: Sourcery review
🔇 Additional comments (13)
playwright/BootTests/Satellite/Satellite.boot.ts (1)

29-31: LGTM - Test temporarily disabled.

The test is appropriately skipped with a clear TODO comment indicating temporary disablement. This aligns with the PR objective to isolate the new ContentTemplate test during debugging.

playwright/BootTests/Content/ContentRepeatable.boot.ts (1)

26-28: LGTM - Test temporarily disabled.

The test is appropriately skipped with a clear TODO comment indicating temporary disablement. This aligns with the PR objective to isolate the new ContentTemplate test during debugging.

playwright/BootTests/helpers/helpers.ts (3)

272-285: LGTM!

The navigateToTemplates function follows the same pattern as the existing navigateToRepositories function but is simpler since it doesn't need to handle a zero state. The implementation is clear and consistent with other navigation helpers in this file.


294-324: LGTM!

The deleteTemplate function follows the established pattern from deleteRepository and deleteCompliancePolicy. The graceful failure handling in the catch block is appropriate for cleanup operations that should not fail the test if the resource doesn't exist.


326-330: LGTM!

Simple and correct sleep utility function.

playwright/BootTests/Content/ContentTemplate.boot.ts (6)

1-33: LGTM!

The imports and test setup are well-organized. The test properly imports all necessary helpers and uses the cleanup fixture for resource management.


44-61: LGTM!

Good practice registering cleanup functions early, before creating resources. This ensures cleanup runs even if the test fails mid-execution. The pre-cleanup of the repository URL (line 54) is a defensive measure to handle leftover resources from previous failed runs.


65-158: LGTM!

The repository and template creation steps are well-structured. The 180-second timeouts for waiting on snapshot and template validation are appropriate given these are async backend operations.


245-277: Registration timeout is a known issue; consider adding retry diagnostics.

The pipeline failure confirms the PR description's note about registration against stage failing. The return at line 263 works but may be confusing—it exits the test.step callback, which completes the step successfully.

Consider adding more diagnostic output on failure (e.g., subscription-manager identity or cloud-init status) to help debug the registration issue.

🔎 Suggested diagnostic enhancement
     // If we get here, registration never completed
+    // Get diagnostic info before failing
+    const [, identityOutput] = await image.exec(
+      'sudo subscription-manager identity 2>&1 || true',
+    );
+    const [, cloudInitOutput] = await image.exec(
+      'sudo cloud-init status --long 2>&1 || true',
+    );
+    // eslint-disable-next-line no-console
+    console.log('subscription-manager identity:', identityOutput);
+    // eslint-disable-next-line no-console
+    console.log('cloud-init status:', cloudInitOutput);
+
     throw new Error(
       `System did not register within ${(maxAttempts * delayMs) / 1000} seconds`,
     );

279-291: LGTM!

The verification approach is sound—checking that package URLs contain /templates/ confirms the system is using the content template rather than the base CDN. Per the PR description, this step is expected to fail in the current state until the registration issue is resolved.


293-315: LGTM!

Re-authenticating after the long build process is good defensive practice. The polling parameters (10s delay, 30 attempts = 5 min max) are reasonable for waiting on system inventory updates.

playwright/BootTests/Content/ContentNonRepeatable.boot.ts (2)

27-29: Temporary test skip is acceptable; ensure tracking for re-enablement.

The TODO comment clearly explains this is temporary while debugging ContentTemplate. The ESLint directive is appropriate to suppress the no-skipped-test rule for intentional skips.

Consider creating an issue or task to track re-enabling these tests once ContentTemplate debugging is complete.


144-146: LGTM!

The skip pattern is consistently applied to all three tests with appropriate TODO comments and ESLint directives.

Also applies to: 301-303

Comment on lines +70 to +95
export const registerWithActivationKey = async (page: Page | FrameLocator) => {
if (isHosted()) {
await page.getByRole('button', { name: 'Register' }).click();

// Ensure "Automatically register to Red Hat" is selected (should be default)
await expect(
page.getByRole('radio', { name: 'Automatically register to Red Hat' }),
).toBeChecked();

// Select an activation key from the dropdown
await page.getByRole('button', { name: 'Menu toggle' }).click();
await page.getByRole('option', { name: 'activation-key-' }).click();

// Ensure Enable predictive analytics is checked
const insightsSwitch = page.getByText('Enable predictive analytics');
if (!(await insightsSwitch.isChecked())) {
await insightsSwitch.click();
}

// Ensure Enable remote remediations is checked
const rhcSwitch = page.getByText('Enable remote remediations');
if (!(await rhcSwitch.isChecked())) {
await rhcSwitch.click();
}
}
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for activation key related patterns to understand naming and usage
rg -n "activation-key" --type ts --type js -B 2 -A 2

Repository: osbuild/image-builder-frontend

Length of output: 12242


🏁 Script executed:

#!/bin/bash
# Look for test fixtures, mock data, or configuration files that define activation keys
fd -e json -e ts -e js | xargs grep -l "activation.key\|activation-key" 2>/dev/null | head -20

Repository: osbuild/image-builder-frontend

Length of output: 678


🏁 Script executed:

#!/bin/bash
# Search for the registerWithActivationKey function and its callers to understand context
rg -n "registerWithActivationKey" --type ts -B 3 -A 3

Repository: osbuild/image-builder-frontend

Length of output: 1798


🏁 Script executed:

#!/bin/bash
# Look for other getByRole with option patterns to understand selector conventions
rg -n "getByRole.*option" --type ts -C 2

Repository: osbuild/image-builder-frontend

Length of output: 18704


Consider using a more specific selector for the activation key option.

The partial name match 'activation-key-' on line 81 will select the first option that starts with this prefix. If the dropdown contains multiple activation keys with names starting with 'activation-key-', this selector risks selecting an unintended option. Use a more specific selector (e.g., the full key name or a data-testid attribute) to ensure the correct key is reliably selected.

🤖 Prompt for AI Agents
In @playwright/helpers/wizardHelpers.ts around lines 70-95, The selector in
registerWithActivationKey that clicks the activation key option currently uses a
vague prefix ('activation-key-') and can match the wrong item; update
registerWithActivationKey to select the exact activation key by either accepting
a parameter (e.g., activationKey) and using that full name in the
getByRole('option', { name: activationKey }) call, or by targeting a stable
attribute like a data-testid on the option (e.g.,
getByTestId('activation-key-<id>')), and replace the current partial-match
selector accordingly to guarantee the intended option is clicked.

@ochosi ochosi force-pushed the content-templates-boot-test branch from e1dcdba to 700d6b6 Compare January 7, 2026 17:38
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In @playwright/BootTests/AAP/AAP.boot.ts:
- Around line 63-65: Prettier is failing on the test callback parameter
formatting in the test.skip declaration for "AAP registration boot integration
test"; change the async parameter destructuring from "async ({ page, cleanup })
=>" to remove spaces inside the braces so it reads "async ({page, cleanup}) =>"
(update the test.skip line in AAP.boot.ts accordingly) to satisfy Prettier.

In @playwright/BootTests/Compliance/ComplianceCIS.boot.ts:
- Around line 184-186: Prettier is flagging the test callback parameter
formatting for test.skip; reformat the async callback signature for the
'Compliance alerts - lint warnings display' test so it matches the repo's
Prettier rules (e.g., run the project's Prettier/formatter or adjust the
signature of test.skip(..., async ({ page, cleanup }) => { ... }) to the
expected single-line or multi-line style per config), then save and commit the
formatted change for test.skip and its async callback.
- Around line 34-36: Prettier is failing on the test callback parameter
formatting in the test.skip declaration; reformat the async callback signature
for the 'Compliance step integration test - CIS' test so it satisfies Prettier
(e.g. run Prettier or adjust the callback to a compliant style), specifically
update the test.skip callback that currently uses "async ({ page, cleanup }) =>"
to a properly formatted version (you can split the parameters onto their own
line or add explicit type annotations for { page, cleanup }) and then re-run
Prettier/CI to confirm the linting error is resolved.

In @playwright/helpers/wizardHelpers.ts:
- Around line 90-93: The code calls isChecked() on rhcSwitch which is created
with page.getByText('Enable remote remediations') (a text/label node) causing
runtime errors; change the locator to target the actual input/switch (e.g., use
page.getByLabel('Enable remote remediations') or a checkbox role locator like
page.locator('role=checkbox', { hasText: 'Enable remote remediations' }) so
rhcSwitch refers to the interactive element, then call isChecked() and click()
on that locator.
- Line 81: The selector using page.getByRole('option', { name: 'activation-key-'
}) is too vague and will pick the first option that starts with that prefix;
update the helper in wizardHelpers.ts to accept a specific activation key
parameter (e.g., activationKey) and replace the hardcoded partial name with an
explicit match (e.g., use the exact option name or build
`activation-key-${activationKey}`) when calling page.getByRole or otherwise
narrow the locator; ensure the calling sites pass the intended activation key
value so the correct option is selected.
- Around line 84-87: The locator insightsSwitch currently uses getByText('Enable
predictive analytics'), which targets a text/label and does not support
isChecked(); change the locator to target the actual control (e.g., use
getByLabel('Enable predictive analytics'), page.getByRole('switch', { name:
'Enable predictive analytics' }), or locate the input inside the label like
locator('label', { hasText: 'Enable predictive analytics'
}).locator('input[type="checkbox"]')) so that calling insightsSwitch.isChecked()
and insightsSwitch.click() operates on the real input element.
🧹 Nitpick comments (3)
playwright/BootTests/helpers/helpers.ts (1)

326-330: Consider using Playwright's built-in wait utilities.

The custom sleep function works but Playwright provides more robust waiting mechanisms that can handle race conditions better.

For polling scenarios like this, page.waitForTimeout() or expect.poll() with custom predicates might be more idiomatic. However, the current implementation is acceptable for this use case.

playwright/BootTests/Content/ContentTemplate.boot.ts (2)

186-194: FIXME: Workaround for wizard step loading requirement.

The comment indicates this step shouldn't be necessary but is required to prevent package search failures. This suggests a potential wizard state management issue in the application. Consider tracking this as a bug to fix in the underlying wizard behavior.

Would you like me to open an issue to track this wizard step loading requirement as a bug to be fixed in the application?


245-277: Consider extracting polling constants.

The polling configuration (30 attempts, 10,000ms delay) is defined inline. For consistency with pollForSystemTemplateAttachment on lines 304-310 which passes similar parameters, consider extracting these as named constants at the top of the test for clarity and easier tuning.

♻️ Suggested refactor
+const REGISTRATION_POLL_DELAY_MS = 10_000;
+const REGISTRATION_POLL_MAX_ATTEMPTS = 30;
+
 test('Content integration test - Content Template', async ({
   page,
   cleanup,
 }) => {
   // ... existing code ...
 
   await test.step('Wait for system registration to complete', async () => {
-    const maxAttempts = 30;
-    const delayMs = 10_000; // 10 seconds between attempts
+    const maxAttempts = REGISTRATION_POLL_MAX_ATTEMPTS;
+    const delayMs = REGISTRATION_POLL_DELAY_MS;
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e1dcdba and 700d6b6.

📒 Files selected for processing (8)
  • playwright/BootTests/AAP/AAP.boot.ts
  • playwright/BootTests/Compliance/ComplianceCIS.boot.ts
  • playwright/BootTests/Content/ContentNonRepeatable.boot.ts
  • playwright/BootTests/Content/ContentRepeatable.boot.ts
  • playwright/BootTests/Content/ContentTemplate.boot.ts
  • playwright/BootTests/Satellite/Satellite.boot.ts
  • playwright/BootTests/helpers/helpers.ts
  • playwright/helpers/wizardHelpers.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • playwright/BootTests/Content/ContentNonRepeatable.boot.ts
  • playwright/BootTests/Content/ContentRepeatable.boot.ts
🧰 Additional context used
🧬 Code graph analysis (2)
playwright/helpers/wizardHelpers.ts (1)
playwright/helpers/helpers.ts (1)
  • isHosted (27-29)
playwright/BootTests/helpers/helpers.ts (1)
playwright/helpers/helpers.ts (1)
  • closePopupsIfExist (31-55)
🪛 GitHub Actions: Development checks
playwright/BootTests/AAP/AAP.boot.ts

[error] 65-65: Prettier formatting check failed. Replace ·page,·cleanup· with ⏎··page,⏎··cleanup,⏎.

🪛 GitHub Check: ESLint
playwright/BootTests/helpers/helpers.ts

[warning] 429-429:
Unnecessary conditional, value is always truthy

playwright/BootTests/AAP/AAP.boot.ts

[failure] 65-65:
Replace ·page,·cleanup· with ⏎··page,⏎··cleanup,⏎

playwright/BootTests/Compliance/ComplianceCIS.boot.ts

[failure] 36-36:
Replace ·page,·cleanup· with ⏎··page,⏎··cleanup,⏎


[failure] 186-186:
Replace ·page,·cleanup· with ⏎··page,⏎··cleanup,⏎

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Sourcery review
  • GitHub Check: Red Hat Konflux / image-builder-frontend-on-pull-request
  • GitHub Check: Service Tests
🔇 Additional comments (7)
playwright/BootTests/Satellite/Satellite.boot.ts (1)

29-34: LGTM!

The test skip is properly formatted and documented with a TODO comment.

playwright/BootTests/Content/ContentTemplate.boot.ts (6)

65-87: LGTM!

Repository creation and snapshot waiting steps are well-structured with appropriate timeouts for asynchronous operations.


89-158: LGTM!

Content template creation step is thorough with proper waits for async operations and clear assertions at each wizard step.


222-243: LGTM!

Build, deployment, and package verification steps are clean and follow the expected flow.


279-291: LGTM!

Good verification approach to confirm the system is using content template URLs by checking the package download location.


293-315: LGTM with noted limitations.

Good practice to re-authenticate before API calls after long-running operations. The polling logic is appropriate.

Per the PR objectives, steps 10 and 11 (including this verification) are currently failing due to subscription/registration issues against stage environment. The implementation looks correct; the failure appears to be an environmental/configuration issue rather than a code defect.


54-58: The review comment is incorrect. The deleteRepository function explicitly supports both name and URL identifiers through a single repositoryNameOrUrl: string parameter. The function uses a 'Name/URL filter' textbox and includes a comment stating "We check for list size due to the need of deleting by the name OR url and there is no better selector for that." Both calls in the code—passing repositoryUrl on line 54 and repositoryName on line 58—are valid and intended.

Likely an incorrect or invalid review comment.


// Select an activation key from the dropdown
await page.getByRole('button', { name: 'Menu toggle' }).click();
await page.getByRole('option', { name: 'activation-key-' }).click();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Vague selector may select the wrong activation key.

The partial name match 'activation-key-' will select the first option starting with this prefix. If multiple activation keys exist in the dropdown, this risks selecting an unintended key.

💡 Suggested improvement

Consider parameterizing the function to accept the specific activation key name:

-export const registerWithActivationKey = async (page: Page | FrameLocator) => {
+export const registerWithActivationKey = async (
+  page: Page | FrameLocator,
+  activationKeyName: string = 'activation-key-',
+) => {
   if (isHosted()) {
     await page.getByRole('button', { name: 'Register' }).click();
 
     // Ensure "Automatically register to Red Hat" is selected (should be default)
     await expect(
       page.getByRole('radio', { name: 'Automatically register to Red Hat' }),
     ).toBeChecked();
 
     // Select an activation key from the dropdown
     await page.getByRole('button', { name: 'Menu toggle' }).click();
-    await page.getByRole('option', { name: 'activation-key-' }).click();
+    await page.getByRole('option', { name: activationKeyName }).click();

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @playwright/helpers/wizardHelpers.ts at line 81, The selector using
page.getByRole('option', { name: 'activation-key-' }) is too vague and will pick
the first option that starts with that prefix; update the helper in
wizardHelpers.ts to accept a specific activation key parameter (e.g.,
activationKey) and replace the hardcoded partial name with an explicit match
(e.g., use the exact option name or build `activation-key-${activationKey}`)
when calling page.getByRole or otherwise narrow the locator; ensure the calling
sites pass the intended activation key value so the correct option is selected.

Comment on lines +84 to +87
const insightsSwitch = page.getByText('Enable predictive analytics');
if (!(await insightsSwitch.isChecked())) {
await insightsSwitch.click();
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: isChecked() called on text element.

Line 85 calls isChecked() on the result of getByText('Enable predictive analytics'), which locates a text/label element, not the actual switch/checkbox input. This will either throw a runtime error or always evaluate to false.

🔧 Proposed fix
     // Ensure Enable predictive analytics is checked
-    const insightsSwitch = page.getByText('Enable predictive analytics');
+    const insightsSwitch = page.getByRole('switch', { name: 'Enable predictive analytics' });
     if (!(await insightsSwitch.isChecked())) {
       await insightsSwitch.click();
     }

If the element is not a switch role, use getByLabel('Enable predictive analytics') or locate the input within the container.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const insightsSwitch = page.getByText('Enable predictive analytics');
if (!(await insightsSwitch.isChecked())) {
await insightsSwitch.click();
}
const insightsSwitch = page.getByRole('switch', { name: 'Enable predictive analytics' });
if (!(await insightsSwitch.isChecked())) {
await insightsSwitch.click();
}
🤖 Prompt for AI Agents
In @playwright/helpers/wizardHelpers.ts around lines 84 - 87, The locator
insightsSwitch currently uses getByText('Enable predictive analytics'), which
targets a text/label and does not support isChecked(); change the locator to
target the actual control (e.g., use getByLabel('Enable predictive analytics'),
page.getByRole('switch', { name: 'Enable predictive analytics' }), or locate the
input inside the label like locator('label', { hasText: 'Enable predictive
analytics' }).locator('input[type="checkbox"]')) so that calling
insightsSwitch.isChecked() and insightsSwitch.click() operates on the real input
element.

Comment on lines +90 to +93
const rhcSwitch = page.getByText('Enable remote remediations');
if (!(await rhcSwitch.isChecked())) {
await rhcSwitch.click();
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: isChecked() called on text element.

Line 91 calls isChecked() on the result of getByText('Enable remote remediations'), which locates a text/label element, not the actual switch/checkbox input. This will either throw a runtime error or always evaluate to false.

🔧 Proposed fix
     // Ensure Enable remote remediations is checked
-    const rhcSwitch = page.getByText('Enable remote remediations');
+    const rhcSwitch = page.getByRole('switch', { name: 'Enable remote remediations' });
     if (!(await rhcSwitch.isChecked())) {
       await rhcSwitch.click();
     }

If the element is not a switch role, use getByLabel('Enable remote remediations') or locate the input within the container.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const rhcSwitch = page.getByText('Enable remote remediations');
if (!(await rhcSwitch.isChecked())) {
await rhcSwitch.click();
}
// Ensure Enable remote remediations is checked
const rhcSwitch = page.getByRole('switch', { name: 'Enable remote remediations' });
if (!(await rhcSwitch.isChecked())) {
await rhcSwitch.click();
}
🤖 Prompt for AI Agents
In @playwright/helpers/wizardHelpers.ts around lines 90 - 93, The code calls
isChecked() on rhcSwitch which is created with page.getByText('Enable remote
remediations') (a text/label node) causing runtime errors; change the locator to
target the actual input/switch (e.g., use page.getByLabel('Enable remote
remediations') or a checkbox role locator like page.locator('role=checkbox', {
hasText: 'Enable remote remediations' }) so rhcSwitch refers to the interactive
element, then call isChecked() and click() on that locator.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
playwright/BootTests/Content/ContentTemplate.boot.ts (2)

186-194: FIXME noted — consider tracking this issue.

This workaround indicates a bug where the package search fails unless the Repositories step is explicitly loaded first. Consider opening an issue to track fixing this in the application so the workaround can be removed.

Would you like me to open an issue to track this bug?


203-206: Fragile selector assumes package is always in first row.

getByRole('checkbox', { name: 'Select row 0' }) will fail if the search returns multiple packages or if the expected package isn't in the first position. Consider using a more specific selector that targets the row containing packageName.

♻️ Suggested improvement
-    await expect(
-      frame.getByRole('gridcell', { name: packageName }),
-    ).toBeVisible({ timeout: 60000 });
-    await frame.getByRole('checkbox', { name: 'Select row 0' }).click();
+    await expect(
+      frame.getByRole('gridcell', { name: packageName }),
+    ).toBeVisible({ timeout: 60000 });
+    // Select the row containing our specific package
+    const packageRow = frame.locator('tr').filter({ hasText: packageName });
+    await packageRow.getByRole('checkbox').click();
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 700d6b6 and be9f516.

📒 Files selected for processing (1)
  • playwright/BootTests/Content/ContentTemplate.boot.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (22)
  • GitHub Check: rpm-build:centos-stream-9-aarch64
  • GitHub Check: rpm-build:centos-stream-10-aarch64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: testing-farm:centos-stream-10-x86_64:self
  • GitHub Check: testing-farm:fedora-43-x86_64:self
  • GitHub Check: testing-farm:fedora-42-x86_64:self
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:centos-stream-9-aarch64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-10-aarch64
  • GitHub Check: Sourcery review
  • GitHub Check: boot-tests
  • GitHub Check: playwright-tests
  • GitHub Check: Service Tests
  • GitHub Check: Red Hat Konflux / image-builder-frontend-on-pull-request
🔇 Additional comments (6)
playwright/BootTests/Content/ContentTemplate.boot.ts (6)

1-33: LGTM on imports.

The imports are well-organized and cover all necessary helpers for the test workflow.


65-87: LGTM on repository creation.

The step correctly creates a repository and waits for the snapshot to become valid with an appropriate timeout.


89-158: LGTM on template creation workflow.

The template creation step thoroughly covers the wizard flow, including architecture/OS selection, repository inclusion, and waiting for the template to reach "Valid" status.


222-236: LGTM on build and boot steps.

The image building and OpenStack instance provisioning steps are cleanly organized using helper abstractions.


245-277: Polling logic is sound, minor suggestion on API usage.

The registration polling is implemented correctly. Consider using page.waitForTimeout(delayMs) instead of raw setTimeout for consistency with Playwright's API, though since this step runs SSH commands rather than page interactions, the current approach is acceptable.


279-301: LGTM on template attachment verification.

Good practice to re-authenticate and refresh tokens after long-running build operations. The assertion includes a descriptive failure message for debugging.

ochosi added 4 commits January 7, 2026 20:53
This test
0. logs into consoledot
1. creates a custom repo
2. creates a content template
3. creates a blueprint with the content template
4. installs a package from the content template
5. builds the image
6. downloads and uploads it to openstack
7. launches it in openstack
8. verifies the image content
9. verifies the instance is connected to the content template
@ochosi ochosi force-pushed the content-templates-boot-test branch from be9f516 to af56f50 Compare January 7, 2026 19:53
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In @playwright/BootTests/AAP/AAP.boot.ts:
- Around line 63-65: The test callback parameter list is causing Prettier
formatting issues; remove the unused "cleanup" parameter from the async test
callback to resolve it. Locate the test.skip invocation for "AAP registration
boot integration test" (the line containing test.skip('AAP registration boot
integration test', async ({ page, cleanup }) => { ) and change the parameter
object to only include { page } (or name it { page, _cleanup } if the variable
must remain referenced), then run Prettier/format to ensure CI passes.

In @playwright/BootTests/Compliance/ComplianceCIS.boot.ts:
- Around line 184-186: The test callback's parameter destructuring is causing
Prettier formatting errors; change the signature of the skipped test from using
inline destructured params to a single parameter and destructure inside the
function body to satisfy Prettier: replace the callback async ({ page, cleanup
}) => { ... } used in test.skip('Compliance alerts - lint warnings display',
...) with async (fixtures) => { const { page, cleanup } = fixtures; ... } so
formatting rules are respected while keeping the same fixtures available.
- Around line 34-36: The test declaration's callback parameter spacing violates
Prettier; update the test.skip signature for "Compliance step integration test -
CIS" to use Prettier-compliant parameter spacing by changing the async callback
parameter from "async ({ page, cleanup }) =>" to "async ({page, cleanup}) =>"
(adjust spacing inside the parentheses/braces to match project Prettier rules)
so the file formats without errors.

In @playwright/BootTests/helpers/helpers.ts:
- Around line 294-311: The deleteTemplate function currently wraps
navigateToTemplates, the search input fill, and the visibility assertion in a
single try/catch which treats any error as "template not found"; instead, only
swallow the absence-of-template check: call navigateToTemplates(page) and
perform page.getByRole('searchbox', { name: 'Filter by name'
}).fill(templateName) outside of the try/catch so navigation/fill failures
surface, then narrow the try/catch to only the existence check (e.g., inspect
page.getByRole('row').filter({ hasText: templateName }) via a non-throwing check
like locator.count()/isVisible() or catch only the assertion error from expect)
so that genuine failures still fail the test while missing templates return
gracefully.
- Around line 341-436: The function pollForSystemTemplateAttachment has weak
typing for the API shape and an unnecessary shouldRetry conditional; fix by
defining a proper TypeScript interface for the API payload (e.g., interface
System { id: string; attributes: { display_name: string; template_uuid?: string;
template_name?: string; /* other fields */ } } and use body.data as System[]),
update the system find to use that type, and remove the redundant shouldRetry
check by simplifying the retry loop (return early on success, set a local flag
or simply await sleep(delayMs) when not last attempt), eliminating the final if
(shouldRetry && ...) branch and relying on attempts < maxAttempts to control
retries.
🧹 Nitpick comments (1)
playwright/BootTests/Content/ContentTemplate.boot.ts (1)

186-194: Technical debt: Repository step workaround.

The FIXME comment indicates that loading the Repositories wizard step is required for package search to work. This suggests an initialization or timing issue in the wizard's state management.

Consider opening an issue to investigate why the Repositories step must be visited for package search to function correctly. The root cause may be related to lazy loading or state initialization in the wizard.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between be9f516 and af56f50.

📒 Files selected for processing (8)
  • playwright/BootTests/AAP/AAP.boot.ts
  • playwright/BootTests/Compliance/ComplianceCIS.boot.ts
  • playwright/BootTests/Content/ContentNonRepeatable.boot.ts
  • playwright/BootTests/Content/ContentRepeatable.boot.ts
  • playwright/BootTests/Content/ContentTemplate.boot.ts
  • playwright/BootTests/Satellite/Satellite.boot.ts
  • playwright/BootTests/helpers/helpers.ts
  • playwright/helpers/wizardHelpers.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • playwright/helpers/wizardHelpers.ts
🧰 Additional context used
🧬 Code graph analysis (1)
playwright/BootTests/Content/ContentTemplate.boot.ts (6)
playwright/helpers/helpers.ts (1)
  • isHosted (27-29)
playwright/BootTests/helpers/imageBuilding.ts (2)
  • buildImage (3-35)
  • downloadImage (37-50)
playwright/helpers/wizardHelpers.ts (4)
  • deleteBlueprint (113-145)
  • registerWithActivationKey (70-95)
  • fillInDetails (37-49)
  • createBlueprint (16-27)
playwright/BootTests/helpers/OpenStackWrapper.ts (1)
  • OpenStackWrapper (5-288)
playwright/helpers/login.ts (2)
  • ensureAuthenticated (43-69)
  • storeStorageStateAndToken (133-151)
playwright/helpers/navHelpers.ts (3)
  • navigateToLandingPage (63-69)
  • ibFrame (50-57)
  • fillInImageOutput (18-44)
🪛 GitHub Actions: Development checks
playwright/BootTests/AAP/AAP.boot.ts

[error] 65-65: prettier/prettier: Replace ·page,·cleanup· with proper line breaks. Run 'prettier --write' to fix code style issues in this file.

🪛 GitHub Check: ESLint
playwright/BootTests/helpers/helpers.ts

[warning] 429-429:
Unnecessary conditional, value is always truthy

playwright/BootTests/AAP/AAP.boot.ts

[failure] 65-65:
Replace ·page,·cleanup· with ⏎··page,⏎··cleanup,⏎

playwright/BootTests/Compliance/ComplianceCIS.boot.ts

[failure] 36-36:
Replace ·page,·cleanup· with ⏎··page,⏎··cleanup,⏎


[failure] 186-186:
Replace ·page,·cleanup· with ⏎··page,⏎··cleanup,⏎

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (22)
  • GitHub Check: rpm-build:centos-stream-9-aarch64
  • GitHub Check: rpm-build:centos-stream-10-aarch64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: testing-farm:centos-stream-10-x86_64:self
  • GitHub Check: testing-farm:fedora-43-x86_64:self
  • GitHub Check: testing-farm:fedora-42-x86_64:self
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:centos-stream-9-aarch64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-10-aarch64
  • GitHub Check: Sourcery review
  • GitHub Check: Red Hat Konflux / image-builder-frontend-on-pull-request
  • GitHub Check: Service Tests
  • GitHub Check: playwright-tests
  • GitHub Check: boot-tests
🔇 Additional comments (4)
playwright/BootTests/Content/ContentTemplate.boot.ts (2)

245-277: Registration polling is appropriate given known stage limitations.

The manual polling for subscription-manager status aligns with the PR objectives, which note that registration against stage does not complete despite images being built with activation keys. This workaround allows the test to proceed locally and on production while documenting the stage-specific issue.

The 5-minute timeout (30 attempts × 10 seconds) provides reasonable buffer time for asynchronous registration to complete.


35-301: Comprehensive and well-structured integration test.

The test effectively covers the complete content-template workflow:

  • Repository creation with snapshot validation
  • Template creation through multi-step wizard
  • Blueprint creation with template selection
  • Image build and OpenStack provisioning
  • Package installation verification
  • System registration and template attachment validation

The test includes proper cleanup, appropriate timeouts for long-running operations, and robust UI interaction patterns with retries and visibility checks.

playwright/BootTests/helpers/helpers.ts (1)

272-285: LGTM! Clean navigation helper.

The navigateToTemplates function follows the same pattern as navigateToRepositories and provides a clear way to navigate to the templates page with appropriate timeout handling.

playwright/BootTests/Content/ContentNonRepeatable.boot.ts (1)

27-29: Reminder: Re-enable skipped tests after ContentTemplate debugging.

All three non-repeatable content integration tests are temporarily disabled while debugging the ContentTemplate test. The test scaffolding remains intact, which is good for quick re-enablement.

When is the plan to re-enable these tests? Consider tracking the re-enablement with a dedicated issue to avoid these tests being forgotten.

Also applies to: 144-146, 301-303

Comment on lines +63 to +65
// TODO: Remove skip - temporarily disabled while debugging ContentTemplate test
// eslint-disable-next-line playwright/no-skipped-test
test.skip('AAP registration boot integration test', async ({ page, cleanup }) => {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix Prettier formatting to unblock CI/CD.

The test callback parameters violate Prettier rules. This issue was previously flagged and is blocking the pipeline.

🔧 Proposed fix
-test.skip('AAP registration boot integration test', async ({ page, cleanup }) => {
+test.skip('AAP registration boot integration test', async ({
+  page,
+  cleanup,
+}) => {
🧰 Tools
🪛 GitHub Actions: Development checks

[error] 65-65: prettier/prettier: Replace ·page,·cleanup· with proper line breaks. Run 'prettier --write' to fix code style issues in this file.

🪛 GitHub Check: ESLint

[failure] 65-65:
Replace ·page,·cleanup· with ⏎··page,⏎··cleanup,⏎

🤖 Prompt for AI Agents
In @playwright/BootTests/AAP/AAP.boot.ts around lines 63 - 65, The test callback
parameter list is causing Prettier formatting issues; remove the unused
"cleanup" parameter from the async test callback to resolve it. Locate the
test.skip invocation for "AAP registration boot integration test" (the line
containing test.skip('AAP registration boot integration test', async ({ page,
cleanup }) => { ) and change the parameter object to only include { page } (or
name it { page, _cleanup } if the variable must remain referenced), then run
Prettier/format to ensure CI passes.

Comment on lines +34 to +36
// TODO: Remove skip - temporarily disabled while debugging ContentTemplate test
// eslint-disable-next-line playwright/no-skipped-test
test.skip('Compliance step integration test - CIS', async ({ page, cleanup }) => {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix Prettier formatting to unblock CI/CD.

The test callback parameters violate Prettier rules. This issue was previously flagged and is blocking the pipeline.

🔧 Proposed fix
-test.skip('Compliance step integration test - CIS', async ({ page, cleanup }) => {
+test.skip('Compliance step integration test - CIS', async ({
+  page,
+  cleanup,
+}) => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// TODO: Remove skip - temporarily disabled while debugging ContentTemplate test
// eslint-disable-next-line playwright/no-skipped-test
test.skip('Compliance step integration test - CIS', async ({ page, cleanup }) => {
// TODO: Remove skip - temporarily disabled while debugging ContentTemplate test
// eslint-disable-next-line playwright/no-skipped-test
test.skip('Compliance step integration test - CIS', async ({
page,
cleanup,
}) => {
🧰 Tools
🪛 GitHub Check: ESLint

[failure] 36-36:
Replace ·page,·cleanup· with ⏎··page,⏎··cleanup,⏎

🤖 Prompt for AI Agents
In @playwright/BootTests/Compliance/ComplianceCIS.boot.ts around lines 34 - 36,
The test declaration's callback parameter spacing violates Prettier; update the
test.skip signature for "Compliance step integration test - CIS" to use
Prettier-compliant parameter spacing by changing the async callback parameter
from "async ({ page, cleanup }) =>" to "async ({page, cleanup}) =>" (adjust
spacing inside the parentheses/braces to match project Prettier rules) so the
file formats without errors.

Comment on lines +184 to +186
// TODO: Remove skip - temporarily disabled while debugging ContentTemplate test
// eslint-disable-next-line playwright/no-skipped-test
test.skip('Compliance alerts - lint warnings display', async ({ page, cleanup }) => {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix Prettier formatting to unblock CI/CD.

The test callback parameters violate Prettier rules. This issue was previously flagged and is blocking the pipeline.

🔧 Proposed fix
-test.skip('Compliance alerts - lint warnings display', async ({ page, cleanup }) => {
+test.skip('Compliance alerts - lint warnings display', async ({
+  page,
+  cleanup,
+}) => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// TODO: Remove skip - temporarily disabled while debugging ContentTemplate test
// eslint-disable-next-line playwright/no-skipped-test
test.skip('Compliance alerts - lint warnings display', async ({ page, cleanup }) => {
// TODO: Remove skip - temporarily disabled while debugging ContentTemplate test
// eslint-disable-next-line playwright/no-skipped-test
test.skip('Compliance alerts - lint warnings display', async ({
page,
cleanup,
}) => {
🧰 Tools
🪛 GitHub Check: ESLint

[failure] 186-186:
Replace ·page,·cleanup· with ⏎··page,⏎··cleanup,⏎

🤖 Prompt for AI Agents
In @playwright/BootTests/Compliance/ComplianceCIS.boot.ts around lines 184 -
186, The test callback's parameter destructuring is causing Prettier formatting
errors; change the signature of the skipped test from using inline destructured
params to a single parameter and destructure inside the function body to satisfy
Prettier: replace the callback async ({ page, cleanup }) => { ... } used in
test.skip('Compliance alerts - lint warnings display', ...) with async
(fixtures) => { const { page, cleanup } = fixtures; ... } so formatting rules
are respected while keeping the same fixtures available.

Comment on lines +294 to +311
export const deleteTemplate = async (page: Page, templateName: string) => {
await closePopupsIfExist(page);
await test.step(
'Delete the template with name: ' + templateName,
async () => {
try {
await navigateToTemplates(page);
await page
.getByRole('searchbox', { name: 'Filter by name' })
.fill(templateName);
// Wait for the template row to appear
await expect(
page.getByRole('row').filter({ hasText: templateName }),
).toBeVisible({ timeout: 10000 });
} catch {
// No template of given name was found -> fail gracefully and do not raise error
return;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Broad try/catch still swallows navigation and search errors.

The try block wraps navigation, search input, and the visibility check. Any failure (network issues, locator changes, navigation problems) will be silently treated as "template not found" and the function will return early. This was flagged in a previous review.

Consider narrowing the error handling to specifically detect when the template doesn't exist:

♻️ Proposed refactor to narrow error handling
 export const deleteTemplate = async (page: Page, templateName: string) => {
   await closePopupsIfExist(page);
   await test.step(
     'Delete the template with name: ' + templateName,
     async () => {
-      try {
-        await navigateToTemplates(page);
-        await page
-          .getByRole('searchbox', { name: 'Filter by name' })
-          .fill(templateName);
-        // Wait for the template row to appear
-        await expect(
-          page.getByRole('row').filter({ hasText: templateName }),
-        ).toBeVisible({ timeout: 10000 });
-      } catch {
-        // No template of given name was found -> fail gracefully and do not raise error
+      await navigateToTemplates(page);
+      await page
+        .getByRole('searchbox', { name: 'Filter by name' })
+        .fill(templateName);
+      
+      const templateRow = page.getByRole('row').filter({ hasText: templateName });
+      const rowCount = await templateRow.count();
+      
+      if (rowCount === 0) {
         return;
       }
 
-      await page
-        .getByRole('row')
-        .filter({ hasText: templateName })
-        .getByLabel('Kebab toggle')
-        .click();
+      await templateRow.getByLabel('Kebab toggle').click();
       await page.getByRole('menuitem', { name: 'Delete' }).click();
       await expect(page.getByText('Delete template?')).toBeVisible();
       await page.getByRole('button', { name: 'Delete' }).click();
     },
     { box: true },
   );
 };

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @playwright/BootTests/helpers/helpers.ts around lines 294 - 311, The
deleteTemplate function currently wraps navigateToTemplates, the search input
fill, and the visibility assertion in a single try/catch which treats any error
as "template not found"; instead, only swallow the absence-of-template check:
call navigateToTemplates(page) and perform page.getByRole('searchbox', { name:
'Filter by name' }).fill(templateName) outside of the try/catch so
navigation/fill failures surface, then narrow the try/catch to only the
existence check (e.g., inspect page.getByRole('row').filter({ hasText:
templateName }) via a non-throwing check like locator.count()/isVisible() or
catch only the assertion error from expect) so that genuine failures still fail
the test while missing templates return gracefully.

ochosi added 2 commits January 8, 2026 01:37
This way we can easily double-check in our boot tests if a system
successfully registered and appears in the Insights inventory.
This way we can discern if the system registered, but Patch had delays
in ingesting the system information.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @playwright/BootTests/helpers/helpers.ts:
- Around line 382-385: The found `system` object is under-typed and later
accessed for `template_uuid` and `template_name`, so update its type to include
those properties (e.g., an interface/type with attributes: { display_name:
string; template_uuid?: string; template_name?: string } or use the API response
type) and use that type in the body.data.find callback and the `system` variable
to ensure TypeScript checks those accesses; also remove the redundant
conditional around `shouldRetry` (the always-truthy check flagged by ESLint) by
simplifying the logic where `shouldRetry` is evaluated (eliminate the
unnecessary `if (shouldRetry)` branch and directly handle the retry/return
behavior consistent with the early return at line 410).
🧹 Nitpick comments (4)
playwright/BootTests/helpers/helpers.ts (1)

446-518: Consider adding type definition for consistency.

Similar to pollForSystemTemplateAttachment, the inline type { display_name?: string; fqdn?: string } at line 488 could be extracted to an interface for better maintainability. Optional, but would improve consistency across both polling functions.

♻️ Optional refactor
interface InventorySystem {
  id: string;
  display_name?: string;
  fqdn?: string;
}

// Then use:
const system = body.results.find(
  (sys: InventorySystem) =>
    sys.display_name === hostname || sys.fqdn === hostname,
);
playwright/BootTests/Content/ContentTemplate.boot.ts (3)

246-278: return inside test.step callback only exits the step, not the test.

At line 264, return; exits the test.step callback but the test continues executing subsequent steps. This appears intentional (early exit on success), but could be clearer.

Also, consider extracting this polling logic to a helper (similar to pollForSystemInInventory) to reduce duplication and improve reusability.

♻️ Consider extracting to a helper
// In helpers.ts
export const pollForSystemRegistration = async (
  image: OpenStackWrapper,
  delayMs: number = 10_000,
  maxAttempts: number = 30,
): Promise<void> => {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    const [exitCode, output] = await image.exec('sudo subscription-manager status');
    console.log(`Registration check attempt ${attempt}/${maxAttempts}: exit=${exitCode}`);
    
    if (exitCode === 0) {
      console.log('System registration complete:', output);
      return;
    }
    
    if (attempt < maxAttempts) {
      await new Promise((resolve) => setTimeout(resolve, delayMs));
    }
  }
  
  throw new Error(`System did not register within ${(maxAttempts * delayMs) / 1000} seconds`);
};

81-88: Long timeout may mask repository creation issues.

The 180-second timeout for waiting for "Valid" status is very long. Consider logging periodically or adding intermediate checks to help diagnose slow or failing repository snapshots during debugging.


187-195: FIXME comment indicates a workaround for a wizard bug.

The comment at line 187 notes this step "shouldn't be necessary." Consider filing an issue to track this wizard behavior for future resolution.

Would you like me to help create an issue to track this wizard quirk where the Repositories step must be visited for package search to work?

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between af56f50 and bf7a4c4.

📒 Files selected for processing (2)
  • playwright/BootTests/Content/ContentTemplate.boot.ts
  • playwright/BootTests/helpers/helpers.ts
🧰 Additional context used
🧬 Code graph analysis (1)
playwright/BootTests/helpers/helpers.ts (1)
playwright/helpers/helpers.ts (1)
  • closePopupsIfExist (31-55)
🪛 GitHub Check: ESLint
playwright/BootTests/helpers/helpers.ts

[warning] 429-429:
Unnecessary conditional, value is always truthy

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (22)
  • GitHub Check: rpm-build:centos-stream-9-aarch64
  • GitHub Check: rpm-build:centos-stream-10-aarch64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: testing-farm:fedora-42-x86_64:self
  • GitHub Check: testing-farm:fedora-43-x86_64:self
  • GitHub Check: testing-farm:centos-stream-10-x86_64:self
  • GitHub Check: rpm-build:centos-stream-10-aarch64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-9-aarch64
  • GitHub Check: Sourcery review
  • GitHub Check: Red Hat Konflux / image-builder-frontend-on-pull-request
  • GitHub Check: Service Tests
  • GitHub Check: playwright-tests
  • GitHub Check: boot-tests
🔇 Additional comments (5)
playwright/BootTests/helpers/helpers.ts (2)

272-285: LGTM!

Clean navigation helper with appropriate timeout for visibility check.


326-330: LGTM!

Simple sleep utility.

playwright/BootTests/Content/ContentTemplate.boot.ts (3)

1-35: LGTM!

Imports are well-organized with clear separation between Playwright test utilities, helper functions, and wrapper classes.


36-63: Good cleanup pattern with proper resource management.

The cleanup registration order is correct—resources are cleaned up in reverse order of creation.


280-297: Good practice: re-authenticating after long-running operations.

Refreshing authentication and token after the long build/boot process is a sensible approach to handle session expiration.

Comment on lines 382 to 385
const system = body.data.find(
(sys: { attributes: { display_name: string } }) =>
sys.attributes.display_name === hostname,
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Improve type safety and remove redundant conditional.

  1. Partial typing: system is typed as { attributes: { display_name: string } } but you access template_uuid (line 394) and template_name (line 397) which aren't in the type. TypeScript won't catch API shape changes.

  2. Redundant conditional at line 429: ESLint correctly flags that shouldRetry is always truthy at this point—any successful path returns early at line 410.

🔧 Suggested fixes
+interface PatchSystem {
+  attributes: {
+    display_name: string;
+    template_uuid?: string;
+    template_name?: string;
+  };
+}
+
 export const pollForSystemTemplateAttachment = async (
   // ... params
 ): Promise<boolean> => {
-        const body = await response.json();
+        const body: { data?: PatchSystem[] } = await response.json();
         // ...
           const system = body.data.find(
-            (sys: { attributes: { display_name: string } }) =>
+            (sys: PatchSystem) =>
               sys.attributes.display_name === hostname,
           );
     // Check if we should retry with delay
-    if (shouldRetry && attempts < maxAttempts) {
+    if (attempts < maxAttempts) {
       await sleep(delayMs);
     }

Also applies to: 394-394, 397-397, 429-429

🤖 Prompt for AI Agents
In @playwright/BootTests/helpers/helpers.ts around lines 382 - 385, The found
`system` object is under-typed and later accessed for `template_uuid` and
`template_name`, so update its type to include those properties (e.g., an
interface/type with attributes: { display_name: string; template_uuid?: string;
template_name?: string } or use the API response type) and use that type in the
body.data.find callback and the `system` variable to ensure TypeScript checks
those accesses; also remove the redundant conditional around `shouldRetry` (the
always-truthy check flagged by ESLint) by simplifying the logic where
`shouldRetry` is evaluated (eliminate the unnecessary `if (shouldRetry)` branch
and directly handle the retry/return behavior consistent with the early return
at line 410).

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @playwright/BootTests/helpers/helpers.ts:
- Around line 341-436: The retry check in pollForSystemTemplateAttachment is
redundant: remove the shouldRetry flag (declaration and all assignments) and
replace the final conditional that currently checks "if (shouldRetry && attempts
< maxAttempts)" with a simpler "if (attempts < maxAttempts)" so the loop sleeps
when there are remaining attempts; keep all existing return true paths and the
catch behavior, and retain the sleep(delayMs) call and attempts increment logic
unchanged.
🧹 Nitpick comments (1)
playwright/BootTests/helpers/helpers.ts (1)

446-518: LGTM with operational note.

The polling implementation is cleaner than pollForSystemTemplateAttachment (no redundant shouldRetry flag). The logic correctly checks both display_name and fqdn fields for hostname matching.


Be aware of the extended maximum wait time.

With default parameters (20s delay × 30 attempts), this function can wait up to 10 minutes. While appropriate for system registration and inventory sync scenarios, this could significantly extend test execution time if systems don't appear as expected.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bf7a4c4 and f596d13.

📒 Files selected for processing (1)
  • playwright/BootTests/helpers/helpers.ts
🧰 Additional context used
🪛 GitHub Check: ESLint
playwright/BootTests/helpers/helpers.ts

[warning] 429-429:
Unnecessary conditional, value is always truthy

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (21)
  • GitHub Check: rpm-build:centos-stream-9-aarch64
  • GitHub Check: rpm-build:centos-stream-10-aarch64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: testing-farm:fedora-42-x86_64:self
  • GitHub Check: testing-farm:fedora-43-x86_64:self
  • GitHub Check: testing-farm:centos-stream-10-x86_64:self
  • GitHub Check: rpm-build:centos-stream-10-aarch64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-9-aarch64
  • GitHub Check: Sourcery review
  • GitHub Check: Service Tests
  • GitHub Check: playwright-tests
  • GitHub Check: boot-tests
🔇 Additional comments (3)
playwright/BootTests/helpers/helpers.ts (3)

273-285: LGTM!

The navigation helper follows the established pattern and includes appropriate wait conditions.


287-324: LGTM!

The delete helper follows the established pattern with appropriate graceful failure handling when the template is not found.


326-330: LGTM!

Simple internal utility for the polling helpers.

Comment on lines 341 to 436
export const pollForSystemTemplateAttachment = async (
page: Page,
hostname: string,
expectedTemplateName?: string,
delayMs: number = 10_000,
maxAttempts: number = 30,
): Promise<boolean> => {
/* eslint-disable no-console */
let attempts = 0;

while (attempts < maxAttempts) {
attempts++;
let shouldRetry = false;

try {
// Query the systems API with search filter for the hostname
// Include Authorization header if TOKEN is available (set during login)
const headers: Record<string, string> = {};
if (process.env.TOKEN) {
headers['Authorization'] = process.env.TOKEN;
}
const response = await page.request.get(
`/api/patch/v3/systems?search=${encodeURIComponent(hostname)}&limit=100`,
{ headers },
);

if (response.status() !== 200) {
console.log(
`API request failed with status ${response.status()}, attempt ${attempts}/${maxAttempts}`,
);
shouldRetry = true;
} else {
const body = await response.json();

if (!body.data || !Array.isArray(body.data)) {
console.log(
`Invalid response format, attempt ${attempts}/${maxAttempts}`,
);
shouldRetry = true;
} else {
// Find the system with matching hostname
const system = body.data.find(
(sys: { attributes: { display_name: string } }) =>
sys.attributes.display_name === hostname,
);

if (!system) {
console.log(
`System '${hostname}' not found in inventory, attempt ${attempts}/${maxAttempts}`,
);
shouldRetry = true;
} else {
// Check if system has a template_uuid assigned
const hasTemplate = !!system.attributes?.template_uuid;

if (hasTemplate) {
const templateName = system.attributes.template_name;
if (
expectedTemplateName &&
templateName !== expectedTemplateName
) {
console.log(
`System '${hostname}' is attached to template '${templateName}' but expected '${expectedTemplateName}', attempt ${attempts}/${maxAttempts}`,
);
shouldRetry = true;
} else {
console.log(
`System '${hostname}' is attached to template: ${templateName}`,
);
return true;
}
} else {
console.log(
`System '${hostname}' is not attached to any template, attempt ${attempts}/${maxAttempts}`,
);
shouldRetry = true;
}
}
}
}
} catch (error) {
console.log(
`Error checking system template attachment: ${error}, attempt ${attempts}/${maxAttempts}`,
);
shouldRetry = true;
}

// Check if we should retry with delay
if (shouldRetry && attempts < maxAttempts) {
await sleep(delayMs);
}
}
/* eslint-enable no-console */

return false;
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Check where TOKEN is set in the test suite
rg -n "process\.env\.TOKEN" playwright/BootTests/ --type=ts -B 2 -A 2

Repository: osbuild/image-builder-frontend

Length of output: 1142


🏁 Script executed:

# Also search for TOKEN assignment in login or setup
rg -n "TOKEN\s*=" playwright/BootTests/ --type=ts

Repository: osbuild/image-builder-frontend

Length of output: 56


🏁 Script executed:

# Look at the helpers file structure to understand test setup
fd "helpers\.ts|login\." playwright/BootTests/ --type f

Repository: osbuild/image-builder-frontend

Length of output: 113


🏁 Script executed:

# Look for login function and TOKEN setup
rg -n "TOKEN" playwright/BootTests/ --type=ts -B 3 -A 3 | head -100

Repository: osbuild/image-builder-frontend

Length of output: 2552


🏁 Script executed:

# Check for .env files or environment setup
fd "\.(env|config)" playwright/BootTests/ --type f

Repository: osbuild/image-builder-frontend

Length of output: 56


🏁 Script executed:

# Look for test fixtures or setup files
fd "(setup|fixture|before)" playwright/BootTests/ --type=ts

Repository: osbuild/image-builder-frontend

Length of output: 243


🏁 Script executed:

# Check the structure of the BootTests directory
ls -la playwright/BootTests/ | head -20

Repository: osbuild/image-builder-frontend

Length of output: 520


🏁 Script executed:

# Find the storeStorageStateAndToken function
rg -n "storeStorageStateAndToken" playwright/BootTests/ --type=ts -B 2 -A 10

Repository: osbuild/image-builder-frontend

Length of output: 2293


🏁 Script executed:

# Check for ensureAuthenticated function
rg -n "ensureAuthenticated|login" playwright/BootTests/helpers/ --type=ts -B 2 -A 5

Repository: osbuild/image-builder-frontend

Length of output: 1466


🏁 Script executed:

# Now let's verify the shouldRetry logic by examining the actual code flow
cat -n playwright/BootTests/helpers/helpers.ts | sed -n '340,436p'

Repository: osbuild/image-builder-frontend

Length of output: 3883


Simplify the retry condition by removing the redundant shouldRetry check.

The conditional at line 429 is redundant. The shouldRetry variable can only be true when execution reaches that point (set in every failure path: lines 371, 379, 391, 405, 416, and 425), and reaching line 429 means the function did not return true at line 410. Simplify to just check attempts < maxAttempts.

🔧 Proposed fix
-    // Check if we should retry with delay
-    if (shouldRetry && attempts < maxAttempts) {
+    // Wait before next attempt
+    if (attempts < maxAttempts) {
       await sleep(delayMs);
     }

You can also remove the shouldRetry variable entirely (line 353 and all assignments) since it's no longer needed.


TOKEN is properly set during authentication.

The TOKEN environment variable is established via storeStorageStateAndToken(page) called after ensureAuthenticated() in the test flow. This ensures the Authorization header is populated for API requests.


Be aware of the long maximum wait time.

With default parameters (10s delay × 30 attempts), this function can wait up to 5 minutes. This is reasonable for system registration scenarios but could slow down test execution if the system never appears.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const pollForSystemTemplateAttachment = async (
page: Page,
hostname: string,
expectedTemplateName?: string,
delayMs: number = 10_000,
maxAttempts: number = 30,
): Promise<boolean> => {
/* eslint-disable no-console */
let attempts = 0;
while (attempts < maxAttempts) {
attempts++;
let shouldRetry = false;
try {
// Query the systems API with search filter for the hostname
// Include Authorization header if TOKEN is available (set during login)
const headers: Record<string, string> = {};
if (process.env.TOKEN) {
headers['Authorization'] = process.env.TOKEN;
}
const response = await page.request.get(
`/api/patch/v3/systems?search=${encodeURIComponent(hostname)}&limit=100`,
{ headers },
);
if (response.status() !== 200) {
console.log(
`API request failed with status ${response.status()}, attempt ${attempts}/${maxAttempts}`,
);
shouldRetry = true;
} else {
const body = await response.json();
if (!body.data || !Array.isArray(body.data)) {
console.log(
`Invalid response format, attempt ${attempts}/${maxAttempts}`,
);
shouldRetry = true;
} else {
// Find the system with matching hostname
const system = body.data.find(
(sys: { attributes: { display_name: string } }) =>
sys.attributes.display_name === hostname,
);
if (!system) {
console.log(
`System '${hostname}' not found in inventory, attempt ${attempts}/${maxAttempts}`,
);
shouldRetry = true;
} else {
// Check if system has a template_uuid assigned
const hasTemplate = !!system.attributes?.template_uuid;
if (hasTemplate) {
const templateName = system.attributes.template_name;
if (
expectedTemplateName &&
templateName !== expectedTemplateName
) {
console.log(
`System '${hostname}' is attached to template '${templateName}' but expected '${expectedTemplateName}', attempt ${attempts}/${maxAttempts}`,
);
shouldRetry = true;
} else {
console.log(
`System '${hostname}' is attached to template: ${templateName}`,
);
return true;
}
} else {
console.log(
`System '${hostname}' is not attached to any template, attempt ${attempts}/${maxAttempts}`,
);
shouldRetry = true;
}
}
}
}
} catch (error) {
console.log(
`Error checking system template attachment: ${error}, attempt ${attempts}/${maxAttempts}`,
);
shouldRetry = true;
}
// Check if we should retry with delay
if (shouldRetry && attempts < maxAttempts) {
await sleep(delayMs);
}
}
/* eslint-enable no-console */
return false;
};
export const pollForSystemTemplateAttachment = async (
page: Page,
hostname: string,
expectedTemplateName?: string,
delayMs: number = 10_000,
maxAttempts: number = 30,
): Promise<boolean> => {
/* eslint-disable no-console */
let attempts = 0;
while (attempts < maxAttempts) {
attempts++;
let shouldRetry = false;
try {
// Query the systems API with search filter for the hostname
// Include Authorization header if TOKEN is available (set during login)
const headers: Record<string, string> = {};
if (process.env.TOKEN) {
headers['Authorization'] = process.env.TOKEN;
}
const response = await page.request.get(
`/api/patch/v3/systems?search=${encodeURIComponent(hostname)}&limit=100`,
{ headers },
);
if (response.status() !== 200) {
console.log(
`API request failed with status ${response.status()}, attempt ${attempts}/${maxAttempts}`,
);
shouldRetry = true;
} else {
const body = await response.json();
if (!body.data || !Array.isArray(body.data)) {
console.log(
`Invalid response format, attempt ${attempts}/${maxAttempts}`,
);
shouldRetry = true;
} else {
// Find the system with matching hostname
const system = body.data.find(
(sys: { attributes: { display_name: string } }) =>
sys.attributes.display_name === hostname,
);
if (!system) {
console.log(
`System '${hostname}' not found in inventory, attempt ${attempts}/${maxAttempts}`,
);
shouldRetry = true;
} else {
// Check if system has a template_uuid assigned
const hasTemplate = !!system.attributes?.template_uuid;
if (hasTemplate) {
const templateName = system.attributes.template_name;
if (
expectedTemplateName &&
templateName !== expectedTemplateName
) {
console.log(
`System '${hostname}' is attached to template '${templateName}' but expected '${expectedTemplateName}', attempt ${attempts}/${maxAttempts}`,
);
shouldRetry = true;
} else {
console.log(
`System '${hostname}' is attached to template: ${templateName}`,
);
return true;
}
} else {
console.log(
`System '${hostname}' is not attached to any template, attempt ${attempts}/${maxAttempts}`,
);
shouldRetry = true;
}
}
}
}
} catch (error) {
console.log(
`Error checking system template attachment: ${error}, attempt ${attempts}/${maxAttempts}`,
);
shouldRetry = true;
}
// Wait before next attempt
if (attempts < maxAttempts) {
await sleep(delayMs);
}
}
/* eslint-enable no-console */
return false;
};
🧰 Tools
🪛 GitHub Check: ESLint

[warning] 429-429:
Unnecessary conditional, value is always truthy

🤖 Prompt for AI Agents
In @playwright/BootTests/helpers/helpers.ts around lines 341 - 436, The retry
check in pollForSystemTemplateAttachment is redundant: remove the shouldRetry
flag (declaration and all assignments) and replace the final conditional that
currently checks "if (shouldRetry && attempts < maxAttempts)" with a simpler "if
(attempts < maxAttempts)" so the loop sleeps when there are remaining attempts;
keep all existing return true paths and the catch behavior, and retain the
sleep(delayMs) call and attempts increment logic unchanged.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
playwright/BootTests/helpers/helpers.ts (2)

341-431: Consider type safety improvements for API responses.

The polling logic is solid and handles errors gracefully. However, the API response parsing could benefit from explicit type definitions to prevent potential runtime errors if the API contract changes.

Optional: Add type definitions for API responses

Consider defining types for the API response structure:

type SystemAttributes = {
  display_name: string;
  template_uuid?: string;
  template_name?: string;
};

type SystemData = {
  attributes: SystemAttributes;
};

type SystemsApiResponse = {
  data?: SystemData[];
};

This would make the code more maintainable and catch breaking changes earlier.

Note on static analysis warning (line 424): The condition shouldRetry && attempts < maxAttempts is flagged as having an always-truthy second part. While technically redundant given the while-loop condition, it serves as defensive programming and doesn't harm functionality.


441-508: Verify the delay difference between polling functions.

This function uses a 20-second delay (vs 10 seconds in pollForSystemTemplateAttachment), resulting in up to 10 minutes of total polling time. Please confirm this longer delay is intentional for inventory registration.

Similar to the template attachment poller, consider adding explicit type definitions for the Inventory API response structure to improve type safety and maintainability.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f596d13 and ad18050.

📒 Files selected for processing (2)
  • playwright/BootTests/Content/ContentTemplate.boot.ts
  • playwright/BootTests/helpers/helpers.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • playwright/BootTests/Content/ContentTemplate.boot.ts
🧰 Additional context used
🧬 Code graph analysis (1)
playwright/BootTests/helpers/helpers.ts (1)
playwright/helpers/helpers.ts (1)
  • closePopupsIfExist (31-55)
🪛 GitHub Check: ESLint
playwright/BootTests/helpers/helpers.ts

[warning] 424-424:
Unnecessary conditional, value is always truthy

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (22)
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:centos-stream-10-aarch64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:centos-stream-9-aarch64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: testing-farm:centos-stream-10-x86_64:self
  • GitHub Check: testing-farm:fedora-43-x86_64:self
  • GitHub Check: testing-farm:fedora-42-x86_64:self
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:centos-stream-9-aarch64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-10-aarch64
  • GitHub Check: Sourcery review
  • GitHub Check: Red Hat Konflux / image-builder-frontend-on-pull-request
  • GitHub Check: playwright-tests
  • GitHub Check: Service Tests
  • GitHub Check: boot-tests
🔇 Additional comments (3)
playwright/BootTests/helpers/helpers.ts (3)

273-285: LGTM!

The navigation helper is clean and follows the established pattern from navigateToRepositories. The simpler implementation without zero-state handling appears intentional.


287-324: LGTM!

The template deletion helper follows the established patterns from deleteCompliancePolicy and deleteRepository. The graceful error handling and proper use of test steps make this a solid addition.


326-330: LGTM!

Standard sleep utility for the polling functions.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
playwright/BootTests/helpers/helpers.ts (1)

346-447: Polling logic works correctly; minor cleanup opportunity.

The retry logic and API polling are well-implemented with appropriate logging and error handling. The function correctly handles various failure scenarios and validates template attachment.

However, as flagged by static analysis, the condition at line 440 is redundant—if execution reaches that point, shouldRetry is always true (otherwise the function would have returned early at line 421).

♻️ Optional simplification
-    // Check if we should retry with delay
-    if (shouldRetry && attempts < maxAttempts) {
+    // Wait before next attempt
+    if (attempts < maxAttempts) {
       await sleep(delayMs);
     }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ad18050 and 48fd615.

📒 Files selected for processing (1)
  • playwright/BootTests/helpers/helpers.ts
🧰 Additional context used
🪛 GitHub Check: ESLint
playwright/BootTests/helpers/helpers.ts

[warning] 440-440:
Unnecessary conditional, value is always truthy

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (15)
  • GitHub Check: testing-farm:centos-stream-10-x86_64:self
  • GitHub Check: testing-farm:fedora-43-x86_64:self
  • GitHub Check: testing-farm:fedora-42-x86_64:self
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:centos-stream-9-aarch64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-10-aarch64
  • GitHub Check: Sourcery review
  • GitHub Check: Red Hat Konflux / image-builder-frontend-on-pull-request
  • GitHub Check: Service Tests
  • GitHub Check: playwright-tests
  • GitHub Check: boot-tests
🔇 Additional comments (4)
playwright/BootTests/helpers/helpers.ts (4)

273-285: LGTM!

The navigateToTemplates function follows the established pattern of other navigation helpers in this file and appropriately waits for the page content to be visible.


287-324: LGTM!

The deleteTemplate function follows the same defensive pattern as deleteRepository and deleteCompliancePolicy, with appropriate graceful failure handling for cleanup scenarios.


326-344: LGTM!

The internal sleep and getAuthHeaders helpers are well-implemented. The JWT extraction from cookies is appropriate for authenticated API calls in test scenarios.


449-526: Verify the different default polling interval.

The implementation is solid with appropriate retry logic and error handling. However, note that this function uses a default delayMs of 20 seconds, while pollForSystemTemplateAttachment uses 10 seconds.

Please confirm whether this difference is intentional based on the Inventory API's response characteristics, or if these should be aligned for consistency.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @playwright/BootTests/Content/ContentTemplate.boot.ts:
- Around line 308-346: This commented-out block containing an alternative flow
using pollForSystemInInventory and pollForSystemTemplateAttachment (which
references hostname and templateName) should be either deleted or documented:
remove the entire commented section if it's dead code, otherwise replace it with
a short explanatory comment above the block stating why it is preserved (e.g.,
"kept for temporary debugging during template boot validation; remove after PR X
or when feature Y stabilizes") and include an expected removal timeline; ensure
no stray console.log or test.step remnants remain and keep the active
pollForSystemsAttachedToTemplate implementation as the canonical logic.

In @playwright/BootTests/helpers/helpers.ts:
- Around line 453-456: The conditional "if (shouldRetry && attempts <
maxAttempts) { await sleep(delayMs); }" is redundant because shouldRetry is
always true when reached; remove the unnecessary shouldRetry check (or eliminate
the shouldRetry variable entirely) and change the check to only "if (attempts <
maxAttempts) { await sleep(delayMs); }" so the logic relies solely on attempts
and maxAttempts; update or remove any now-unused shouldRetry
declaration/assignments in the surrounding function (e.g., where shouldRetry is
set) to avoid dead code.
🧹 Nitpick comments (5)
playwright/BootTests/Content/ContentTemplate.boot.ts (3)

23-24: Remove or uncomment the pollForSystemTemplateAttachment import.

The pollForSystemTemplateAttachment function is imported but commented out. However, this function is not used in the active test code (only in the commented section below), while pollForSystemInInventory is used in the commented block. Consider removing both commented imports since they're not used in the active code path.

🧹 Cleanup unused imports
-  // pollForSystemInInventory,
-  // pollForSystemTemplateAttachment,

186-193: FIXME: Document the root cause of the wizard step workaround.

The FIXME comment indicates that loading the Repositories step shouldn't be necessary for package search to work. This suggests a potential bug in the wizard's state management or data loading. Consider opening a separate issue to track this UI quirk so it can be fixed properly.

Do you want me to create a follow-up issue to track this wizard behavior for proper investigation?


244-276: Consider extracting polling configuration to constants.

The polling logic uses magic numbers (30 attempts, 10,000ms delay) directly in the code. Extracting these to named constants would improve readability and make it easier to tune timeouts across similar polling operations.

♻️ Extract polling configuration
+const REGISTRATION_POLL_DELAY_MS = 10_000;
+const REGISTRATION_MAX_ATTEMPTS = 30;
+
 await test.step('Wait for system registration to complete', async () => {
     // Registration happens asynchronously after boot via cloud-init/rhc
     // Poll subscription-manager status until registration completes
-    const maxAttempts = 30;
-    const delayMs = 10_000; // 10 seconds between attempts
+    const maxAttempts = REGISTRATION_MAX_ATTEMPTS;
+    const delayMs = REGISTRATION_POLL_DELAY_MS;
playwright/BootTests/helpers/helpers.ts (2)

330-330: Minor: Consider using existing async utilities.

The sleep helper is a simple Promise wrapper. While functional, Playwright provides page.waitForTimeout() as a built-in alternative. However, since this is used for API polling (not page interactions), the current approach is acceptable.


355-461: Consider refactoring polling functions to reduce duplication.

The three polling functions (pollForSystemTemplateAttachment, pollForSystemInInventory, pollForSystemsAttachedToTemplate) share very similar structure: retry loop, delay, error handling, and logging. Consider extracting a generic polling helper to reduce code duplication and improve maintainability.

♻️ Example generic polling helper
/**
 * Generic polling helper with configurable check function
 */
async function pollUntil<T>(
  checkFn: () => Promise<{ success: boolean; result?: T; shouldRetry: boolean }>,
  options: {
    delayMs: number;
    maxAttempts: number;
    operationName: string;
  }
): Promise<T | null> {
  let attempts = 0;
  
  while (attempts < options.maxAttempts) {
    attempts++;
    
    try {
      const { success, result, shouldRetry } = await checkFn();
      
      if (success && result !== undefined) {
        return result;
      }
      
      if (!shouldRetry) {
        break;
      }
    } catch (error) {
      console.log(
        `${options.operationName} failed: ${error}, attempt ${attempts}/${options.maxAttempts}`
      );
    }
    
    if (attempts < options.maxAttempts) {
      await sleep(options.delayMs);
    }
  }
  
  return null;
}

Then each specific polling function would call this helper with its specific check logic.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 48fd615 and 0f046cd.

📒 Files selected for processing (2)
  • playwright/BootTests/Content/ContentTemplate.boot.ts
  • playwright/BootTests/helpers/helpers.ts
🧰 Additional context used
🪛 GitHub Check: ESLint
playwright/BootTests/helpers/helpers.ts

[warning] 454-454:
Unnecessary conditional, value is always truthy

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: playwright-tests
  • GitHub Check: Service Tests
  • GitHub Check: boot-tests
  • GitHub Check: Sourcery review
  • GitHub Check: Red Hat Konflux / image-builder-frontend-on-pull-request
🔇 Additional comments (5)
playwright/BootTests/helpers/helpers.ts (4)

273-285: LGTM!

The navigateToTemplates function correctly navigates to the templates page and waits for the expected content to be visible.


287-324: LGTM! Graceful failure handling for missing templates.

The deleteTemplate function properly handles the case where a template doesn't exist by catching the exception and failing gracefully. This is consistent with similar deletion helpers in the file.


565-677: LGTM! Comprehensive polling with proper error handling.

The pollForSystemsAttachedToTemplate function correctly handles the multi-step API workflow (template lookup → systems query) with appropriate error handling and detailed logging for debugging.


337-344: JWT cookie extraction is consistent with the authentication flow.

The cs_jwt cookie extraction pattern matches the approach used in playwright/helpers/login.ts and is correctly formatted as a Bearer token. The function is used consistently across API calls (systems, Inventory, and other endpoints) with the same pattern. The JWT scope and permissions are managed by the backend APIs being called.

playwright/BootTests/Content/ContentTemplate.boot.ts (1)

48-49: The hardcoded repository URL (https://jlsherrill.fedorapeople.org/fake-repos/needed-errata/) is accessible and appears to be an intentional test fixture used consistently across multiple boot test files (ContentTemplate.boot.ts, ContentRepeatable.boot.ts, ContentNonRepeatable.boot.ts). The URL is hosted on stable Fedora Project infrastructure and returns HTTP 200. The tests properly clean up the repository state after execution, indicating the developers are aware of this external dependency.

Comment on lines +453 to +456
// Check if we should retry with delay
if (shouldRetry && attempts < maxAttempts) {
await sleep(delayMs);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix the unnecessary conditional flagged by ESLint.

The ESLint warning is correct: the condition shouldRetry && attempts < maxAttempts on line 454 will always have shouldRetry as truthy because it's only set to true in all the code paths that reach this point. The check is redundant since shouldRetry is never set to false before this line.

🔧 Simplify the conditional
     // Check if we should retry with delay
-    if (shouldRetry && attempts < maxAttempts) {
+    if (attempts < maxAttempts) {
       await sleep(delayMs);
     }

Or alternatively, remove the shouldRetry variable entirely if it serves no purpose:

-  let attempts = 0;
-
   while (attempts < maxAttempts) {
     attempts++;
-    let shouldRetry = false;
 
     try {
       // Query the systems API with search filter for the hostname
       // Extract auth token from cookies for API authentication
       const headers = await getAuthHeaders(page);
       // ... rest of logic
     } catch (error) {
       console.log(
         `Error checking system template attachment: ${error}, attempt ${attempts}/${maxAttempts}`,
       );
-      shouldRetry = true;
     }
 
     // Check if we should retry with delay
-    if (shouldRetry && attempts < maxAttempts) {
+    if (attempts < maxAttempts) {
       await sleep(delayMs);
     }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Check if we should retry with delay
if (shouldRetry && attempts < maxAttempts) {
await sleep(delayMs);
}
// Check if we should retry with delay
if (attempts < maxAttempts) {
await sleep(delayMs);
}
🧰 Tools
🪛 GitHub Check: ESLint

[warning] 454-454:
Unnecessary conditional, value is always truthy

🤖 Prompt for AI Agents
In @playwright/BootTests/helpers/helpers.ts around lines 453 - 456, The
conditional "if (shouldRetry && attempts < maxAttempts) { await sleep(delayMs);
}" is redundant because shouldRetry is always true when reached; remove the
unnecessary shouldRetry check (or eliminate the shouldRetry variable entirely)
and change the check to only "if (attempts < maxAttempts) { await
sleep(delayMs); }" so the logic relies solely on attempts and maxAttempts;
update or remove any now-unused shouldRetry declaration/assignments in the
surrounding function (e.g., where shouldRetry is set) to avoid dead code.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @playwright/BootTests/Content/ContentTemplate.boot.ts:
- Around line 206-210: The test passes the wrong identifier to
pollForSystemTemplateAttachment: replace the second argument blueprintName with
hostname when calling pollForSystemTemplateAttachment(page, ..., templateName,
...), since the helper signature expects hostname and queries
?search=${encodeURIComponent(hostname)}; also update the expect() failure
message to reference the hostname variable (e.g., `System '${hostname}' should
be attached to template '${templateName}'`) so the assertion message matches the
queried value.
🧹 Nitpick comments (2)
playwright/BootTests/helpers/helpers.ts (1)

454-454: Remove unnecessary conditional check.

The shouldRetry variable is always true when execution reaches this line (since the function returns early at line 435 when shouldRetry is false). The meaningful check here is only attempts < maxAttempts.

♻️ Simplify the conditional
-    if (shouldRetry && attempts < maxAttempts) {
+    if (attempts < maxAttempts) {
       await sleep(delayMs);
     }
playwright/BootTests/Content/ContentTemplate.boot.ts (1)

184-192: Document the wizard quirk workaround.

The FIXME comment notes that opening the Repositories step is necessary for package search to work. While this is acknowledged in the PR objectives, consider whether this workaround could be addressed in a follow-up or if it represents an underlying wizard bug that should be reported.

Would you like me to help create an issue to track the wizard behavior where the package search requires the repositories step to be loaded first?

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0f046cd and dffde0b.

📒 Files selected for processing (2)
  • playwright/BootTests/Content/ContentTemplate.boot.ts
  • playwright/BootTests/helpers/helpers.ts
🧰 Additional context used
🧬 Code graph analysis (1)
playwright/BootTests/helpers/helpers.ts (1)
playwright/helpers/helpers.ts (1)
  • closePopupsIfExist (31-55)
🪛 GitHub Check: ESLint
playwright/BootTests/helpers/helpers.ts

[warning] 454-454:
Unnecessary conditional, value is always truthy

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (25)
  • GitHub Check: testing-farm:centos-stream-10-x86_64:self
  • GitHub Check: testing-farm:fedora-42-x86_64:self
  • GitHub Check: testing-farm:fedora-43-x86_64:self
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:centos-stream-10-aarch64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:centos-stream-9-aarch64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: testing-farm:centos-stream-10-x86_64:self
  • GitHub Check: testing-farm:fedora-43-x86_64:self
  • GitHub Check: testing-farm:fedora-42-x86_64:self
  • GitHub Check: rpm-build:centos-stream-10-aarch64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:centos-stream-9-aarch64
  • GitHub Check: Red Hat Konflux / image-builder-frontend-on-pull-request
  • GitHub Check: Sourcery review
  • GitHub Check: Service Tests
  • GitHub Check: playwright-tests
  • GitHub Check: boot-tests
🔇 Additional comments (10)
playwright/BootTests/helpers/helpers.ts (5)

273-285: LGTM! Clean navigation helper.

The function follows the established pattern from navigateToRepositories and provides clear navigation to the templates page with appropriate visibility checks.


287-324: LGTM! Consistent deletion pattern.

The deleteTemplate function follows the same graceful failure pattern as deleteRepository and deleteCompliancePolicy, making the codebase more consistent.


326-344: LGTM! Appropriate internal helpers.

The sleep and getAuthHeaders helpers are appropriately scoped as internal (non-exported) utilities for the polling functions.


471-555: LGTM! Well-structured inventory polling.

The pollForSystemInInventory function provides comprehensive polling with proper error handling, logging, and flexible matching logic (checking both display_name and fqdn).


355-461: The polling implementation is appropriately designed for the actual use case.

The function correctly matches systems using blueprintName (which becomes the instance name in OpenStack), and the startsWith fallback provides robustness against potential display_name variations. The default polling parameters of 10 seconds delay with 30 attempts (5 minutes total) are reasonable for typical system registration times in hosted environments, comparable to or more aggressive than similar polling functions in the codebase (e.g., pollForSystemInInventory uses 20 seconds with 10 minutes total). No verification needed.

playwright/BootTests/Content/ContentTemplate.boot.ts (5)

33-60: LGTM! Proper test setup with cleanup.

The test setup properly generates unique identifiers and registers all cleanup functions to ensure resources are removed even if the test fails.


64-86: LGTM! Appropriate timeouts for repository operations.

The repository creation and snapshot validation steps use reasonable timeouts (180 seconds) for operations that depend on external snapshot creation.


88-156: LGTM! Comprehensive template creation workflow.

The multi-step template creation wizard is well-structured with appropriate waits and visibility checks at each step. The 180-second timeout for template validation aligns with the repository snapshot timeout.


243-275: Known registration failure on stage environment.

According to the PR objectives, this registration check (and the subsequent verification steps) fails on stage because "subscription/registration of the image against stage does not complete despite the image being built with an activation key." While the polling logic appears correct, the test may need to be adjusted or the underlying registration issue resolved before this can reliably pass.

Consider whether this test should:

  1. Be marked as .skip() on stage until the registration issue is resolved
  2. Have adjusted timeouts/parameters for the stage environment
  3. Have the registration steps made optional until the infrastructure issue is fixed

278-287: LGTM! Proper session refresh before API calls.

Re-authenticating before polling the Inventory API is a good practice, as the session may have expired during the long-running build and provisioning steps.

Comment on lines +206 to +210
await test.step('Set hostname for system identification', async () => {
await frame.getByRole('button', { name: 'Hostname' }).click();
await frame.getByRole('textbox', { name: 'hostname input' }).fill(hostname);
await frame.getByRole('button', { name: 'Review and finish' }).click();
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

rg -n "hostname|blueprintName" playwright/BootTests/Content/ContentTemplate.boot.ts -C 2

Repository: osbuild/image-builder-frontend

Length of output: 2349


🏁 Script executed:

rg -n "pollForSystemTemplateAttachment" playwright/BootTests/Content/ -A 10

Repository: osbuild/image-builder-frontend

Length of output: 1941


🏁 Script executed:

rg -n "function pollForSystemTemplateAttachment|const pollForSystemTemplateAttachment|export.*pollForSystemTemplateAttachment" playwright/BootTests/helpers/ -A 15

Repository: osbuild/image-builder-frontend

Length of output: 1193


🏁 Script executed:

rg -n "pollForSystemTemplateAttachment" playwright/BootTests/helpers/helpers.ts -A 25 | head -50

Repository: osbuild/image-builder-frontend

Length of output: 1028


Fix incorrect parameter passed to pollForSystemTemplateAttachment.

Line 295 passes blueprintName but the function expects hostname. The function signature (helpers.ts:357) declares the second parameter as hostname: string and queries the API with ?search=${encodeURIComponent(hostname)}. Since blueprintName and hostname are distinct variables with different values (lines 42 and 46), passing blueprintName will cause the template attachment check to query the wrong system identifier.

Change line 295 to pass hostname instead of blueprintName, and update the error message at line 302 accordingly.

Affected code
    const isAttached = await pollForSystemTemplateAttachment(
      page,
      blueprintName,  // Should be: hostname
      templateName,
      10_000,
      30,
    );
    expect(
      isAttached,
      `System '${blueprintName}' should be attached to template '${templateName}'`,  // Should reference: hostname
    ).toBe(true);
🤖 Prompt for AI Agents
In @playwright/BootTests/Content/ContentTemplate.boot.ts around lines 206 - 210,
The test passes the wrong identifier to pollForSystemTemplateAttachment: replace
the second argument blueprintName with hostname when calling
pollForSystemTemplateAttachment(page, ..., templateName, ...), since the helper
signature expects hostname and queries ?search=${encodeURIComponent(hostname)};
also update the expect() failure message to reference the hostname variable
(e.g., `System '${hostname}' should be attached to template '${templateName}'`)
so the assertion message matches the queried value.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant