Skip to content

test(e2e): add full Paddle E2E coverage to webbilling-demo (WST-564)#925

Merged
rogersole merged 25 commits into
mainfrom
roger/wst-715-paddle-e2e-variants
Jun 12, 2026
Merged

test(e2e): add full Paddle E2E coverage to webbilling-demo (WST-564)#925
rogersole merged 25 commits into
mainfrom
roger/wst-715-paddle-e2e-variants

Conversation

@rogersole

@rogersole rogersole commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Full Paddle E2E coverage for examples/webbilling-demo (WST-564). This PR now targets main and contains the entire stack; #916, #917, #918, #919 and #920 remain open as individually reviewable slices of the same commits — merging this one merges everything (close the slices afterwards). Supersedes #820 (credit @nicfix for the original groundwork).

Test suite (src/tests/paddle/, 11 tests)

Test Modes Notes
Config guard Fails loudly on CI if VITE_RC_PADDLE_E2E_API_KEY is missing instead of silently skipping (skips locally)
Happy-path purchase inline + overlay Real sandbox purchases; inline also asserts sandbox banner, RC order summary, processing state; both end on the entitlement-gated success page
Email as query param inline + overlay Paddle omits the email field entirely when customerEmail is provided (verified against the sandbox; mode-independent since the email travels via /checkout/start)
RC Paywall purchase inline + overlay V2 paywall → package selection → Continue → checkout → success; gated by ALLOW_PAYWALLS_TESTS
Cancel/close inline + overlay Inline via the RC return button; overlay via Paddle's own 'Return to {seller}' control; no payment completion needed
Error path mode-agnostic checkout/start returning paddle_billing_params: null → error screen

Key infrastructure

  • forcePaddleCheckoutMode(page, "inline" | "overlay"): rewrites paddle_billing_params.inline_checkout_enabled in the real /checkout/start response, so one E2E project deterministically covers both presentation modes regardless of the backend per-project flag (WST-700).
  • completePaddleCheckoutForm(frame, ...): mode-agnostic form fill using Paddle's own test ids, Andorra to skip the postcode field, and a submit re-click loop (Paddle silently swallows submits that land while the async email authentication is in flight).
  • Frame locators verified against the live sandbox: inline scoped to the RC container; overlay via iframe.paddle-frame, iframe[name='paddle_frame'].
  • Harness: the /v1/events stub now only intercepts RevenueCat's endpoint (Paddle has its own); userId/email fixtures test-scoped.
  • Env plumbing + docs: VITE_RC_PADDLE_E2E_API_KEY, VITE_SKIP_PADDLE_TESTS, CircleCI exports, README setup section.

