Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f7d1008
feat(web-sdk_angular): add self-contained Playwright E2E suite [NT-3466]
nalchevanidze Jun 16, 2026
afb2ab7
fix(web-sdk_angular): fix 4 failing E2E specs [NT-3466]
nalchevanidze Jun 16, 2026
62ae4ec
feat(e2e-web): shared Playwright suite for web SDK implementations [N…
nalchevanidze Jun 16, 2026
fa1221b
fix(e2e-web): clear cookies in beforeEach to ensure deterministic aud…
nalchevanidze Jun 16, 2026
eb795ea
fix(e2e-web): add test:unit stub for pre-push hook [NT-3466]
nalchevanidze Jun 16, 2026
438f944
refactor(e2e-web): move env vars to parent callers [NT-3466]
nalchevanidze Jun 16, 2026
9512984
fix(e2e-web): isolate storage per test and handle browser-variant dif…
nalchevanidze Jun 16, 2026
28a55ed
chore(e2e-web): remove .env.example [NT-3466]
nalchevanidze Jun 16, 2026
bc871eb
chore(e2e-web): remove dotenv dependency and .env loading [NT-3466]
nalchevanidze Jun 16, 2026
9bef336
fix(e2e-web): remove or() fallbacks and clarify CSR scope [NT-3466]
nalchevanidze Jun 16, 2026
6f6e7d9
fix(e2e-web): restore original visitor/experiment variant assertions …
nalchevanidze Jun 16, 2026
abd1471
fix(web-sdk_react): re-resolve entries when selectedOptimizations arr…
nalchevanidze Jun 16, 2026
297ede0
chore(e2e): load .env from parent implementation and rename BASE_URL …
nalchevanidze Jun 16, 2026
148a727
fix(e2e): install e2e-web before running web SDK e2e tests in CI [NT-…
nalchevanidze Jun 16, 2026
ec9bb66
refactor(e2e): switch Angular to static server on port 3000, clean up…
nalchevanidze Jun 16, 2026
96a9a65
ci: install e2e-web and use it for playwright in React and Angular E2…
nalchevanidze Jun 16, 2026
cab87d8
fix(web-sdk_angular): suppress CJS warnings, fix import.meta.env cras…
nalchevanidze Jun 16, 2026
855b641
fix(e2e): disable preview panel in Angular .env.example for E2E consi…
nalchevanidze Jun 16, 2026
e8932b3
fix(web-sdk_angular): opt-in preview panel instead of opt-out [NT-3466]
nalchevanidze Jun 16, 2026
d8bedd8
chore(web-sdk_angular): revert angular.json define to static empty ob…
nalchevanidze Jun 16, 2026
0b4a70d
fix(e2e): run tsx directly in PM2 to prevent port 8000 leak on serve:…
nalchevanidze Jun 16, 2026
3b48a30
fix(web-sdk_angular): simplify serve scripts and remove redundant cur…
nalchevanidze Jun 16, 2026
40d198f
fix(e2e): always capture traces locally and wait for mocks on port 80…
nalchevanidze Jun 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 59 additions & 5 deletions .github/workflows/main-pipeline.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ jobs:
e2e_node_sdk_web_sdk: ${{ steps.filter.outputs.e2e_node_sdk_web_sdk }}
e2e_web_sdk: ${{ steps.filter.outputs.e2e_web_sdk }}
e2e_web_sdk_react: ${{ steps.filter.outputs.e2e_web_sdk_react }}
e2e_web_sdk_angular: ${{ steps.filter.outputs.e2e_web_sdk_angular }}
e2e_react_web_sdk: ${{ steps.filter.outputs.e2e_react_web_sdk }}
e2e_react_native_android: ${{ steps.filter.outputs.e2e_react_native_android }}
e2e_android: ${{ steps.filter.outputs.e2e_android }}
Expand Down Expand Up @@ -71,7 +72,12 @@ jobs:
- '!{docs/**,documentation/**,**/docs/**,**/documentation/**}'
# React + Web SDK implementation E2E coverage scope.
e2e_web_sdk_react:
- '{implementations/web-sdk_react/**,lib/**,packages/web/web-sdk/**,packages/web/preview-panel/**,packages/universal/core-sdk/**,packages/universal/api-client/**,packages/universal/api-schemas/**,package.json,pnpm-lock.yaml,.github/workflows/main-pipeline.yaml}'
- '{implementations/web-sdk_react/**,implementations/e2e-web/**,lib/**,packages/web/web-sdk/**,packages/web/preview-panel/**,packages/universal/core-sdk/**,packages/universal/api-client/**,packages/universal/api-schemas/**,package.json,pnpm-lock.yaml,.github/workflows/main-pipeline.yaml}'
- '!**/*.@(md|mdx|markdown)'
- '!{docs/**,documentation/**,**/docs/**,**/documentation/**}'
# Angular + Web SDK implementation E2E coverage scope.
e2e_web_sdk_angular:
- '{implementations/web-sdk_angular/**,implementations/e2e-web/**,lib/**,packages/web/web-sdk/**,packages/web/preview-panel/**,packages/universal/core-sdk/**,packages/universal/api-client/**,packages/universal/api-schemas/**,package.json,pnpm-lock.yaml,.github/workflows/main-pipeline.yaml}'
- '!**/*.@(md|mdx|markdown)'
- '!{docs/**,documentation/**,**/docs/**,**/documentation/**}'
# React Web SDK (optimization-react-web) implementation E2E coverage scope.
Expand Down Expand Up @@ -544,17 +550,65 @@ jobs:
path: pkgs
- run: pnpm store prune
- run: pnpm run implementation:web-sdk_react -- implementation:install -- --no-frozen-lockfile
- run:
pnpm run implementation:web-sdk_react -- implementation:playwright:install -- --with-deps
- run: pnpm run implementation:run -- e2e-web implementation:install -- --no-frozen-lockfile
- run: pnpm run implementation:run -- e2e-web implementation:playwright:install -- --with-deps
- run: pnpm run implementation:web-sdk_react -- implementation:test:e2e:run

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ !cancelled() }}
with:
name: ci-results-web-sdk_react
path: |
./implementations/web-sdk_react/playwright-report/
./implementations/web-sdk_react/test-results/
./implementations/e2e-web/playwright-report/
./implementations/e2e-web/test-results/
retention-days: 1

