Skip to content

Migrated tiers browser tests to root e2e framework#26980

Merged
9larsons merged 2 commits intomainfrom
migrate-tiers-browser-test-to-e2e
Mar 26, 2026
Merged

Migrated tiers browser tests to root e2e framework#26980
9larsons merged 2 commits intomainfrom
migrate-tiers-browser-test-to-e2e

Conversation

@9larsons
Copy link
Copy Markdown
Contributor

Summary

  • Migrated 3 valuable tier tests (create, update with portal verification, archive/unarchive) from ghost/core/test/e2e-browser/ to e2e/tests/admin/settings/tiers.test.ts
  • Added TiersSection page object with reusable tier CRUD methods (create, edit, archive, unarchive, enable in portal)
  • Dropped 2 redundant tests (default prices smoke check, tier+offer creation already covered by portal-offers.test.ts)

Test plan

  • yarn lint passes (0 errors)
  • yarn test:types passes
  • yarn test tests/admin/settings/tiers.test.ts — all 3 tests pass

Migrated 3 valuable tests (create tier, update tier with portal
verification, archive/unarchive) from ghost/core browser tests to e2e/.
Dropped 2 redundant tests (default prices smoke, tier+offer creation
already covered by portal-offers). Added TiersSection page object for
reusable tier CRUD interactions.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 26, 2026

Walkthrough

Adds a new Playwright page helper module for Ghost Admin Tiers (TiersSection and TierFormData) with methods to create, edit, archive/unarchive tiers and enable tiers in portal settings. Integrates TiersSection into SettingsPage. Adds a new E2E test suite for admin tiers covering create/edit/archive/unarchive and portal visibility. Removes an existing legacy tiers test file. Also updates PortalSection (tier checkbox helper, closeCustomizeModal) and SignUpPage (paid tier locators and amount/description helpers).

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main objective: migrating tier browser tests from the legacy framework to the root e2e framework.
Description check ✅ Passed The description is directly related to the changeset, detailing the migration of tier tests, the addition of the TiersSection page object, and test removals.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch migrate-tiers-browser-test-to-e2e

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
Copy Markdown
Contributor

@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: 4

🧹 Nitpick comments (1)
e2e/tests/admin/settings/tiers.test.ts (1)

80-121: Split archive and unarchive into separate tests for single-scenario isolation.

This test validates archive behavior, portal absence, unarchive behavior, and portal reappearance in one flow. Splitting improves failure diagnosis and aligns with scenario boundaries.

As per coding guidelines, "Test names should follow ... with one test = one scenario (never mix multiple scenarios)" and "Do not include multiple scenarios in one test".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/tests/admin/settings/tiers.test.ts` around lines 80 - 121, Split the
combined "archive and unarchive" test into two independent tests: one that
verifies archiving moves a newly created tier from active to archived and
removes it from portal settings, and a second that verifies unarchiving moves a
tier from archived back to active and restores it in portal settings. For each
test, create its own tier via SettingsPage.tiersSection.createTier, then call
SettingsPage.tiersSection.archiveTier(tierSlug) in the archive test and assert
visibility changes and portal absence using
SettingsPage.portalSection.openCustomizeModal and portalModal.getByLabel; in the
unarchive test first archive the tier (or create it in archived state), then
call SettingsPage.tiersSection.unarchiveTier(tierSlug), assert it appears in
activeTab and in portal settings, and ensure each test is self-contained (no
shared state) and uses the existing helpers (tierCard, activeTab, archivedTab,
portalModal) referenced in the diff.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@e2e/helpers/pages/admin/settings/sections/tiers-section.ts`:
- Around line 91-95: In enableTierInPortal, add a visibility guard using
waitFor() on the portal modal before interacting: after clicking the 'Customize'
button, call portalModal.waitFor({ state: 'visible' }) (or the project's waitFor
helper) to ensure the portalModal (getByTestId('portal-modal')) is visible
before subsequent operations; this replaces any expect() usage in the page
object and prevents flaky interactions.
- Around line 58-69: The editTier method currently uses truthy checks which
prevent clearing fields with empty strings; update editTier (and its calls to
tierDetailModal.getByLabel for labels "Name", "Description", "Monthly price",
"Yearly price") to check for undefined instead of truthiness (e.g., use
data.name !== undefined or Object.prototype.hasOwnProperty.call(data, 'name'))
so empty string values are applied to the form, ensuring TierFormData fields can
be cleared as well as set.