CI

  • Chromium-only on CI (like the Stripe Checkout suite); firefox/webkit run locally.
  • Happy paths run fully on CI — the WST-709 spike ([DO NOT MERGE] spike(e2e): validate Paddle CI purchase completion (WST-709) #923) confirmed Paddle sandbox completes purchases from CircleCI IPs (no Stripe-style CAPTCHA gating).
  • Requires E2E_RC_PADDLE_E2E_API_KEY in CircleCI (configured) and paddle_e2e_test offering with entitlement + V2 paywall (configured).

Test plan

🤖 Generated with Claude Code


Note

Low Risk
Changes are confined to demo E2E tests, docs, and CircleCI env exports; no production SDK or billing logic is modified.

Overview
Adds full Paddle E2E coverage to examples/webbilling-demo, parallel to the existing Stripe Checkout suite.

New Playwright specs under src/tests/paddle/ cover inline and overlay checkout (happy path, email via query param, RC Paywall, cancel/close, and checkout/start missing paddle_billing_params). forcePaddleCheckoutMode rewrites inline_checkout_enabled on the real /checkout/start response so both modes run from one E2E project. Shared helpers handle Paddle iframe locators, sandbox card fill (Andorra, submit retry loop), and navigation with VITE_RC_PADDLE_E2E_API_KEY.

Harness updates: SKIP_PADDLE_TESTS / skipPaddleTestsIfDisabled, CI guard so a missing Paddle key fails on CI instead of silent skip, userId/email fixtures scoped per test, and /v1/events stubbing limited to RevenueCat hosts so Paddle traffic is not blocked. CircleCI, .env.example, and README document VITE_RC_PADDLE_E2E_API_KEY and VITE_SKIP_PADDLE_TESTS; Paddle runs Chromium-only on CI.

Reviewed by Cursor Bugbot for commit 521c28e. Bugbot is set up for automated code reviews on this repo. Configure here.

rogersole and others added 4 commits June 10, 2026 15:27
…t-scoped (WST-710)

Extracted from #820 (credit: @nicfix). The /v1/events stub previously
fulfilled any provider's /v1/events endpoint; Paddle exposes its own
events-like endpoints that must go through. Also makes the userId/email
fixtures explicitly test-scoped ahead of the Paddle E2E suite.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds VITE_RC_PADDLE_E2E_API_KEY / VITE_SKIP_PADDLE_TESTS wiring
(fixtures, skip helper, .env.example, README, CircleCI exports).
No tests reference the key yet; the config guard lands with the
Paddle suite so CI cannot fail before the CircleCI secret exists.

Infra lifted from #820 (credit: @nicfix).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread examples/webbilling-demo/src/tests/paddle/test-helpers.ts Outdated
Comment thread examples/webbilling-demo/src/tests/paddle/purchase-flow.test.ts Outdated
@rogersole rogersole force-pushed the roger/wst-715-paddle-e2e-variants branch from 685cb24 to 4a44191 Compare June 12, 2026 12:54
rogersole added a commit that referenced this pull request Jun 12, 2026
Addresses review feedback on #925: parameterize the email-as-query-param
test over inline and overlay, empirically confirming Paddle omits the
email field in both modes when customerEmail is provided (the email
travels via /checkout/start, before any presentation choice). Helper
comment expanded to capture that.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
rogersole and others added 7 commits June 12, 2026 15:01
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…(WST-712)

- navigateToPaddleLandingUrl + forcePaddleCheckoutMode: forces inline or
  overlay presentation by rewriting inline_checkout_enabled in the real
  /checkout/start response, so one E2E project covers both modes
  deterministically regardless of the backend per-project flag.
- Mode-aware frame locators (inline container scoped; overlay via
  paddle.com iframe src) and completePaddleCheckoutForm parameterized by
  FrameLocator so it serves both modes (field locators from #820,
  credit: @nicfix).
- config-guard test: fails loudly on CI if VITE_RC_PADDLE_E2E_API_KEY is
  missing instead of letting the suite silently skip.
- First test: error screen when checkout/start returns missing paddle
  billing params (mode-agnostic, CI-safe).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…T-712)

Replaces the role-based field guesses with Paddle's own test ids
(cardNumberInput, authenticationEmailInput, cardPaymentFormSubmitButton)
and the fill order proven in rc-billing-checkout/src/e2e (country first,
Andorra to avoid the postcode field; wait for logoutLinkTextCTA before
submitting). Pins the overlay frame selector to
iframe.paddle-frame/iframe[name='paddle_frame'].

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…n (WST-712)

Verified against the real Paddle E2E project: this checkout has no
logoutLinkTextCTA and renders the submit as 'Subscribe now', and the
error screen shows 'An unknown error occurred' rather than the Stripe
flow's 'Purchase not started' copy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Paddle authenticates the filled email asynchronously and silently
swallows submit clicks that land mid-authentication (observed against
the sandbox). Blur the email field and re-click submit until the form
transitions away.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@rogersole rogersole force-pushed the roger/wst-714-paddle-e2e-happy-paths branch from 3f3e271 to b6dca4b Compare June 12, 2026 13:02
rogersole added a commit that referenced this pull request Jun 12, 2026
Addresses review feedback on #925: parameterize the email-as-query-param
test over inline and overlay, empirically confirming Paddle omits the
email field in both modes when customerEmail is provided (the email
travels via /checkout/start, before any presentation choice). Helper
comment expanded to capture that.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@rogersole rogersole force-pushed the roger/wst-715-paddle-e2e-variants branch from f27a944 to 73b75ad Compare June 12, 2026 13:02
rogersole and others added 9 commits June 12, 2026 15:02
…ST-713)