e2e-web-sdk_angular:
name: 🅰️ E2E Angular + Web SDK
runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal
timeout-minutes: 15
needs: [setup, changes, build]
if: needs.changes.outputs.e2e_web_sdk_angular == 'true'
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1

- name: Create .env from .env.example
run: cp implementations/web-sdk_angular/.env.example implementations/web-sdk_angular/.env

- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false

- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3

- name: Set up caches (Namespace)
uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: |
pnpm
playwright
apt

- run: pnpm install --prefer-offline --frozen-lockfile
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: sdk-package-tarballs
path: pkgs
- run: pnpm store prune
- run:
pnpm run implementation:web-sdk_angular -- implementation:install -- --no-frozen-lockfile
- run: pnpm run implementation:run -- e2e-web implementation:install -- --no-frozen-lockfile
- run: pnpm run implementation:run -- e2e-web implementation:playwright:install -- --with-deps
- run: pnpm run implementation:web-sdk_angular -- implementation:test:e2e:run

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ !cancelled() }}
with:
name: ci-results-web-sdk_angular
path: |
./implementations/e2e-web/playwright-report/
./implementations/e2e-web/test-results/
retention-days: 1

e2e-react-web-sdk:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { expect, test } from '@playwright/test'

test.describe('identified user', () => {
test.use({ storageState: { cookies: [], origins: [] } })

test.beforeEach(async ({ page }) => {
await page.goto('/')
await page.waitForLoadState('domcontentloaded')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { expect, test } from '@playwright/test'

test.describe('unidentified user', () => {
test.use({ storageState: { cookies: [], origins: [] } })

test.beforeEach(async ({ page }) => {
await page.goto('/')
await page.waitForLoadState('domcontentloaded')
Expand Down
19 changes: 19 additions & 0 deletions implementations/e2e-web/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@implementation/e2e-web",
"private": true,
"version": "0.0.0",
"description": "Shared Playwright E2E suite for CSR web SDK implementations.",
"license": "MIT",
"scripts": {
"test": "playwright test",
"test:ui": "playwright test --ui",
"test:report": "playwright show-report",
"implementation:playwright:install": "playwright install",
"implementation:playwright:install-deps": "playwright install-deps",
"implementation:setup:e2e": "pnpm implementation:playwright:install && pnpm implementation:playwright:install-deps",
"test:unit": "echo \"No unit tests necessary\""
},
"devDependencies": {
"@playwright/test": "1.58.2"
}
}
43 changes: 43 additions & 0 deletions implementations/e2e-web/playwright.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { defineConfig, devices } from '@playwright/test'

const isCI = Boolean(process.env.CI)

export default defineConfig({
testDir: './e2e',
fullyParallel: true,
forbidOnly: isCI,
retries: isCI ? 2 : 0,
workers: 1,
timeout: 60000,
expect: { timeout: 5000 },
reporter: [['html', { open: 'never' }]],
use: {
baseURL: process.env.BASE_URL ?? 'http://localhost:3000',
// 'on-first-retry' never writes traces locally (0 retries) — the UI trace viewer
// hits 404/500 on every snapshot load. Always capture locally so the viewer works.
trace: isCI ? 'on-first-retry' : 'on',
video: 'on-first-retry',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
],
webServer: [
{
command: 'echo "server managed externally"',
url: process.env.BASE_URL ?? 'http://localhost:3000',
reuseExistingServer: true,
timeout: 120000,
},
{
// Wait for the mocks server (port 8000) before starting tests.
// PM2 returns immediately after forking — without this poll, the first
// API call races the tsx startup and hangs until the 60s test timeout.
command: 'echo "mocks managed externally"',
url: process.env.MOCKS_URL ?? 'http://localhost:8000',
reuseExistingServer: true,
timeout: 30000,
},
],
})
1 change: 1 addition & 0 deletions implementations/e2e-web/pnpm-workspace.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sharedWorkspaceLockfile: false
15 changes: 15 additions & 0 deletions implementations/e2e-web/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "dom"],
"module": "ES2022",
"moduleResolution": "bundler",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true
},
"include": ["e2e/**/*.ts"]
}
2 changes: 1 addition & 1 deletion implementations/web-sdk_angular/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ PUBLIC_CONTENTFUL_SPACE_ID="mock-space-id"

PUBLIC_CONTENTFUL_CDA_HOST="localhost:8000"
PUBLIC_CONTENTFUL_BASE_PATH="contentful"
PUBLIC_OPTIMIZATION_ENABLE_PREVIEW_PANEL="true"
PUBLIC_OPTIMIZATION_ENABLE_PREVIEW_PANEL="false"
13 changes: 11 additions & 2 deletions implementations/web-sdk_angular/angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,23 @@
"browser": "src/main.ts",
"tsConfig": "tsconfig.json",
"assets": [],
"styles": ["src/styles.css"]
"styles": ["src/styles.css"],
"allowedCommonJsDependencies": [
"lodash",
"contentful-sdk-core",
"qs",
"json-stringify-safe"
],
"define": {
"import.meta.env": "{}"
}
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kB",
"maximumWarning": "600kB",
"maximumError": "1MB"
}
],
Expand Down
10 changes: 10 additions & 0 deletions implementations/web-sdk_angular/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,17 @@
"dev": "ng serve",
"build": "ng build",
"clean": "rimraf ./dist",
"serve": "pnpm serve:mocks && pnpm serve:app",
"serve:mocks": "pm2 start --name web-sdk_angular-mocks \"pnpm --dir ../../lib/mocks serve\"",
"serve:mocks:stop": "pm2 stop web-sdk_angular-mocks && pm2 delete web-sdk_angular-mocks",
"serve:app": "ng build && pm2 start --name web-sdk_angular-app \"http-server dist/web-sdk_angular/browser -p 3000 --silent\"",
"serve:app:stop": "pm2 stop web-sdk_angular-app && pm2 delete web-sdk_angular-app",
"serve:stop": "pnpm serve:app:stop && pnpm serve:mocks:stop",
"test:e2e:setup": "pnpm serve:mocks && pnpm serve:app",
"test:e2e": "pnpm test:e2e:setup && pnpm --dir ../e2e-web test; E2E_RESULT=$?; pnpm serve:stop; exit $E2E_RESULT",
"test:e2e:ui": "pnpm test:e2e:setup && pnpm --dir ../e2e-web test:ui; pnpm serve:stop;",
"test:e2e:report": "pnpm --dir ../e2e-web test:report",
"implementation:setup:e2e": "pnpm --dir ../e2e-web implementation:setup:e2e",
"test:unit": "echo \"No unit tests necessary\"",
"typecheck": "tsc -p tsconfig.json --noEmit"
},
Expand All @@ -31,6 +40,7 @@
"@angular/cli": "^22.0.0",
"@angular/compiler-cli": "^22.0.0",
"@types/node": "^24.0.13",
"http-server": "^14.1.1",
"pm2": "^6.0.14",
"rimraf": "^6.1.3",
"typescript": "~6.0.3"
Expand Down
2 changes: 1 addition & 1 deletion implementations/web-sdk_angular/src/app/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const appConfig: ApplicationConfig = {
cdaHost: import.meta.env.PUBLIC_CONTENTFUL_CDA_HOST ?? 'localhost:8000',
basePath: import.meta.env.PUBLIC_CONTENTFUL_BASE_PATH ?? 'contentful',
},
...(import.meta.env.PUBLIC_OPTIMIZATION_ENABLE_PREVIEW_PANEL !== 'false'
...(import.meta.env.PUBLIC_OPTIMIZATION_ENABLE_PREVIEW_PANEL === 'true'
? { previewPanel: {} }
: {}),
}),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
<section class="control-panel">
<h2 class="control-panel__title">SDK state</h2>
<h2 class="control-panel__title">Utilities</h2>

<div class="control-panel__table">
<span
class="control-panel__row-label"
data-testid="consent-status"
data-tooltip="SDK tracking is active only when consent is true"
>Consent</span
>
<span class="control-panel__row-value">{{ consent() ?? 'undefined' }}</span>
<span class="control-panel__row-value" data-testid="consent-status"
>Consent: {{ consent() ?? 'undefined' }}</span
>
@if (consent() === true) {
<button
class="btn btn--danger btn--sm"
Expand All @@ -23,23 +24,32 @@ <h2 class="control-panel__title">SDK state</h2>
data-testid="consent-button"
(click)="toggleConsent()"
>
Grant
Accept Consent
</button>
}

<span
class="control-panel__row-label"
data-testid="identified-status"
data-tooltip="User has been identified with a profile via the identify() call"
>Identified</span
>
<span class="control-panel__row-value">{{ isIdentified() ? 'Yes' : 'No' }}</span>
<span class="control-panel__row-value" data-testid="identified-status"
>{{ isIdentified() ? 'Yes' : 'No' }}</span
>
@if (isIdentified()) {
<button class="btn btn--danger btn--sm" data-testid="reset-button" (click)="reset()">
<button
class="btn btn--danger btn--sm"
data-testid="live-updates-reset-button"
(click)="reset()"
>
Reset
</button>
} @else {
<button class="btn btn--secondary btn--sm" data-testid="identify-button" (click)="identify()">
<button
class="btn btn--secondary btn--sm"
data-testid="live-updates-identify-button"
(click)="identify()"
>
Identify
</button>
}
Expand All @@ -50,29 +60,34 @@ <h2 class="control-panel__title">SDK state</h2>
data-tooltip="When ON, entries re-resolve and rerender on profile changes"
>Live updates</span
>
<span class="control-panel__row-value"
<span class="control-panel__row-value" data-testid="global-live-updates-status"
>{{ liveUpdatesService.globalLiveUpdates() ? 'ON' : 'OFF' }}</span
>
<button
class="btn btn--sm"
[class.btn--secondary]="!liveUpdatesService.globalLiveUpdates()"
[class.btn--danger]="liveUpdatesService.globalLiveUpdates()"
data-testid="live-updates-toggle"
data-testid="toggle-global-live-updates-button"
(click)="liveUpdatesService.toggle()"
>
{{ liveUpdatesService.globalLiveUpdates() ? 'OFF' : 'ON' }}
</button>

