Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
e8a44ac
[NT-1910] Update from main and reset previous changes onto branch
akfreas Nov 25, 2025
81b6ea7
[NT-1910] Modify server.ts to not check for host and port when matchi…
akfreas Nov 25, 2025
8219f13
[NT-1910] Configure with android local url, fix issue with init of op…
akfreas Nov 25, 2025
822deee
[NT-1910] Configure android emulator with proper proxy config, upload…
akfreas Nov 25, 2025
7f43bba
[NT-1910] Update server.ts to use use PORT variable to build urls
akfreas Nov 25, 2025
8c44467
[NT-1910] Rename package to standard @implementation/react-native and…
akfreas Dec 1, 2025
ed3affa
[NT-1910] Change android api level from 34 to 33 for stability, enabl…
akfreas Dec 1, 2025
6b4a507
[NT-1910] Increase disk space for android emulator
akfreas Dec 1, 2025
d865ebb
[NT-1910] Use pnpm --filter @implementation/xxx exec instead of pnpx
akfreas Dec 2, 2025
3cab914
[NT-1910] Remove duplicate react-native-dotenv in package, don't dist…
akfreas Dec 2, 2025
f64ba31
[NT-1910] Add documentation to script and to E2E-TESTING.md that desc…
akfreas Dec 2, 2025
ae7db70
[NT-1910] Fix type declarations in react native implementation
akfreas Dec 2, 2025
edf8352
implementations/react-native/package.json[NT-1910] Add script for run…
akfreas Dec 2, 2025
e6a2009
[NT-1910] Update e2e testing doc to use root pnpm build/test command
akfreas Dec 3, 2025
77d8bf3
[NT-1910] Update scripts to use consistent naming
akfreas Dec 3, 2025
431b73c
[NT-1910] Delete .nvmrc
akfreas Dec 1, 2025
7f1832f
[NT-2010] Add NestedPersonalization component and export from react-n…
akfreas Nov 27, 2025
1f523e7
[NT-2010] Add nested personalization support to React Native implemen…
akfreas Nov 27, 2025
8d5bb9f
[NT-2010] Add E2E tests for nested personalization variants
akfreas Nov 27, 2025
4cb419f
[NT-2010] Use resolved content entry rather than base entry, update e…
akfreas Dec 1, 2025
b9bc029
[NT-2010] Fix lint and test for unidentified user variant
akfreas Dec 1, 2025
c9b18d1
[NT-2010] Restore .nvmrc
akfreas Dec 3, 2025
9486cea
[NT-1910] Update scripts to use consistent naming
akfreas Dec 3, 2025
1006fc9
[NT-2010] Increase timeout for analytics test as a test
akfreas Dec 3, 2025
984e0d5
[NT-2010] Use label instead of regex for analytics matching
akfreas Dec 3, 2025
314ea60
[NT-2010] Allow Personalization components to be nested, remove Neste…
akfreas Dec 4, 2025
4ff2ef1
[NT-2010] Update nestedcontententry to use default component
akfreas Dec 5, 2025
ab627a1
[NT-2010] Fis issue with flex size on App
akfreas Dec 5, 2025
867ed3b
[NT-2010] Remove styles from implementation
akfreas Dec 5, 2025
8699896
[NT-2010] Add 120 second wait time to element visibility tests
akfreas Dec 8, 2025
699e49d
Merge branch 'main' of github.com:contentful/optimization into NT-201…
akfreas Jan 5, 2026
8a59ac7
[NT-2010] Remove experience.js
akfreas Jan 5, 2026
cf7475c
[NT-2010] Install pulseaudio due to changes on Ubuntu 24.04
akfreas Jan 5, 2026
18cc60e
[NT-2010] Update network settings for dns and port mapping
akfreas Jan 5, 2026
0fb2feb
[NT-2010] Update dns ony in emulator flag
akfreas Jan 5, 2026
9f14bf1
[NT-2010] Bind mock server to 0.0.0.0
akfreas Jan 6, 2026
f78ac8c
Add test file to trigger build
akfreas Jan 6, 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
10 changes: 10 additions & 0 deletions .github/workflows/main-pipeline.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,11 @@ jobs:
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm

- name: Install PulseAudio compatibility for emulator
run: |
sudo apt-get update
sudo apt-get install -y libpulse0

- uses: pnpm/action-setup@v4