- Inline: open forced-inline checkout, click the RC return button
  (data-testid=paddle-return-button), assert the checkout unmounts and
  the demo stays on the paywall (no /success navigation).
- Overlay: open forced-overlay checkout, close via Paddle's own control
  (fires a user-initiated checkout.closed), assert the overlay iframe
  is gone and the paywall is interactive again.

Neither test completes a payment, so both always run on CI regardless
of the datacenter-IP completion spike (WST-709).

Overlay iframe/close-control selectors carry TODO(WST-713) markers to
be pinned during headed verification against the sandbox.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…T-713)

Paddle's overlay close affordance is a 'Return to {seller}' button
inside the overlay frame, not an X/close control.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…T-714)

- Inline: sandbox banner + RC order summary visible (presence only —
  exact totals depend on checkout.updated timing), form completion in
  the container-scoped frame, processing-or-success race, success page,
  Continue -> /success/.
- Overlay: form completion in Paddle's overlay frame, success page.

Both tests skip on CI until the WST-709 spike validates that Paddle
sandbox completes purchases from datacenter IPs (Stripe does not),
mirroring the Stripe suite's original LOCAL_ONLY_COMPLETION gating.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…xists (WST-714)

The Paddle E2E project's products grant no entitlement yet, so the
demo's entitlement-gated success page cannot render its content (it
currently crashes on a null offering in WithEntitlement). Reaching
/success/ already proves the purchase completed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… exists (WST-714)

The paddle_e2e_test offering's products now grant an entitlement, so
the success page renders its content like in the Stripe suite.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The WST-709 spike (#923) confirmed Paddle sandbox completes purchases
from CircleCI IPs — both happy paths passed on CI — so the
local-only gating the Stripe suite needs is unnecessary here.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Addresses review feedback on #920: fold the near-identical inline and
overlay happy-path tests into a single forEach over the two modes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Email as query parameter (inline): with customerEmail provided,
  Paddle omits the email field entirely (verified against the
  sandbox) — helper updated to treat the absent field as expected.
- RC Paywall + Paddle (overlay): gated by ALLOW_PAYWALLS_TESTS;
  requires an RC Paywall configured on the paddle_e2e_test offering.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-715)

The paddle_e2e_test offering now has a V2 paywall: duration-labeled
package buttons and a Continue CTA. The success page's Continue is
scoped to the SDK footer since the paywall's own Continue CTA stays
mounted behind the checkout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
rogersole and others added 2 commits June 12, 2026 15:03
Parameterizes the RC Paywall purchase over inline and overlay, since
inline mounts the RC checkout UI over the paywall and deserves its own
coverage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Addresses review feedback on #925: parameterize the email-as-query-param
test over inline and overlay, empirically confirming Paddle omits the
email field in both modes when customerEmail is provided (the email
travels via /checkout/start, before any presentation choice). Helper
comment expanded to capture that.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@rogersole rogersole force-pushed the roger/wst-714-paddle-e2e-happy-paths branch from b6dca4b to ad6c5c9 Compare June 12, 2026 13:03
@rogersole rogersole force-pushed the roger/wst-715-paddle-e2e-variants branch from 73b75ad to 287cbb1 Compare June 12, 2026 13:03
@rogersole rogersole changed the base branch from roger/wst-714-paddle-e2e-happy-paths to main June 12, 2026 13:10
@rogersole rogersole changed the title test(e2e): add Paddle email-prefill and RC Paywall variants (WST-715) test(e2e): add full Paddle E2E coverage to webbilling-demo (WST-564) Jun 12, 2026
Comment thread examples/webbilling-demo/src/tests/paddle/cancel-flow.test.ts Outdated
Comment thread examples/webbilling-demo/src/tests/paddle/cancel-flow.test.ts Outdated
Comment thread examples/webbilling-demo/src/tests/paddle/purchase-flow.test.ts Outdated

@vicfergar vicfergar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM overall, but I left a few suggestions.

It might also be worth checking why the number of skipped tests increased:

# In main:
  64 skipped
  162 passed

# In this branch:
  82 skipped (+18)
  176 passed (+14)

Addresses review feedback on #925: fold the two cancel tests into a
forEach over the modes, and remove the 1s afterEach sleeps — Paddle
showed no rate limiting across repeated back-to-back full-suite runs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@rogersole