<span
class="control-panel__row-label"
data-testid="preview-panel-status"
data-tooltip="Contentful preview panel is open — forces live updates regardless of the global toggle"
>Preview panel</span
>
<span class="control-panel__row-value"
<span class="control-panel__row-value" data-testid="preview-panel-status"
>{{ liveUpdatesService.previewPanelVisible() ? 'Open' : 'Closed' }}</span
>
<span></span>
<button
class="btn btn--sm btn--secondary"
data-testid="simulate-preview-panel-button"
(click)="liveUpdatesService.togglePreviewPanel()"
>
{{ liveUpdatesService.previewPanelVisible() ? 'Close Preview Panel' : 'Open Preview Panel' }}
</button>

<span
class="control-panel__row-label"
Expand All @@ -85,19 +100,20 @@ <h2 class="control-panel__title">SDK state</h2>

<span
class="control-panel__row-label"
data-testid="selected-optimizations-count"
data-tooltip="Number of selected optimization variants currently applied"
>Active optimizations</span
>
<span class="control-panel__row-value">{{ optimizationCount() }}</span>
<span class="control-panel__row-value" data-testid="selected-optimizations-count"
>{{ optimizationCount() }}</span
>
<span></span>
</div>

@if (onTrackConversion()) {
<div class="control-panel__actions">
<button
class="btn btn--secondary btn--sm"
data-testid="track-conversion-button"
data-testid="page-two-demo-cta"
type="button"
(click)="trackConversion()"
>
Expand Down
Loading
Loading