Added gift-link analytics journey E2E test#29005
Conversation
WalkthroughThis PR updates the post analytics overview page object with gift-link locators and helper methods, adds a new e2e gift-link analytics journey that creates a paid post, opens the share modal, copies and visits a gift link, and checks analytics results, updates the Gift link card visitors element test id, exports the new page object from the barrel, and bumps the analytics service Docker image digest. Changes
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant Test
participant PostAnalyticsOverviewPage
participant IsolatedPage
Test->>PostAnalyticsOverviewPage: openShareModal()
Test->>PostAnalyticsOverviewPage: copyGiftLinkUrl()
Test->>IsolatedPage: visit gift-read URL
IsolatedPage-->>Test: analytics beacon sent
Test->>PostAnalyticsOverviewPage: refreshData()
Test->>PostAnalyticsOverviewPage: assert visitors value and filters
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
|
| Command | Status | Duration | Result |
|---|---|---|---|
nx run-many --target=build --projects=tag:publi... |
✅ Succeeded | <1s | View ↗ |
nx run-many -t test:unit -p @tryghost/posts,@tr... |
✅ Succeeded | 2m 29s | View ↗ |
nx run @tryghost/admin:build |
✅ Succeeded | 1m 49s | View ↗ |
nx run-many -t lint -p @tryghost/posts,@tryghos... |
✅ Succeeded | 21s | View ↗ |
nx run ghost:build:assets |
✅ Succeeded | 2s | View ↗ |
nx run ghost:build:tsc |
✅ Succeeded | 6s | View ↗ |
💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗
☁️ Nx Cloud last updated this comment at 2026-07-01 14:59:42 UTC
ref https://linear.app/ghost/issue/BER-3746/integrate-gift-link-usage-tracking-with-analytics - end-to-end coverage for the gift-link analytics journey: create a paid post, mint a gift link, visit its public URL to fire the real ghost-stats beacon, then assert the visit surfaces in admin — both the per-link visitor count (share modal badge) and the web-traffic "Gift link = used" filter - runs in the analytics Playwright project (real beacon -> proxy -> Tinybird, with the lane's TINYBIRD_WAIT=true making ingestion synchronous, so no polling) - skipped behind GIFT_LINK_PROXY_READY: the pinned analytics proxy rebuilds the page-hit payload and drops gift_link, so the beacon can't carry it until TryGhost/TrafficAnalytics#742 ships and the lane's proxy image is bumped; flip the env on (and bump the image) in that same change to enable - adds PostAnalyticsOverviewPage for the gift-link card + share modal
ref https://linear.app/tryghost/issue/BER-3746 - tightened the proxy-readiness note to the essentials (the pipeline, the #742 dependency, and how to enable) and dropped the self-evident badge comment - e2e guidance prefers fewer comments and clearer names
ref https://linear.app/tryghost/issue/BER-3746 - TrafficAnalytics#742 (gift passthrough) merged and shipped in ghost/traffic-analytics 1.0.261, so the pinned dev/e2e proxy now carries gift_link end-to-end instead of dropping it - with the real image in place the GIFT_LINK_PROXY_READY escape hatch has no reason to exist; removing it lets the journey test run by default in CI rather than skipping until a manual local build was wired up
af9b928 to
523448e
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 523448eeb0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| await webTraffic.selectFilterField('used'); | ||
|
|
||
| await expect(webTraffic.getActiveFilter('Gift link')).toBeVisible(); | ||
| await expect(webTraffic.totalUniqueVisitorsTab).toContainText('1'); |
There was a problem hiding this comment.
Isolate the gift-link count test from prior visits
When this file runs as a whole, the E2E fixture reuses one Ghost/Tinybird environment per file unless the file opts into per-test isolation, so the first test’s gift-link page hit is still present when this second test applies the site-wide Gift link = used filter. After the preceding test has already recorded one gift-link visitor, this assertion can see two unique gift-link visitors instead of one; either isolate the file/test or scope the assertion to data created within this test.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@e2e/tests/admin/analytics/gift-link-journey.test.ts`:
- Around line 15-17: The gift-link setup in the test currently assumes
`page.request.put` succeeded and that `gift_links` contains at least one item
before reading `giftLinks[0].token`. Update the `gift-link-journey.test.ts` flow
to first assert the PUT response is successful, then verify the parsed JSON
contains a non-empty `gift_links` array before indexing, so failures in this
test surface as clear assertions instead of an undefined access error.
🪄 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: 5213d8c0-dac4-48d8-9f99-cd4eed628420
📒 Files selected for processing (4)
compose.dev.analytics.yamle2e/helpers/pages/admin/analytics/post-analytics/index.tse2e/helpers/pages/admin/analytics/post-analytics/post-analytics-overview-page.tse2e/tests/admin/analytics/gift-link-journey.test.ts
| const response = await page.request.put(`/ghost/api/admin/posts/${post.id}/gift_links`); | ||
| const {gift_links: giftLinks} = await response.json(); | ||
| const token = giftLinks[0].token; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Guard the gift-link API response before indexing.
response.json() result isn't checked for success or a non-empty gift_links array before accessing giftLinks[0].token. If the PUT fails or returns no links, the failure surfaces as a confusing undefined error deep in the helper rather than a clear assertion failure.
🛡️ Proposed fix
const response = await page.request.put(`/ghost/api/admin/posts/${post.id}/gift_links`);
+ expect(response.ok()).toBeTruthy();
const {gift_links: giftLinks} = await response.json();
+ expect(giftLinks.length).toBeGreaterThan(0);
const token = giftLinks[0].token;📝 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.
| const response = await page.request.put(`/ghost/api/admin/posts/${post.id}/gift_links`); | |
| const {gift_links: giftLinks} = await response.json(); | |
| const token = giftLinks[0].token; | |
| const response = await page.request.put(`/ghost/api/admin/posts/${post.id}/gift_links`); | |
| expect(response.ok()).toBeTruthy(); | |
| const {gift_links: giftLinks} = await response.json(); | |
| expect(giftLinks.length).toBeGreaterThan(0); | |
| const token = giftLinks[0].token; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@e2e/tests/admin/analytics/gift-link-journey.test.ts` around lines 15 - 17,
The gift-link setup in the test currently assumes `page.request.put` succeeded
and that `gift_links` contains at least one item before reading
`giftLinks[0].token`. Update the `gift-link-journey.test.ts` flow to first
assert the PUT response is successful, then verify the parsed JSON contains a
non-empty `gift_links` array before indexing, so failures in this test surface
as clear assertions instead of an undefined access error.
ref https://linear.app/tryghost/issue/BER-3746 - the test minted the link over the admin API and only checked a badge, so it never exercised the path a user actually takes - it now creates the link through the share modal's copy button (reading the URL back off the clipboard), reads the gifted post in a real browser, and asserts the visit surfaces on the post's gift-link analytics card as well as the web-traffic filter - adds a data-testid for the card's visitor count and a clipboard-backed copy action on the overview page object; reloads (not hash re-nav) to refetch, the same guard the other analytics visit tests use
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5ccc9e3c8c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| await withIsolatedPage(browser, {baseURL}, async ({page: publicPage}) => { | ||
| const {pathname, search} = new URL(giftUrl); | ||
| await new PublicPage(publicPage).goto(`${pathname}${search}`); |
There was a problem hiding this comment.
Run the journey test in the analytics project
In the context I checked, e2e/playwright.config.mjs puts only files matching tests/analytics/**/*.test.ts in the analytics project and the main project ignores only that same pattern, so this new tests/admin/analytics/... file runs under main. In that project PublicPage.goto does not enable synthetic analytics or wait for the page-hit beacon, so this gift-link visit is never reliably ingested before the later 1-visitor assertions; move the file under the analytics project's matched path or update the project globs.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
🧹 Nitpick comments (2)
e2e/helpers/pages/admin/analytics/post-analytics/post-analytics-overview-page.ts (1)
8-9: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDeclare assertion-facing locator as
public readonly.
giftLinkCardVisitorsis asserted against directly in the test (toHaveText('1')), but it's declared as barereadonlyrather thanpublic readonly.PublicPagealready follows the explicitpublic readonly(assertion-facing) vsprivate readonly(internal-only) split for its locators — this new field breaks that convention.✏️ Suggested fix
- readonly giftLinkCardVisitors: Locator; - readonly copyGiftLinkButton: Locator; + public readonly giftLinkCardVisitors: Locator; + private readonly copyGiftLinkButton: Locator;As per path instructions, "In Page Objects, expose locators as
public readonlywhen used with assertions."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@e2e/helpers/pages/admin/analytics/post-analytics/post-analytics-overview-page.ts` around lines 8 - 9, Update the locator declaration in PostAnalyticsOverviewPage so the assertion-facing field giftLinkCardVisitors follows the PublicPage convention and is marked public readonly instead of bare readonly. Keep copyGiftLinkButton aligned with its intended visibility by leaving it private only if it is internal, and ensure any locator used directly in tests remains publicly exposed for assertions.Source: Path instructions
e2e/tests/admin/analytics/gift-link-journey.test.ts (1)
35-61: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winConsider consolidating the two scenarios into one test.
Both tests call the expensive
createGiftLinkThenVisithelper (post creation, UI-driven mint through the share modal, and a full isolated-context visit with ingestion wait) independently, doubling that cost for what is one journey verified from two angles (gift-link card visitors, web-traffic filter). Based on learnings, grouping closely related scenarios sharing the same setup/outcome into a singletest()is preferred ine2e/tests/admin/given how expensive E2E setup is here.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@e2e/tests/admin/analytics/gift-link-journey.test.ts` around lines 35 - 61, Consolidate the two Gift link analytics journey cases in gift-link-journey.test.ts into a single test that reuses one createGiftLinkThenVisit setup. Move the assertions for overview.giftLinkCardVisitors and AnalyticsWebTrafficPage into the same test flow, keeping the existing helper and page objects but avoiding a second expensive journey setup.Source: Learnings
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In
`@e2e/helpers/pages/admin/analytics/post-analytics/post-analytics-overview-page.ts`:
- Around line 8-9: Update the locator declaration in PostAnalyticsOverviewPage
so the assertion-facing field giftLinkCardVisitors follows the PublicPage
convention and is marked public readonly instead of bare readonly. Keep
copyGiftLinkButton aligned with its intended visibility by leaving it private
only if it is internal, and ensure any locator used directly in tests remains
publicly exposed for assertions.
In `@e2e/tests/admin/analytics/gift-link-journey.test.ts`:
- Around line 35-61: Consolidate the two Gift link analytics journey cases in
gift-link-journey.test.ts into a single test that reuses one
createGiftLinkThenVisit setup. Move the assertions for
overview.giftLinkCardVisitors and AnalyticsWebTrafficPage into the same test
flow, keeping the existing helper and page objects but avoiding a second
expensive journey setup.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: c2ddc5f4-5400-4167-81b8-4677408552a8
📒 Files selected for processing (3)
apps/posts/src/views/PostAnalytics/Overview/overview.tsxe2e/helpers/pages/admin/analytics/post-analytics/post-analytics-overview-page.tse2e/tests/admin/analytics/gift-link-journey.test.ts
✅ Files skipped from review due to trivial changes (1)
- apps/posts/src/views/PostAnalytics/Overview/overview.tsx

ref https://linear.app/ghost/issue/BER-3746/integrate-gift-link-usage-tracking-with-analytics
Draft — end-to-end coverage for the gift-link analytics journey. Opened as a draft because it can't pass until the analytics proxy carries
gift_link(see Blocked on, below).What it covers
The one seam no single-layer test proves: that a real gift read flows producer → client beacon → TrafficAnalytics proxy → Tinybird and surfaces back in admin. It:
PUT /posts/:id/gift_links)./<slug>/?gift=<token>in an isolated context, firing the realghost-statsbeacon.gift-link-viewsbadge), andRuns in the
analyticsPlaywright project, wherePublicPage.gotowaits for the page-hit request and the proxy runsTINYBIRD_WAIT=true— so ingestion is synchronous and no polling is needed.Blocked on
Skipped behind
GIFT_LINK_PROXY_READY. The pinned analytics proxy rebuilds the page-hit payload field-by-field and dropsgift_link, so the beacon can't carry it yet. To enable, in one change:gift_linkpassthrough) and publish an image.GIFT_LINK_PROXY_READYon (or remove the guard) and undraft.Also depends on the gift-link analytics stack being merged: #28854 (datafiles /
api_gift_link_visits_v2+gift_linkcolumn) and the usage-count wiring fix (#29004).Notes
PostAnalyticsOverviewPage(gift-link card + share modal) page object.pnpm lintandpnpm test:typespass.