- uses: actions/setup-node@v6
Expand Down Expand Up @@ -408,15 +413,20 @@ jobs:
force-avd-creation: true
emulator-options:
-no-window -no-audio -no-boot-anim -gpu swiftshader_indirect -no-snapshot-save
-dns-server 8.8.8.8
disable-animations: true
emulator-boot-timeout: 300
disk-size: 6G
script: |
echo "Verifying JAVA_HOME: $JAVA_HOME"
java -version
echo "Clearing any proxy settings..."
adb shell settings put global http_proxy :0 || true
echo "Setting up adb reverse port forwarding..."
adb reverse tcp:8000 tcp:8000
adb reverse tcp:8081 tcp:8081
echo "Verifying adb reverse is set up..."
adb reverse --list
echo "Building Android app..."
pnpm --filter @implementation/react-native test:e2e:android:build
echo "Running E2E tests..."
Expand Down
2 changes: 1 addition & 1 deletion implementations/react-native/.detoxrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ module.exports = {
type: 'android.apk',
binaryPath: path.join(__dirname, 'android/app/build/outputs/apk/debug/app-debug.apk'),
build: `cd ${__dirname}/android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug`,
reversePorts: [8081],
reversePorts: [8081, 8000],
},
'android.release': {
type: 'android.apk',
Expand Down
7 changes: 4 additions & 3 deletions implementations/react-native/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const ENTRY_IDS = [
'5XHssysWUDECHzKLzoIsg1',
'6zqoWXyiSrf0ja7I2WGtYj',
'7pa5bOx8Z9NmNcr7mISvD',
ENV_CONFIG.entries.nested,
]

function App(): React.JSX.Element {
Expand Down Expand Up @@ -75,20 +76,20 @@ function App(): React.JSX.Element {

return (
<OptimizationProvider instance={sdk}>
<SafeAreaView>
<SafeAreaView style={{ flex: 1 }}>
<View style={{ padding: 10, flexDirection: 'row', gap: 10 }}>
{!isIdentified ? (
<Button testID="identify-button" title="Identify" onPress={handleIdentify} />
) : (
<Button testID="reset-button" title="Reset" onPress={handleReset} />
)}
</View>
<ScrollView>
<ScrollView style={{ flex: 1 }}>
{entries.map((entry) => (
<ContentEntry key={entry.sys.id} entry={entry} sdk={sdk} />
))}
</ScrollView>
<View>
<View style={{ padding: 10 }}>
<AnalyticsEventDisplay sdk={sdk} />
</View>
</SafeAreaView>
Expand Down
43 changes: 43 additions & 0 deletions implementations/react-native/e2e/00-connectivity.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* Network Connectivity Test
*
* This test runs FIRST (due to alphabetical ordering) to verify that the
* mock server is reachable from the app. If this fails, all other tests
* will likely fail due to network issues.
*/

describe('network connectivity', () => {
beforeAll(async () => {
await device.launchApp({ newInstance: true })
})

it('should verify mock server is reachable', async () => {
// Wait for the app to start loading
// If the app can't reach the mock server, it will show "Loading..." indefinitely
// or show an error message

// First, wait for either the loading text or an error
await waitFor(element(by.text('Loading...')))
.toBeVisible()
.withTimeout(10000)

// Now wait for the loading to complete (identify button appears)
// This means the SDK initialized and fetched data from the mock server
await waitFor(element(by.id('identify-button')))
.toBeVisible()
.withTimeout(30000)

// If we get here, the network is working!
console.log('✅ Network connectivity verified - mock server is reachable')
})

it('should display content entries from mock server', async () => {
// Verify at least one content entry is displayed
// This confirms the Contentful mock is also working
await waitFor(element(by.id('entry-text-1MwiFl4z7gkwqGYdvCmr8c')))
.toBeVisible()
.withTimeout(30000)

console.log('✅ Content entries loaded from mock server')
})
})
8 changes: 4 additions & 4 deletions implementations/react-native/e2e/analytics.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { clearProfileState } = require('./helpers')
const { clearProfileState, ELEMENT_VISIBILITY_TIMEOUT } = require('./helpers')

describe('Analytics Events', () => {
beforeAll(async () => {
Expand All @@ -12,14 +12,14 @@ describe('Analytics Events', () => {
it('should track component impression events for visible entries', async () => {
// Wait for the app to load
const analyticsTitle = element(by.text('Analytics Events'))
await waitFor(analyticsTitle).toBeVisible().withTimeout(10000)
await waitFor(analyticsTitle).toBeVisible().withTimeout(ELEMENT_VISIBILITY_TIMEOUT)

// Wait a bit for content to load and be visible
await new Promise((resolve) => setTimeout(resolve, 3000))

// Look for component events with entry IDs
// The merge tag entry should trigger a component event
const componentEvent = element(by.text(/component - Component: 1MwiFl4z7gkwqGYdvCmr8c/i))
await waitFor(componentEvent).toBeVisible().withTimeout(15000)
const componentEvent = element(by.label('component - Component: 1MwiFl4z7gkwqGYdvCmr8c'))
await waitFor(componentEvent).toBeVisible().withTimeout(ELEMENT_VISIBILITY_TIMEOUT)
})
})
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { clearProfileState } = require('./helpers')
const { clearProfileState, ELEMENT_VISIBILITY_TIMEOUT } = require('./helpers')

describe('identified user', () => {
beforeAll(async () => {
Expand All @@ -10,7 +10,7 @@ describe('identified user', () => {

await waitFor(element(by.id('identify-button')))
.toBeVisible()
.withTimeout(10000)
.withTimeout(ELEMENT_VISIBILITY_TIMEOUT)

await element(by.id('identify-button')).tap()

Expand All @@ -22,7 +22,7 @@ describe('identified user', () => {
// Wait for the entry text to appear
await waitFor(element(by.id('entry-text-1MwiFl4z7gkwqGYdvCmr8c')))
.toBeVisible()
.withTimeout(10000)
.withTimeout(ELEMENT_VISIBILITY_TIMEOUT)

// Verify the element contains the merge tag text - check for key parts
await expect(
Expand All @@ -38,7 +38,7 @@ describe('identified user', () => {
// Wait for the entry text to appear
await waitFor(element(by.id('entry-text-4ib0hsHWoSOnCVdDkizE8d')))
.toBeVisible()
.withTimeout(10000)
.withTimeout(ELEMENT_VISIBILITY_TIMEOUT)

// Check for the variant text
await expect(
Expand All @@ -54,7 +54,7 @@ describe('identified user', () => {
// Wait for the entry text to appear
await waitFor(element(by.id('entry-text-xFwgG3oNaOcjzWiGe4vXo')))
.toBeVisible()
.withTimeout(10000)
.withTimeout(ELEMENT_VISIBILITY_TIMEOUT)

// Check for the variant text
await expect(
Expand All @@ -71,7 +71,7 @@ describe('identified user', () => {
it('should display variant for return visitors', async () => {
await waitFor(element(by.id('entry-text-2Z2WLOx07InSewC3LUB3eX')))
.toBeVisible()
.withTimeout(10000)
.withTimeout(ELEMENT_VISIBILITY_TIMEOUT)

await expect(
element(
Expand All @@ -85,7 +85,7 @@ describe('identified user', () => {
it('should display variant B for A/B/C experiment', async () => {
await waitFor(element(by.id('entry-text-5XHssysWUDECHzKLzoIsg1')))
.toBeVisible()
.withTimeout(10000)
.withTimeout(ELEMENT_VISIBILITY_TIMEOUT)

await expect(
element(
Expand All @@ -99,7 +99,7 @@ describe('identified user', () => {
it('should display variant for visitors with custom event', async () => {
await waitFor(element(by.id('entry-text-6zqoWXyiSrf0ja7I2WGtYj')))
.toBeVisible()
.withTimeout(10000)
.withTimeout(ELEMENT_VISIBILITY_TIMEOUT)

await expect(
element(
Expand All @@ -113,7 +113,7 @@ describe('identified user', () => {
it('should display variant for identified users', async () => {
await waitFor(element(by.id('entry-text-7pa5bOx8Z9NmNcr7mISvD')))
.toBeVisible()
.withTimeout(10000)
.withTimeout(ELEMENT_VISIBILITY_TIMEOUT)

await expect(
element(
Expand All @@ -124,4 +124,42 @@ describe('identified user', () => {
).toBeVisible()
})
})

describe('nested personalization variants', () => {
it('should display level 0 nested variant for return visitors', async () => {
await waitFor(element(by.id('entry-text-2KIWllNZJT205BwOSkMINg')))
.toBeVisible()
.withTimeout(ELEMENT_VISIBILITY_TIMEOUT)

await expect(
element(
by.label('This is a level 0 nested variant entry. [Entry: 2KIWllNZJT205BwOSkMINg]'),
),
).toBeVisible()
})

it('should display level 1 nested variant for return visitors', async () => {
await waitFor(element(by.id('entry-text-5a8ONfBdanJtlJ39WWnH1w')))
.toBeVisible()
.withTimeout(ELEMENT_VISIBILITY_TIMEOUT)

await expect(
element(
by.label('This is a level 1 nested variant entry. [Entry: 5a8ONfBdanJtlJ39WWnH1w]'),
),
).toBeVisible()
})

it('should display level 2 nested variant for return visitors', async () => {
await waitFor(element(by.id('entry-text-4hDiXxYEFrXHXcQgmdL9Uv')))
.toBeVisible()
.withTimeout(ELEMENT_VISIBILITY_TIMEOUT)

await expect(
element(
by.label('This is a level 2 nested variant entry. [Entry: 4hDiXxYEFrXHXcQgmdL9Uv]'),
),
).toBeVisible()
})
})
})
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { clearProfileState } = require('./helpers')
const { clearProfileState, ELEMENT_VISIBILITY_TIMEOUT } = require('./helpers')

describe('unidentified user', () => {
beforeAll(async () => {
Expand All @@ -14,7 +14,7 @@ describe('unidentified user', () => {
// Wait for the entry text to appear
await waitFor(element(by.id('entry-text-1MwiFl4z7gkwqGYdvCmr8c')))
.toBeVisible()
.withTimeout(10000)
.withTimeout(ELEMENT_VISIBILITY_TIMEOUT)

// Verify the element contains the merge tag text - check for key parts
await expect(
Expand All @@ -30,7 +30,7 @@ describe('unidentified user', () => {
// Wait for the entry text to appear
await waitFor(element(by.id('entry-text-4ib0hsHWoSOnCVdDkizE8d')))
.toBeVisible()
.withTimeout(10000)
.withTimeout(ELEMENT_VISIBILITY_TIMEOUT)

// Check for the variant text
await expect(
Expand All @@ -46,7 +46,7 @@ describe('unidentified user', () => {
// Wait for the entry text to appear
await waitFor(element(by.id('entry-text-xFwgG3oNaOcjzWiGe4vXo')))
.toBeVisible()
.withTimeout(10000)
.withTimeout(ELEMENT_VISIBILITY_TIMEOUT)

// Check for the variant text
await expect(
Expand All @@ -63,7 +63,7 @@ describe('unidentified user', () => {
it('should display variant for new visitors', async () => {
await waitFor(element(by.id('entry-text-2Z2WLOx07InSewC3LUB3eX')))
.toBeVisible()
.withTimeout(10000)
.withTimeout(ELEMENT_VISIBILITY_TIMEOUT)

await expect(
element(
Expand All @@ -77,7 +77,7 @@ describe('unidentified user', () => {
it('should display variant B for A/B/C experiment', async () => {
await waitFor(element(by.id('entry-text-5XHssysWUDECHzKLzoIsg1')))
.toBeVisible()
.withTimeout(10000)
.withTimeout(ELEMENT_VISIBILITY_TIMEOUT)

await expect(
element(
Expand All @@ -91,7 +91,7 @@ describe('unidentified user', () => {
it('should display baseline for visitors with or without custom event', async () => {
await waitFor(element(by.id('entry-text-6zqoWXyiSrf0ja7I2WGtYj')))
.toBeVisible()
.withTimeout(10000)
.withTimeout(ELEMENT_VISIBILITY_TIMEOUT)

await expect(
element(
Expand All @@ -105,7 +105,7 @@ describe('unidentified user', () => {
it('should display baseline for all identified or unidentified users', async () => {
await waitFor(element(by.id('entry-text-7pa5bOx8Z9NmNcr7mISvD')))
.toBeVisible()
.withTimeout(10000)
.withTimeout(ELEMENT_VISIBILITY_TIMEOUT)

await expect(
element(
Expand All @@ -116,4 +116,42 @@ describe('unidentified user', () => {
).toBeVisible()
})
})

describe('nested personalization baselines', () => {
it('should display level 0 nested baseline for new visitors', async () => {
await waitFor(element(by.id('entry-text-1JAU028vQ7v6nB2swl3NBo')))
.toBeVisible()
.withTimeout(ELEMENT_VISIBILITY_TIMEOUT)

await expect(
element(
by.label('This is a level 0 nested baseline entry. [Entry: 1JAU028vQ7v6nB2swl3NBo]'),
),
).toBeVisible()
})

it('should display level 1 nested baseline for new visitors', async () => {
await waitFor(element(by.id('entry-text-5i4SdJXw9oDEY0vgO7CwF4')))
.toBeVisible()
.withTimeout(ELEMENT_VISIBILITY_TIMEOUT)

await expect(
element(
by.label('This is a level 1 nested baseline entry. [Entry: 5i4SdJXw9oDEY0vgO7CwF4]'),
),
).toBeVisible()
})

it('should display level 2 nested baseline for new visitors', async () => {
await waitFor(element(by.id('entry-text-uaNY4YJ0HFPAX3gKXiRdX')))
.toBeVisible()
.withTimeout(ELEMENT_VISIBILITY_TIMEOUT)

await expect(
element(
by.label('This is a level 2 nested baseline entry. [Entry: uaNY4YJ0HFPAX3gKXiRdX]'),
),
).toBeVisible()
})
})
})
4 changes: 4 additions & 0 deletions implementations/react-native/e2e/helpers.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Timeout value for waiting for elements to become visible (in milliseconds)
const ELEMENT_VISIBILITY_TIMEOUT = 120000 // 120 seconds

async function clearProfileState() {
const platform = device.getPlatform()

Expand All @@ -7,4 +10,5 @@ async function clearProfileState() {

module.exports = {
clearProfileState,
ELEMENT_VISIBILITY_TIMEOUT,
}
Loading
Loading