In `@e2e/tests/admin/settings/tiers.test.ts`:
- Around line 17-18: Replace ad-hoc tier test data (e.g., the tierName variable
built with `Test Tier ${Date.now()}` in tiers.test.ts and similar inline
Date.now usages at the other spots) with the standardized factory calls from the
data-factory module; locate the places that create test tier inputs (the
`tierName` initialization and the other inline data at the cited locations) and
call the appropriate factory function (e.g., the tier factory or a
TestDataFactory.createTier-style API) to produce unique, deterministic test
data, then use the factory output in the test assertions and API calls.
- Around line 66-67: The test uses raw selector strings in
page.frameLocator('[data-testid="portal-popup-frame"]') and
portalFrame.locator('[data-test-tier="paid"]').filter({hasText: updatedName}) to
find the portal frame and tier card; replace these with semantic/page-object
locator helpers (e.g. a PortalPopup.getFrameLocator() or
PortalPopup.locatorForTier('paid', updatedName) and a
TiersPage.getPaidTierByName(updatedName)) and update the test to call those
methods instead of using raw CSS/test-id strings so the test follows the E2E
locator policy; ensure the new helpers use the approved semantic/data-testid
locators internally and return the same locator types used by the test.

---

Nitpick comments:
In `@e2e/tests/admin/settings/tiers.test.ts`:
- Around line 80-121: Split the combined "archive and unarchive" test into two
independent tests: one that verifies archiving moves a newly created tier from
active to archived and removes it from portal settings, and a second that
verifies unarchiving moves a tier from archived back to active and restores it
in portal settings. For each test, create its own tier via
SettingsPage.tiersSection.createTier, then call
SettingsPage.tiersSection.archiveTier(tierSlug) in the archive test and assert
visibility changes and portal absence using
SettingsPage.portalSection.openCustomizeModal and portalModal.getByLabel; in the
unarchive test first archive the tier (or create it in archived state), then
call SettingsPage.tiersSection.unarchiveTier(tierSlug), assert it appears in
activeTab and in portal settings, and ensure each test is self-contained (no
shared state) and uses the existing helpers (tierCard, activeTab, archivedTab,
portalModal) referenced in the diff.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a53ae8f5-85a5-4e71-85c5-541bf8ba17ad

📥 Commits

Reviewing files that changed from the base of the PR and between 9a5f867 and 10802f7.

📒 Files selected for processing (5)
  • e2e/helpers/pages/admin/settings/sections/index.ts
  • e2e/helpers/pages/admin/settings/sections/tiers-section.ts
  • e2e/helpers/pages/admin/settings/settings-page.ts
  • e2e/tests/admin/settings/tiers.test.ts
  • ghost/core/test/e2e-browser/admin/tiers.spec.js
💤 Files with no reviewable changes (1)
  • ghost/core/test/e2e-browser/admin/tiers.spec.js

@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown
Contributor

@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.

♻️ Duplicate comments (1)
e2e/helpers/pages/admin/settings/sections/tiers-section.ts (1)

58-69: ⚠️ Potential issue | 🟡 Minor

Allow clearing fields in editTier.

Line 59/62/65/68 use truthy checks, so '' cannot be applied to clear existing values. Use !== undefined checks instead.

Proposed fix
     async editTier(data: Partial<TierFormData>): Promise<void> {
-        if (data.name) {
+        if (data.name !== undefined) {
             await this.tierDetailModal.getByLabel('Name').fill(data.name);
         }
-        if (data.description) {
+        if (data.description !== undefined) {
             await this.tierDetailModal.getByLabel('Description').fill(data.description);
         }
-        if (data.monthlyPrice) {
+        if (data.monthlyPrice !== undefined) {
             await this.tierDetailModal.getByLabel('Monthly price').fill(data.monthlyPrice);
         }
-        if (data.yearlyPrice) {
+        if (data.yearlyPrice !== undefined) {
             await this.tierDetailModal.getByLabel('Yearly price').fill(data.yearlyPrice);
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/helpers/pages/admin/settings/sections/tiers-section.ts` around lines 58 -
69, The editTier method uses truthy checks so passing empty strings cannot clear
fields; update the four conditional checks in editTier (for name, description,
monthlyPrice, yearlyPrice) to test against !== undefined instead of truthiness,
and then call tierDetailModal.getByLabel(...).fill(...) when the value is !==
undefined so empty strings will overwrite existing values.
🧹 Nitpick comments (1)
e2e/helpers/pages/admin/settings/sections/tiers-section.ts (1)

91-114: Reuse PortalSection helpers for portal modal flow.

enableTierInPortal duplicates open/close/locator flow already encapsulated in PortalSection. Centralizing this reduces drift and keeps modal guards consistent.

Refactor sketch
+import {PortalSection} from './portal-section';
...
     async enableTierInPortal(tierName: string): Promise<void> {
-        const portalSection = this.page.getByTestId('portal');
-        await portalSection.getByRole('button', {name: 'Customize'}).click();
-        const portalModal = this.page.getByTestId('portal-modal');
-        await portalModal.waitFor({state: 'visible'});
+        const portalSection = new PortalSection(this.page);
+        await portalSection.openCustomizeModal();
+        const portalModal = portalSection.portalModal;

-        const tierCheckbox = portalModal.getByLabel(tierName).first();
+        const tierCheckbox = portalSection.tierCheckbox(tierName);
         if (!await tierCheckbox.isChecked()) {
             await tierCheckbox.check();
         }
...
         await portalModal.getByRole('button', {name: 'Save'}).click();
-        await portalModal.getByRole('button', {name: 'Close'}).click();
+        await portalSection.closeCustomizeModal();
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/helpers/pages/admin/settings/sections/tiers-section.ts` around lines 91 -
114, The enableTierInPortal method duplicates portal open/close/locator logic
that already lives in PortalSection; refactor enableTierInPortal to reuse
PortalSection's helpers by replacing the manual open (getByTestId('portal') +
Customize click + waitFor visible) and close (Save/Close clicks) and checkbox
locators with PortalSection methods (e.g., openModal(), closeModal(), and
helpers for toggling checkboxes) so the modal guard/locator behaviour is
centralized and maintained by PortalSection; update enableTierInPortal to call
PortalSection.openModal(), use
PortalSection.toggleTier(tierName)/toggleBillingOption('Monthly'|'Yearly') or
equivalent helpers, then call PortalSection.saveAndClose() (or the matching
helper names) to remove duplicated modal flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@e2e/helpers/pages/admin/settings/sections/tiers-section.ts`:
- Around line 58-69: The editTier method uses truthy checks so passing empty
strings cannot clear fields; update the four conditional checks in editTier (for
name, description, monthlyPrice, yearlyPrice) to test against !== undefined
instead of truthiness, and then call tierDetailModal.getByLabel(...).fill(...)
when the value is !== undefined so empty strings will overwrite existing values.

---

Nitpick comments:
In `@e2e/helpers/pages/admin/settings/sections/tiers-section.ts`:
- Around line 91-114: The enableTierInPortal method duplicates portal
open/close/locator logic that already lives in PortalSection; refactor
enableTierInPortal to reuse PortalSection's helpers by replacing the manual open
(getByTestId('portal') + Customize click + waitFor visible) and close
(Save/Close clicks) and checkbox locators with PortalSection methods (e.g.,
openModal(), closeModal(), and helpers for toggling checkboxes) so the modal
guard/locator behaviour is centralized and maintained by PortalSection; update
enableTierInPortal to call PortalSection.openModal(), use
PortalSection.toggleTier(tierName)/toggleBillingOption('Monthly'|'Yearly') or
equivalent helpers, then call PortalSection.saveAndClose() (or the matching
helper names) to remove duplicated modal flow.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ce1a252b-fef9-4a51-a625-1f508ed0b755

📥 Commits

Reviewing files that changed from the base of the PR and between 10802f7 and c41aa79.

📒 Files selected for processing (4)
  • e2e/helpers/pages/admin/settings/sections/portal-section.ts
  • e2e/helpers/pages/admin/settings/sections/tiers-section.ts
  • e2e/helpers/pages/portal/sign-up-page.ts
  • e2e/tests/admin/settings/tiers.test.ts
✅ Files skipped from review due to trivial changes (1)
  • e2e/tests/admin/settings/tiers.test.ts

@9larsons 9larsons merged commit 16a5f1d into main Mar 26, 2026
34 checks passed
@9larsons 9larsons deleted the migrate-tiers-browser-test-to-e2e branch March 26, 2026 13:53
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