rogersole commented Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

Re: the skipped-test increase — pulled the actual CI outputs from both runs and decoded them (test-e2e #14937 on this branch vs test-e2e #14894 on main):

main (#14894) this branch (#14937) Δ
entries 228 (76 tests × 3 browsers) 258 (86 × 3) +30
passed 164 176 +12
skipped 64 82 +18

The +30 entries are the new Paddle suite: 10 tests × 3 browsers. Expected split is +20 skipped (firefox + webkit skip the whole suite on CI — same chromium-only policy as the existing stripe-checkout suite; all browsers run locally) and +10 passed (chromium runs everything: config guard, error path, 2 cancels, and 6 real sandbox purchases across inline/overlay). The observed +18/+12 differs from that by exactly 2 tests, which is run-to-run noise — both runs had 2 flaky retries, and the Stripe Checkout completion tests skip nondeterministically when Stripe presents a CAPTCHA (SKIP_STRIPE_TESTS_ON_CAPTCHA).

The ~64-skip baseline is identical in both runs and fully accounted for:

  • 33 — tax-calculation "real" variants (11 tests): always skipped on firefox/webkit (22), and snoozed on chromium via VITE_SKIP_TAX_REAL_TESTS_UNTIL=2026-08-30 (11) — visible in both run logs.
  • 18 — Stripe Checkout suite (9 tests) on firefox/webkit (pre-existing chromium-only policy).
  • ~13 — chromium Stripe CAPTCHA skips (2 in both runs) plus the handful of pre-existing conditional in-test skips, ±the flaky-retry noise.

Bottom line: zero unexpected skips, no Paddle test ever skips on chromium, and the baseline skip behavior is byte-for-byte the same as main.

Comment thread examples/webbilling-demo/src/tests/helpers/integration-test.ts
The previous filter only matched revenuecat.com, but production SDK
builds send analytics to e.revenue.cat (and dev/test builds to
localhost), so RevenueCat's own events were no longer being stubbed.
Match by hostname against revenue.cat / revenuecat.com / localhost,
still letting other providers' /v1/events-like endpoints through.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@rogersole

Copy link
Copy Markdown
Contributor Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit eafccba. Configure here.

@rogersole rogersole merged commit 7faad8f into main Jun 12, 2026
7 checks passed
@rogersole rogersole deleted the roger/wst-715-paddle-e2e-variants branch June 12, 2026 14:41
RCGitBot added a commit that referenced this pull request Jun 16, 2026
**This is an automatic release.**

## RevenueCat SDK
### ✨ New Features
* Add Slovenian support for paywalls (#932) via Monika Mateska
(@MonikaMateska)
* [WEB-4279] Discount line items in native wallets (#905) via James
O'Donnell (@james-od)

## RevenueCatUI SDK
### ✨ New Features
* WFL-217 | Add support for workflows endpoints (#913) via Rosie Watson
(@RosieWatson)

### 🔄 Other Changes
* [AUTOMATIC] Update generated error codes (#931) via RevenueCat Git Bot
(@RCGitBot)
* Danger: block manual edits to generated error codes (#930) via Álvaro
Brey (@AlvaroBrey)
* Add update-error-codes workflow (#926) via Álvaro Brey (@AlvaroBrey)
* test(e2e): add full Paddle E2E coverage to webbilling-demo (WST-564)
(#925) via Roger Solé (@rogersole)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Version and changelog-only changes with no runtime logic modified in
this PR.
> 
> **Overview**
> **Automatic release** that publishes **1.43.0** by aligning version
metadata everywhere it is duplicated: `.version`, `package.json`,
`src/helpers/constants.ts`, and the docs redirect in
`scripts/docs/index.html` (1.42.4 → 1.43.0).
> 
> `CHANGELOG.md` and `CHANGELOG.latest.md` are updated to document what
ships in this release (already merged elsewhere): **Slovenian paywall
locale**, **discount line items in native wallets**, **RevenueCatUI
workflows endpoints**, regenerated error codes plus CI/Danger
guardrails, and expanded Paddle E2E coverage in webbilling-demo.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
ef5e9f3. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants