Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Build RN example with detox. #558

Merged
merged 23 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
95 changes: 95 additions & 0 deletions .github/workflows/react-native-detox.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
name: sdk/react-native/example

# The example builds independently of react-native because of the duration of the build.
# We limit it to only build under specific circumstances.
# Additionally this does allow for scheduled builds of just the example, to handle changes in expo,
# should they be desired.

on:
push:
branches: [main, 'feat/**']
paths-ignore:
- '**.md' #Do not need to run CI for markdown changes.
pull_request:
branches: [main, 'feat/**']
paths:
- 'packages/shared/common/**'
- 'packages/shared/sdk-client/**'
- 'packages/sdk/react-native/**'
- 'packages/shared/mocks/**'

jobs:
detox-android:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
defaults:
run:
working-directory: packages/sdk/react-native/example
steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4

- name: Install deps
run: yarn workspaces focus
- name: Build
run: yarn workspaces foreach -pR --topological-dev --from 'react-native-example' run build

- uses: ./actions/release-secrets
name: 'Get mobile key'
with:
aws_assume_role: ${{ vars.AWS_ROLE_ARN_EXAMPLES }}
ssm_parameter_pairs: '/sdk/common/hello-apps/mobile-key = MOBILE_KEY,
/sdk/common/hello-apps/boolean-flag-key = LAUNCHDARKLY_FLAG_KEY'

- name: Create .env file.
run: echo "MOBILE_KEY=$MOBILE_KEY" > .env

- name: Enable KVM group perms (for performance)
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm

- name: Expo Prebuild
run: npx expo prebuild

# Java setup is after checkout and expo prebuild so that it can locate the
# gradle configuration.
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17
cache: 'gradle'

- name: Detox build
run: yarn detox build --configuration android.emu.release

- name: Get android emulator device name
id: device
run: node -e "console.log('AVD_NAME=' + require('./.detoxrc').devices.emulator.device.avdName)" >> $GITHUB_OUTPUT

- name: Make space for the emulator.
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
with:
android: false # We need android.

- name: Detox test
uses: reactivecircus/android-emulator-runner@f0d1ed2dcad93c7479e8b2f2226c83af54494915
with:
api-level: 31
arch: x86_64
avd-name: ${{ steps.device.outputs.AVD_NAME }}
working-directory: packages/sdk/react-native/example
script: yarn detox test --configuration android.emu.release --headless --record-logs all

- name: Upload artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: detox-artifacts
path: packages/sdk/react-native/example/artifacts
62 changes: 0 additions & 62 deletions .github/workflows/react-native.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,65 +22,3 @@ jobs:
with:
workspace_name: '@launchdarkly/react-native-client-sdk'
workspace_path: packages/sdk/react-native
detox-ios:
# TODO: disable detox for now because it's unstable.
if: false
# macos-latest uses macos-12 and we need macos-14 to get xcode 15.
# https://github.com/actions/runner-images/blob/main/README.md
runs-on: macos-14
permissions:
id-token: write
contents: read
defaults:
run:
working-directory: packages/sdk/react-native/example
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
- name: Install deps
run: yarn workspaces focus
- name: Build
run: yarn workspaces foreach -pR --topological-dev --from 'react-native-example' run build
- name: Install macOS dependencies
run: |
brew tap wix/brew
brew install applesimutils
env:
HOMEBREW_NO_AUTO_UPDATE: 1
HOMEBREW_NO_INSTALL_CLEANUP: 1

- name: Cache Detox build
id: cache-detox-build
uses: actions/cache@v4
with:
path: ios/build
key: ${{ runner.os }}-detox-build
restore-keys: |
${{ runner.os }}-detox-build

- name: Detox rebuild framework cache
run: yarn detox rebuild-framework-cache

- uses: ./actions/release-secrets
name: 'Get mobile key'
with:
aws_assume_role: ${{ vars.AWS_ROLE_ARN }}
ssm_parameter_pairs: '/sdk/detox/mobile-key = MOBILE_KEY'

- name: Set mobile key
run: echo "MOBILE_KEY=$MOBILE_KEY" > .env

- name: Expo prebuild
# HACK: Deleting ios/.xcode.env.local is needed to solve an xcode build issue with rn 0.73
# https://github.com/facebook/react-native/issues/42112#issuecomment-1884536225
run: |
export NO_FLIPPER=1
yarn expo-prebuild
rm -rf ./ios/.xcode.env.local

- name: Detox build
run: yarn detox build --configuration ios.sim.release

- name: Detox test
run: yarn detox test --configuration ios.sim.release --cleanup --headless --record-logs all --take-screenshots failing
2 changes: 1 addition & 1 deletion packages/sdk/react-native/example/.detoxrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ module.exports = {
emulator: {
type: 'android.emulator',
device: {
avdName: 'Pixel_3a_API_33_arm64-v8a',
avdName: 'Pixel_4_API_30',
},
},
},
Expand Down
3 changes: 3 additions & 0 deletions packages/sdk/react-native/example/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,6 @@ ios
android

!yarn.lock

# detox
artifacts
3 changes: 2 additions & 1 deletion packages/sdk/react-native/example/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
},
"web": {
"favicon": "./assets/favicon.png"
}
},
"plugins": ["@config-plugins/detox"]
}
}
33 changes: 14 additions & 19 deletions packages/sdk/react-native/example/e2e/starter.test.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,37 @@
import { by, device, element, expect, waitFor } from 'detox';

describe('Example', () => {
describe('given the example application', () => {
beforeAll(async () => {
await device.launchApp({
newInstance: true,
launchArgs: {
detoxURLBlacklistRegex: '\\("^https://clientstream.launchdarkly.com/meval"\\)',
// Detox will wait for HTTP requests to complete. This prevents detox from waiting for
// requests matching this URL to complete.
detoxURLBlacklistRegex: '\\("^https://clientstream.launchdarkly.com/meval.*"\\)',
},
});
});

// For speed, all tests are sequential and dependent.
// beforeEach(async () => {
// await device.reloadReactNative();
// });

afterAll(async () => {
await device.terminateApp();
});

test('app loads and renders correctly', async () => {
it('loads and renders correctly with default values', async () => {
await expect(element(by.text(/welcome to launchdarkly/i))).toBeVisible();
await expect(element(by.text(/my-boolean-flag-1: false/i))).toBeVisible();
await expect(element(by.text(/sample-feature: false/i))).toBeVisible();
});

test('identify', async () => {
await element(by.id('userKey')).typeText('test-user');
it('can identify and evaluate with non-default values', async () => {
const featureFlagKey = process.env.LAUNCHDARKLY_FLAG_KEY ?? 'sample-feature';
await element(by.id('userKey')).typeText('example-user-key');
await element(by.id('flagKey')).replaceText(featureFlagKey);
await element(by.text(/identify/i)).tap();

await waitFor(element(by.text(/my-boolean-flag-1: true/i)))
await waitFor(element(by.text(new RegExp(`${featureFlagKey}: true`))))
.toBeVisible()
.withTimeout(2000);
});

test('variation', async () => {
await element(by.id('flagKey')).replaceText('my-boolean-flag-2');
it('can set a flag and has defaults for a non-existent flag', async () => {
await element(by.id('flagKey')).replaceText('not-found-flag');

await waitFor(element(by.text(/my-boolean-flag-2: true/i)))
await waitFor(element(by.text(/not-found-flag: false/i)))
.toBeVisible()
.withTimeout(2000);
});
Expand Down
1 change: 1 addition & 0 deletions packages/sdk/react-native/example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@config-plugins/detox": "^8.0.0",
"@types/detox": "^18.1.0",
"@types/jest": "^29.5.11",
"@types/node": "^20.10.5",
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/react-native/example/src/welcome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ConnectionMode } from '@launchdarkly/js-client-sdk-common';
import { useBoolVariation, useLDClient } from '@launchdarkly/react-native-client-sdk';

export default function Welcome() {
const [flagKey, setFlagKey] = useState('my-boolean-flag-1');
const [flagKey, setFlagKey] = useState('sample-feature');
const [userKey, setUserKey] = useState('');
const flagValue = useBoolVariation(flagKey, false);
const ldc = useLDClient();
Expand Down
Loading