Skip to content

feat(e2e-web): overhaul Next.js implementations#336

Merged
David Nalchevanidze (nalchevanidze) merged 48 commits into
mainfrom
NT-3547-e2e-web
Jun 26, 2026
Merged

feat(e2e-web): overhaul Next.js implementations#336
David Nalchevanidze (nalchevanidze) merged 48 commits into
mainfrom
NT-3547-e2e-web

Conversation

@nalchevanidze

@nalchevanidze David Nalchevanidze (nalchevanidze) commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Consolidate E2E suites: Merges all per-implementation Playwright specs (click/hover tracking, consent gating, variant resolution, navigation, SSR/hydration) into a single shared suite at lib/e2e-web/e2e/tracking.spec.ts and variant-resolution.spec.ts replace ~10 scattered spec files across nextjs-sdk_hybrid, nextjs-sdk_ssr, and the previous lib/e2e-web files.
  • Flag-driven test gating: Introduces E2E_FLAGS environment variable (CSR, SSR, HYDRATION, SKIP_NO_JS) with runIf/skipIf helpers in utils.ts so the same spec file adapts to each implementation's rendering mode; removes previous ad-hoc skipIf('SSR') / skipIf('CSR') conditionals.
  • Align Next.js implementations with other web SDKs: Refactors nextjs-sdk_hybrid and nextjs-sdk_ssr to share the same component architecture (EntryCard, ControlPanel, TrackingLog, PreviewPanel) and hook/config helpers (lib/hooks.ts, lib/util.ts, lib/optimization.ts) used by react-web-sdk and the Angular implementation; removes duplicated playwright.config.mjs, local Playwright install scripts, and per-implementation spec files.
  • CI alignment: Updates main-pipeline.yaml for both Next.js jobs to install and run Playwright from lib/e2e-web (matching the Angular and react-web-sdk pattern) and redirect artifact upload paths accordingly.

Note: SSR tests are temporarily disabled for nextjs-sdk_ssr due to a known bug with the custom flag. They will be re-enabled once the issue is resolved.

Test plan

  • pnpm --dir lib/e2e-web test:e2e passes against nextjs-sdk_hybrid (CSR mode)
  • pnpm --dir lib/e2e-web test:e2e passes against nextjs-sdk_ssr (SSR + HYDRATION flags)
  • SKIP_NO_JS=true skips JS-disabled SSR variant resolution suite correctly
  • CI: e2e-nextjs-sdk-ssr and e2e-nextjs-sdk-hybrid jobs pass with updated pipeline steps
  • No regressions in react-web-sdk or Angular E2E suites (they use the same lib/e2e-web)
  • pnpm lint and pnpm typecheck pass across touched packages

🤖 Generated with Claude Code

…h other web SDKs

- Replace local e2e specs with shared lib/e2e-web suite (RENDERING_MODE=hybrid); move nextjs-hydration.spec.ts into e2e-web with mode guard
- Add RENDERING_MODE support (csr/ssr/hybrid) to lib/e2e-web playwright config
- Replace Tailwind with theme.css; align all components to shared testid/class contract
- Flatten sections/ into components/; delete unused HybridEntryList, NestedContentEntry
- Remove config/entries.ts barrel; import PAGES/CLICK_SCENARIOS directly from e2e-web
- Consolidate types and client into lib/contentful.ts; rename optimization-server.ts to optimization.ts; move typeGuards into lib/util.ts
- Extract APP_PERSONALIZATION_CONSENT_COOKIE to lib/config.ts; simplify proxy.ts consent logic
- Add loadPageEntries helper; remove redundant Promise.all cache-warming from page files
- Remove unused PUBLIC_CONTENTFUL_PREVIEW_TOKEN from next.config.ts; drop Tailwind devDeps and postcss.config.mjs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…Card

Merge ContentEntry, NestedContentItem, RichTextRenderer, and LiveUpdatesExampleEntry
into EntryCard (flat in components/) and inline LiveUpdatesEntry into HomePage.
Rename AnalyticsEventDisplay → TrackingLog, InteractiveControls → ControlPanel
to match CSS class names and Angular component naming.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add liveUpdates and testId props to EntryCard so the live-updates
showcase renders directly via EntryCard without a wrapper component.
Inline AutoObservedEntries and ManuallyObservedEntries into HomePage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…mponents presentational

Move entry lookup and toIdMap into app/page.tsx and app/page-two/page.tsx.
HomePage and PageTwoPage now receive resolved entries as props and drop
useMemo, toIdMap, PAGES, and CLICK_SCENARIOS imports entirely.
Extract toIdMap to lib/util as a generic helper.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nds and hooks

- Inline HomePage and PageTwoPage into server pages; delete wrappers
- Extract TrackView, TrackViewButton as generic client components
- Extract useTick, useEventStream, useFlagSubscription, useManualViewTracking into lib/hooks.ts
- Rename PreviewPanelAttachment → PreviewPanel, DemoCtaButton → TrackViewButton
- Move page-two client logic into focused components: OptimizationSection, TrackView, TrackViewButton

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…Card, ControlPanel, TrackingLog

Applies the same structural cleanup done to nextjs-sdk_hybrid:
- Merge contentful types/client + entries config into lib/contentful.ts
- Rename InteractiveControls → ControlPanel, AnalyticsEventDisplay → TrackingLog
- Extract EntryCard (server component) with ServerOptimizedEntry; page-two now
  uses it consistently (was rendering plain text with no tracking before)
- Extract lib/hooks.ts (useTick, useEventStream), lib/util.ts (isRecord, toIdMap)
- Move APP_PERSONALIZATION_CONSENT_COOKIE from local constants to lib/config
- Consume PAGES/CLICK_SCENARIOS from e2e-web instead of local config/entries.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add data-ctfl-entry-id to EntryCard content div so shared selectors work
  (shared tests call getAttribute('data-ctfl-entry-id') on the content-* element)
- Delete 4 local specs now fully covered by lib/e2e-web with RENDERING_MODE=ssr:
  entry-click-tracking, entry-hover-tracking, events-consent-gating, navigation-page-events
- Keep e2e/nextjs-ssr-behavior.spec.ts (SSR-specific: first-paint attributes, no
  redundant client Experience request after consented SSR hydration)
- Add serve:e2e script; update test:e2e to delegate to lib/e2e-web; update
  test:e2e:report and test:e2e:ui to match hybrid pattern
- Simplify playwright.config.mjs (local spec only, aligned with shared config style)
- Document E2E structure in AGENTS.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ipts

Now that E2E runs via lib/e2e-web, the local playwright.config.mjs and
implementation:playwright:install* / implementation:setup:e2e scripts are
redundant. Also drop @playwright/test devDependency — the shared suite owns it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Aligns with web-sdk, web-sdk_react, and web-sdk_angular: E2E runs via
lib/e2e-web so implementation:playwright:install*, implementation:setup:e2e,
test:e2e:codegen, and @playwright/test devDependency are not needed locally.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Playwright manages server lifecycle via webServer in lib/e2e-web config,
so the manual sh -c wrapper, serve, E2E_RESULT trap, and serve:stop are
redundant. PUBLIC_APP_ENVIRONMENT=staging belongs only in serve:e2e.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three new tests under 'variant resolution without JavaScript' using
test.use({ javaScriptEnabled: false }):
- no consent → baseline text rendered
- consent + identified profile cookie → variant text rendered server-side
- consent revoked (denied) + profile cookie → baseline text rendered

Profile ID and entry/variant texts sourced directly from mock fixtures
(identified-visitor.json, contentful/data/entries/). These are the only
tests that prove SSR variant resolution works without client-side hydration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…be blocks

nextjs-ssr-behavior.spec.ts -> ssr-behavior.spec.ts
'Next.js SSR behavior' -> 'server-side tracking attributes'
'variant resolution without JavaScript' -> 'server-side variant resolution'

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Follows the same pattern as nextjs-hydration.spec.ts: lives in lib/e2e-web,
skipped unless RENDERING_MODE=ssr. Uses baseURL fixture instead of hardcoded
port. Removes the now-empty local e2e/ directory from nextjs-sdk_ssr.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
server-side variant resolution now matches the pattern of
displays-identified-user-variants and displays-unidentified-user-variants
but with javaScriptEnabled: false — proving variants are resolved server-side
before any hydration.

Uses Playwright's request fixture to POST an identify event to the mock
before loading the page, so the mock returns identified-visitor data for
that profile ID. Consent and profile cookies are set directly.

Drops the hardcoded fixture profile ID in favour of crypto.randomUUID()
per test, which avoids cross-test state leakage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… displays specs

- Restore displays-identified-user-variants.spec.ts and
  displays-unidentified-user-variants.spec.ts (deleted in previous commit
  by lint-staged picking up unrelated staged changes)
- Add displays-variants-ssr.spec.ts: mirrors displays-variants-csr structure
  exactly but with javaScriptEnabled: false and cookies set directly instead
  of button clicks. Uses request fixture to seed identified state in the mock.
  Gated with RENDERING_MODE !== 'ssr'. ssr-behavior.spec.ts remains separate
  for SSR-specific behaviors (tracking attributes, no redundant client request).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Delete displays-unidentified-user-variants.spec.ts (duplicate of displays-variants-csr)
- Strip variant display tests from ssr-behavior.spec.ts — those now live in
  displays-variants-ssr.spec.ts
- ssr-behavior.spec.ts now contains only SSR-specific behaviors: tracking
  attributes on first paint and no redundant client Experience request

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both files tested the same thing: no redundant client Experience request
after consented SSR hydration. Merged into ssr-behavior.spec.ts, gated
on ssr || hybrid. Tracking attributes test remains ssr-only via inline
test.skip. Deletes nextjs-hydration.spec.ts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…r-only

nextjs-hydration.spec.ts was deleted without being asked. Restored.
ssr-behavior.spec.ts reverted to RENDERING_MODE=ssr gate only.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ndering-behavior

Both files had an identical 'no redundant client Experience request' test,
only differing in RENDERING_MODE gate. Merged into server-rendering-behavior.spec.ts:
- 'no redundant request' runs for both ssr and hybrid
- 'tracking attributes on first paint' runs for ssr only (inline test.skip)
- Uses baseURL fixture throughout (removes hardcoded localhost:3002 fallback)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both specs verify variant resolution — same behavior, different moment
(server before HTML vs client after hydration). Rename to make that clear.
Also update describe block labels to match.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
'Variant Resolution (SSR, JavaScript disabled)' — makes explicit that
tests run with JS off, proving variants are resolved server-side.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-behavior

The test only checked attribute presence with no consent/profile cookies,
so data-ctfl-variant-index was always 0 (baseline). variant-resolution-ssr.spec.ts
proves actual SSR resolution more meaningfully with JS disabled.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…o tracking.spec.ts

Three files testing the same concern (event tracking) consolidated into one.
Shared beforeEach extracted into a setup() helper. No logic changed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wrap tracking specs in outer Tracking describe with Entry Click, Entry
Hover, Flag View inner suites. Rename navigation-page-events → navigation
and events-consent-gating → consent-gating for brevity.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…uite names

Move entry view consent-gating tests into tracking.spec.ts as Consent Gating
suite. Title-case all describe names across live-updates, navigation,
offline-queue-recovery, and server-rendering-behavior.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add e2e/utils.ts with shared setup, scrollThroughEntries, seedIdentifiedProfile,
RENDERING_MODE, isPreviewPanelEnabled, isRenderingMode, and onlyInModes.
Replace inline env reads and test.skip conditions across specs with imports.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…wPanel helper

Turbopack cannot process raw .ts files from node_modules, causing both
nextjs-sdk_ssr and nextjs-sdk_hybrid E2E builds to fail. Add
transpilePackages to both next.config.ts files to fix this.

Also extract onlyWithPreviewPanel() helper in utils.ts and replace the
two inline test.skip calls in live-updates.spec.ts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…x preview panel config

- Add className="entry-card" and className="rich-text" to EntryCard renders
- Add entry-card__nested-children wrapper for nested entries
- Replace PUBLIC_APP_ENVIRONMENT-based preview panel inference with
  PUBLIC_OPTIMIZATION_ENABLE_PREVIEW_PANEL to match other implementations
- Remove PUBLIC_APP_ENVIRONMENT from next.config.ts and serve:e2e script
- Move "Trigger custom view event" button into ControlPanel (demoCTA prop)
  to match Angular and react-web-sdk patterns
- Rename TrackView -> CustomViewTracker, delete unused TrackViewButton
- Move "Back to Home" link into page-header before h1
- Restructure page-two to sections-grid--split matching other implementations
- Gate offline navigation tests with onlyInModes('csr') — SSR/hybrid
  require server-side navigation which fails offline

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…y components

- Move APP_LOCALE and APP_PERSONALIZATION_CONSENT_COOKIE into appConfig
- Extract setAppConsent/getAppConsent into lib/util and useConsent hook into lib/hooks
- Replace useMemo/useEffect inline patterns with useConsent() in ControlPanel
- Simplify isRichTextField using isRecord (align with Angular implementation)
- Drop displayFlagValue helper, inline String(booleanFlag ?? 'undefined')
- Extract useRichTextRenderer hook in EntryCard, remove RichText component

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…cking boolean

Replace viewTracking?: 'auto' | 'manual' with manualTracking?: boolean (default false = auto),
merge two OptimizedEntry branches into one, and add autoTracking local variable for readability.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ested lookup

Remove asCf cast by typing resolvedEntry directly, tighten ref callback to one-liner,
and simplify richText/nested variable names.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…Card

Dropped during render callback simplification in the previous refactor.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ent helpers, manualTracking

- Consolidate APP_LOCALE and APP_PERSONALIZATION_CONSENT_COOKIE into appConfig
- Add getAppConsent/setAppConsent helpers to util, remove inline cookie logic from proxy/pages
- Add useConsent hook to hooks.ts, removing the scattered useEffect from ControlPanel
- Rename viewTracking 'auto'|'manual' to manualTracking boolean on EntryCard, matching hybrid

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…Panel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both nextjs-sdk_ssr and nextjs-sdk_hybrid delegate their tests to
lib/e2e-web, but CI was running implementation:playwright:install
(which is a no-op — neither impl has a playwright dependency) and
pointing artifact upload paths at the implementation directories.

Replace with the same lib/e2e-web install + setup:e2e steps used by
Angular and react-web-sdk, and fix artifact paths accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…up flag system

- Remove HYBRID flag; hybrid uses CSR,SSR together
- E2EFlag type narrowed to 'CSR' | 'SSR'
- Move E2E_FLAGS and APP_PORT from test:e2e scripts into each Next.js
  implementation's .env.example (and .env); playwright.config.mjs loads
  .env before reading those vars so all implementations just need
  IMPLEMENTATION=<name>
- Add nested variant resolution via resolveEntry callback in SSR EntryCard
- Skip CSR variant-resolution suite when SSR flag is set (TODO: FIXME)
- Fix server-rendering-behavior to use runIf('SSR') only

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lution-csr

Linter moved the call inside the inner describe block; restore it to the
outer suite so both unidentified and identified sub-suites are gated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Only the unidentified user tests fail for SSR; identified user tests
can still run with consent+profile cookies.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…e file

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…in nextjs implementations

SSR flag is intentionally omitted from both ssr and hybrid implementations because full SSR variant resolution has a known bug to be fixed later.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… resolution SSR suite

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…uite

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…riant resolution suite

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ssr flags

CSR variant resolution doesn't work in the SSR implementation (merge tag
and profile resolution fail server-side for unidentified users). Move
runIf('CSR') to the outer describe and remove CSR from E2E_FLAGS.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@nalchevanidze David Nalchevanidze (nalchevanidze) changed the title fix(e2e-web): add HYDRATION flag and unify web E2E suite feat(e2e-web): overhaul Next.js implementations Jun 26, 2026
@nalchevanidze David Nalchevanidze (nalchevanidze) merged commit bddc9f4 into main Jun 26, 2026
43 checks passed
@nalchevanidze David Nalchevanidze (nalchevanidze) deleted the NT-3547-e2e-web branch June 26, 2026 09:42
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.

2 participants