From a83fdb7bb8a80170b7f829f0c0774c863ca9ea2b Mon Sep 17 00:00:00 2001 From: David Whitlark <110626950+dawhitla@users.noreply.github.com> Date: Thu, 15 Jun 2023 10:46:13 -0400 Subject: [PATCH] ci: starting with android ci e2e tests (#139) * fix: removing leading whitespace from ruby version file * ci: fixing e2e testing scripts for android / ios * ci: starting with android ci e2e tests --- .detoxrc.json | 21 +-- .github/workflows/pr.yaml | 54 +++++++ .nvmrc | 1 + .ruby-version | 1 - e2e/advancedPlayer.e2e.js | 2 - e2e/playgroundPlayer.e2e.js | 208 ++++++++++++++++++++++++- e2e/playgroundPlayerEvents.e2e.js | 247 +----------------------------- e2e/simplePlayer.e2e.js | 2 - e2e/utils.js | 8 + package.json | 2 +- 10 files changed, 274 insertions(+), 272 deletions(-) create mode 100644 .github/workflows/pr.yaml create mode 100644 .nvmrc diff --git a/.detoxrc.json b/.detoxrc.json index e7d4f69..7586491 100644 --- a/.detoxrc.json +++ b/.detoxrc.json @@ -28,20 +28,13 @@ } }, "devices": { - "simulator": { + "ios": { "type": "ios.simulator", "device": { - "type": "iPhone 11" + "type": "iPhone 14" } }, - "emulator": { - "type": "android.emulator", - "device": { - "avdName": "Pixel_3a_API_30_x86" - }, - "utilBinaryPaths": ["./test-butler-app.apk"] - }, - "ci-emulator": { + "android": { "type": "android.emulator", "device": { "avdName": "TestingAVD" @@ -51,19 +44,19 @@ }, "configurations": { "ios": { - "device": "simulator", + "device": "ios", "app": "ios.debug" }, "ios.sim.release": { - "device": "simulator", + "device": "ios", "app": "ios.release" }, "android": { - "device": "emulator", + "device": "android", "app": "android.debug" }, "android.emu.release": { - "device": "ci-emulator", + "device": "android", "app": "android.release" } } diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml new file mode 100644 index 0000000..2c81558 --- /dev/null +++ b/.github/workflows/pr.yaml @@ -0,0 +1,54 @@ +name: PR checks + +on: + workflow_dispatch: + pull_request: + branches: + - main + +jobs: + android: + runs-on: macos-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + cache: gradle + - uses: actions/setup-node@v3 + with: + node-version-file: '.nvmrc' + cache: 'yarn' + + - name: yarn + run: yarn + + - name: e2e:build:android:release + run: yarn e2e:build:android:release + + - name: get device name + id: device + run: node -e "console.log('AVD_NAME=' + require('./.detoxrc').devices.android.device.avdName)" >> $GITHUB_OUTPUT + + - name: get android device image + run: | + echo "y" | $ANDROID_HOME/tools/bin/sdkmanager --install "system-images;android-31;x86" + echo "no" | $ANDROID_HOME/tools/bin/avdmanager create avd --force --name emu --device "${{ steps.device.outputs.AVD_NAME }}" -k 'system-images;android-31;x86' + $ANDROID_HOME/emulator/emulator -list-avds + + - name: start android device + timeout-minutes: 10 + continue-on-error: true + run: | + echo "starting emulator" + nohup $ANDROID_HOME/emulator/emulator -avd emu -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none & + $ANDROID_HOME/platform-tools/adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed | tr -d '\r') ]]; do sleep 1; done; input keyevent 82' + $ANDROID_HOME/platform-tools/adb devices + echo "emulator started" + + - name: e2e:test:android:release + run: yarn e2e:test:android:release + + + diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..3b3a53b --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +16.20.0 \ No newline at end of file diff --git a/.ruby-version b/.ruby-version index e018890..a603bb5 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1,2 +1 @@ - 2.7.5 diff --git a/e2e/advancedPlayer.e2e.js b/e2e/advancedPlayer.e2e.js index 8cf4674..c9c4bb5 100644 --- a/e2e/advancedPlayer.e2e.js +++ b/e2e/advancedPlayer.e2e.js @@ -2,8 +2,6 @@ import { expectNativePlayerToBeVisible, togglePlayPauseVideo } from './utils'; -jest.retryTimes(3); - describe('Advanced player', () => { beforeAll(async () => { await device.launchApp(); diff --git a/e2e/playgroundPlayer.e2e.js b/e2e/playgroundPlayer.e2e.js index 69ae787..f3c4002 100644 --- a/e2e/playgroundPlayer.e2e.js +++ b/e2e/playgroundPlayer.e2e.js @@ -5,14 +5,10 @@ import { atLeastOneLogIsVisible, navigateToPlayground, togglePlayPauseVideo, + scrollToModalBottom, + TIMEOUT, } from './utils'; -const TIMEOUT = 300000; - -jest.setTimeout(1200000); - -jest.retryTimes(3); - describe('Playground player', () => { beforeAll(async () => { await device.launchApp(); @@ -95,4 +91,204 @@ describe('Playground player', () => { .not.toHaveText('live') .withTimeout(TIMEOUT); }); + + it("Player doesn't crash after setting auto quality", async () => { + await expectNativePlayerToBeVisible(); + await togglePlayPauseVideo(); + + await waitFor(element(by.id('settingsIcon'))) + .toBeVisible() + .withTimeout(TIMEOUT); + await element(by.id('settingsIcon')).tap(); + await element(by.text('720P').withAncestor(by.id('qualitiesPicker'))).tap(); + await element(by.text('AUTO').withAncestor(by.id('qualitiesPicker'))).tap(); + await element(by.id('closeIcon')).tap(); + + await expectNativePlayerToBeVisible(); // Not a crash + }); + + it("Player doesn't crash after changing muted property", async () => { + await expectNativePlayerToBeVisible(); + await togglePlayPauseVideo(); + + await waitFor(element(by.id('settingsIcon'))) + .toBeVisible() + .withTimeout(TIMEOUT); + await element(by.id('settingsIcon')).tap(); + await scrollToModalBottom(); + await element(by.id('muted')).tap(); + await element(by.id('closeIcon')).tap(); + + await expectNativePlayerToBeVisible(); // Not a crash + }); + + it("Player doesn't crash after changing autoplay property", async () => { + await expectNativePlayerToBeVisible(); + await togglePlayPauseVideo(); + + await waitFor(element(by.id('settingsIcon'))) + .toBeVisible() + .withTimeout(TIMEOUT); + await element(by.id('settingsIcon')).tap(); + await scrollToModalBottom(); + await element(by.id('autoplay')).tap(); + await element(by.id('closeIcon')).tap(); + + await expectNativePlayerToBeVisible(); // Not a crash + }); + + it("Player doesn't crash after changing paused property", async () => { + await expectNativePlayerToBeVisible(); + await togglePlayPauseVideo(); + + await waitFor(element(by.id('settingsIcon'))) + .toBeVisible() + .withTimeout(TIMEOUT); + await element(by.id('settingsIcon')).tap(); + await scrollToModalBottom(); + await element(by.id('paused')).tap(); + await element(by.id('closeIcon')).tap(); + + await expectNativePlayerToBeVisible(); // Not a crash + }); + + it("Player doesn't crash after changing liveLowLatency property", async () => { + await expectNativePlayerToBeVisible(); + await togglePlayPauseVideo(); + + await waitFor(element(by.id('settingsIcon'))) + .toBeVisible() + .withTimeout(TIMEOUT); + await element(by.id('settingsIcon')).tap(); + await scrollToModalBottom(); + await element(by.id('liveLowLatency')).tap(); + await element(by.id('closeIcon')).tap(); + + await expectNativePlayerToBeVisible(); // Not a crash + }); + + it("Player doesn't crash after changing autoQuality property", async () => { + await expectNativePlayerToBeVisible(); + await togglePlayPauseVideo(); + + await waitFor(element(by.id('settingsIcon'))) + .toBeVisible() + .withTimeout(TIMEOUT); + await element(by.id('settingsIcon')).tap(); + await scrollToModalBottom(); + await element(by.id('autoQuality')).tap(); + await element(by.id('closeIcon')).tap(); + + await expectNativePlayerToBeVisible(); // Not a crash + }); + + it("Player doesn't crash after changing log level", async () => { + await expectNativePlayerToBeVisible(); + await togglePlayPauseVideo(); + + await waitFor(element(by.id('settingsIcon'))) + .toBeVisible() + .withTimeout(TIMEOUT); + await element(by.id('settingsIcon')).tap(); + await scrollToModalBottom(); + await element(by.text('DEBUG').withAncestor(by.id('logLevelPicker'))).tap(); + await element(by.id('closeIcon')).tap(); + + await expectNativePlayerToBeVisible(); // Not a crash + }); + + it("Player doesn't crash after changing autoMaxQuality", async () => { + await expectNativePlayerToBeVisible(); + await togglePlayPauseVideo(); + + await waitFor(element(by.id('settingsIcon'))) + .toBeVisible() + .withTimeout(TIMEOUT); + await element(by.id('settingsIcon')).tap(); + await scrollToModalBottom(); + await element( + by.text('720P').withAncestor(by.id('autoMaxQualityPicker')) + ).tap(); + await element( + by.text('AUTO').withAncestor(by.id('autoMaxQualityPicker')) + ).tap(); + await element(by.id('closeIcon')).tap(); + + await expectNativePlayerToBeVisible(); // Not a crash + }); + + it("Player doesn't crash after changing playback rate", async () => { + await expectNativePlayerToBeVisible(); + await togglePlayPauseVideo(); + + await waitFor(element(by.id('settingsIcon'))) + .toBeVisible() + .withTimeout(TIMEOUT); + await element(by.id('settingsIcon')).tap(); + // await scrollToModalBottom(); + await element(by.id('playbackRate')).replaceText('2'); + await element(by.id('closeIcon')).tap(); + + await expectNativePlayerToBeVisible(); // Not a crash + }); + + it("Player doesn't crash after changing progress interval", async () => { + await expectNativePlayerToBeVisible(); + await togglePlayPauseVideo(); + + await waitFor(element(by.id('settingsIcon'))) + .toBeVisible() + .withTimeout(TIMEOUT); + await element(by.id('settingsIcon')).tap(); + await scrollToModalBottom(); + await element(by.id('progressInterval')).replaceText('1'); + await element(by.id('closeIcon')).tap(); + + await expectNativePlayerToBeVisible(); // Not a crash + }); + + it("Player doesn't crash after changing volume", async () => { + await expectNativePlayerToBeVisible(); + await togglePlayPauseVideo(); + + await waitFor(element(by.id('settingsIcon'))) + .toBeVisible() + .withTimeout(TIMEOUT); + await element(by.id('settingsIcon')).tap(); + await scrollToModalBottom(); + await element(by.id('volume')).replaceText('0.5'); + await element(by.id('closeIcon')).tap(); + + await expectNativePlayerToBeVisible(); // Not a crash + }); + + it("Player doesn't crash after changing initialBufferDuration", async () => { + await expectNativePlayerToBeVisible(); + await togglePlayPauseVideo(); + + await waitFor(element(by.id('settingsIcon'))) + .toBeVisible() + .withTimeout(TIMEOUT); + await element(by.id('settingsIcon')).tap(); + await scrollToModalBottom() + await element(by.id('initialBufferDuration')).replaceText('4.0'); + await element(by.id('closeIcon')).tap(); + + await expectNativePlayerToBeVisible(); // Not a crash + }); + + it("Player doesn't crash after changing pauseInBackground", async () => { + await expectNativePlayerToBeVisible(); + await togglePlayPauseVideo(); + + await waitFor(element(by.id('settingsIcon'))) + .toBeVisible() + .withTimeout(TIMEOUT); + await element(by.id('settingsIcon')).tap(); + await scrollToModalBottom(); + await element(by.id('pauseInBackground')).tap(); + await element(by.id('closeIcon')).tap(); + + await expectNativePlayerToBeVisible(); // Not a crash + }); }); diff --git a/e2e/playgroundPlayerEvents.e2e.js b/e2e/playgroundPlayerEvents.e2e.js index 4f6d8c0..05de735 100644 --- a/e2e/playgroundPlayerEvents.e2e.js +++ b/e2e/playgroundPlayerEvents.e2e.js @@ -5,15 +5,9 @@ import { atLeastOneLogIsVisible, navigateToPlayground, togglePlayPauseVideo, + TIMEOUT, } from './utils'; -const TIMEOUT = 300000; - -jest.setTimeout(1200000); - -jest.retryTimes(3); - - describe('Playground player events', () => { beforeAll(async () => { await device.launchApp(); @@ -148,243 +142,4 @@ describe('Playground player events', () => { await atLeastOneLogIsVisible('load started', TIMEOUT); }); - it("Player doesn't crash after setting auto quality", async () => { - await expectNativePlayerToBeVisible(); - await togglePlayPauseVideo(); - - await waitFor(element(by.id('settingsIcon'))) - .toBeVisible() - .withTimeout(TIMEOUT); - await element(by.id('settingsIcon')).tap(); - await waitFor(element(by.id('qualitiesPicker'))) - .toBeVisible() - .whileElement(by.id('modalScrollView')) - .scroll(50, 'down'); - await element(by.text('720P').withAncestor(by.id('qualitiesPicker'))).tap(); - await element(by.text('AUTO').withAncestor(by.id('qualitiesPicker'))).tap(); - await element(by.id('closeIcon')).tap(); - - await expectNativePlayerToBeVisible(); // Not a crash - }); - - it("Player doesn't crash after changing muted property", async () => { - await expectNativePlayerToBeVisible(); - await togglePlayPauseVideo(); - - await waitFor(element(by.id('settingsIcon'))) - .toBeVisible() - .withTimeout(TIMEOUT); - await element(by.id('settingsIcon')).tap(); - await waitFor(element(by.id('muted'))) - .toBeVisible() - .whileElement(by.id('modalScrollView')) - .scroll(50, 'down'); - await element(by.id('muted')).tap(); - await element(by.id('closeIcon')).tap(); - - await expectNativePlayerToBeVisible(); // Not a crash - }); - - it("Player doesn't crash after changing autoplay property", async () => { - await expectNativePlayerToBeVisible(); - await togglePlayPauseVideo(); - - await waitFor(element(by.id('settingsIcon'))) - .toBeVisible() - .withTimeout(TIMEOUT); - await element(by.id('settingsIcon')).tap(); - await waitFor(element(by.id('autoplay'))) - .toBeVisible() - .whileElement(by.id('modalScrollView')) - .scroll(50, 'down'); - await element(by.id('autoplay')).tap(); - await element(by.id('closeIcon')).tap(); - - await expectNativePlayerToBeVisible(); // Not a crash - }); - - it("Player doesn't crash after changing paused property", async () => { - await expectNativePlayerToBeVisible(); - await togglePlayPauseVideo(); - - await waitFor(element(by.id('settingsIcon'))) - .toBeVisible() - .withTimeout(TIMEOUT); - await element(by.id('settingsIcon')).tap(); - await waitFor(element(by.id('paused'))) - .toBeVisible() - .whileElement(by.id('modalScrollView')) - .scroll(50, 'down'); - await element(by.id('paused')).tap(); - await element(by.id('closeIcon')).tap(); - - await expectNativePlayerToBeVisible(); // Not a crash - }); - - it("Player doesn't crash after changing liveLowLatency property", async () => { - await expectNativePlayerToBeVisible(); - await togglePlayPauseVideo(); - - await waitFor(element(by.id('settingsIcon'))) - .toBeVisible() - .withTimeout(TIMEOUT); - await element(by.id('settingsIcon')).tap(); - await waitFor(element(by.id('liveLowLatency'))) - .toBeVisible() - .whileElement(by.id('modalScrollView')) - .scroll(50, 'down'); - await element(by.id('liveLowLatency')).tap(); - await element(by.id('closeIcon')).tap(); - - await expectNativePlayerToBeVisible(); // Not a crash - }); - - it("Player doesn't crash after changing autoQuality property", async () => { - await expectNativePlayerToBeVisible(); - await togglePlayPauseVideo(); - - await waitFor(element(by.id('settingsIcon'))) - .toBeVisible() - .withTimeout(TIMEOUT); - await element(by.id('settingsIcon')).tap(); - await waitFor(element(by.id('autoQuality'))) - .toBeVisible() - .whileElement(by.id('modalScrollView')) - .scroll(50, 'down'); - await element(by.id('autoQuality')).tap(); - await element(by.id('closeIcon')).tap(); - - await expectNativePlayerToBeVisible(); // Not a crash - }); - - it("Player doesn't crash after changing log level", async () => { - await expectNativePlayerToBeVisible(); - await togglePlayPauseVideo(); - - await waitFor(element(by.id('settingsIcon'))) - .toBeVisible() - .withTimeout(TIMEOUT); - await element(by.id('settingsIcon')).tap(); - await waitFor(element(by.id('logLevelPicker'))) - .toBeVisible() - .whileElement(by.id('modalScrollView')) - .scroll(150, 'down'); - await element(by.text('DEBUG').withAncestor(by.id('logLevelPicker'))).tap(); - await element(by.id('closeIcon')).tap(); - - await expectNativePlayerToBeVisible(); // Not a crash - }); - - it("Player doesn't crash after changing autoMaxQuality", async () => { - await expectNativePlayerToBeVisible(); - await togglePlayPauseVideo(); - - await waitFor(element(by.id('settingsIcon'))) - .toBeVisible() - .withTimeout(TIMEOUT); - await element(by.id('settingsIcon')).tap(); - await waitFor(element(by.id('autoMaxQualityPicker'))) - .toBeVisible() - .whileElement(by.id('modalScrollView')) - .scroll(150, 'down'); - await element( - by.text('720P').withAncestor(by.id('autoMaxQualityPicker')) - ).tap(); - await element( - by.text('AUTO').withAncestor(by.id('autoMaxQualityPicker')) - ).tap(); - await element(by.id('closeIcon')).tap(); - - await expectNativePlayerToBeVisible(); // Not a crash - }); - - it("Player doesn't crash after changing playback rate", async () => { - await expectNativePlayerToBeVisible(); - await togglePlayPauseVideo(); - - await waitFor(element(by.id('settingsIcon'))) - .toBeVisible() - .withTimeout(TIMEOUT); - await element(by.id('settingsIcon')).tap(); - await waitFor(element(by.id('playbackRate'))) - .toBeVisible() - .whileElement(by.id('modalScrollView')) - .scroll(50, 'down'); - await element(by.id('playbackRate')).replaceText('2'); - await element(by.id('closeIcon')).tap(); - - await expectNativePlayerToBeVisible(); // Not a crash - }); - - it("Player doesn't crash after changing progress interval", async () => { - await expectNativePlayerToBeVisible(); - await togglePlayPauseVideo(); - - await waitFor(element(by.id('settingsIcon'))) - .toBeVisible() - .withTimeout(TIMEOUT); - await element(by.id('settingsIcon')).tap(); - await waitFor(element(by.id('progressInterval'))) - .toBeVisible() - .whileElement(by.id('modalScrollView')) - .scroll(50, 'down'); - await element(by.id('progressInterval')).replaceText('1'); - await element(by.id('closeIcon')).tap(); - - await expectNativePlayerToBeVisible(); // Not a crash - }); - - it("Player doesn't crash after changing volume", async () => { - await expectNativePlayerToBeVisible(); - await togglePlayPauseVideo(); - - await waitFor(element(by.id('settingsIcon'))) - .toBeVisible() - .withTimeout(TIMEOUT); - await element(by.id('settingsIcon')).tap(); - await waitFor(element(by.id('volume'))) - .toBeVisible() - .whileElement(by.id('modalScrollView')) - .scroll(50, 'down'); - await element(by.id('volume')).replaceText('0.5'); - await element(by.id('closeIcon')).tap(); - - await expectNativePlayerToBeVisible(); // Not a crash - }); - - it("Player doesn't crash after changing initialBufferDuration", async () => { - await expectNativePlayerToBeVisible(); - await togglePlayPauseVideo(); - - await waitFor(element(by.id('settingsIcon'))) - .toBeVisible() - .withTimeout(TIMEOUT); - await element(by.id('settingsIcon')).tap(); - await waitFor(element(by.id('initialBufferDuration'))) - .toBeVisible() - .whileElement(by.id('modalScrollView')) - .scroll(50, 'down'); - await element(by.id('initialBufferDuration')).replaceText('4.0'); - await element(by.id('closeIcon')).tap(); - - await expectNativePlayerToBeVisible(); // Not a crash - }); - - it("Player doesn't crash after changing pauseInBackground", async () => { - await expectNativePlayerToBeVisible(); - await togglePlayPauseVideo(); - - await waitFor(element(by.id('settingsIcon'))) - .toBeVisible() - .withTimeout(TIMEOUT); - await element(by.id('settingsIcon')).tap(); - await waitFor(element(by.id('pauseInBackground'))) - .toBeVisible() - .whileElement(by.id('modalScrollView')) - .scroll(50, 'down'); - await element(by.id('pauseInBackground')).tap(); - await element(by.id('closeIcon')).tap(); - - await expectNativePlayerToBeVisible(); // Not a crash - }); }); diff --git a/e2e/simplePlayer.e2e.js b/e2e/simplePlayer.e2e.js index 64b09a8..c090f7d 100644 --- a/e2e/simplePlayer.e2e.js +++ b/e2e/simplePlayer.e2e.js @@ -2,8 +2,6 @@ import { expectNativePlayerToBeVisible } from './utils'; -jest.retryTimes(3); - describe('Simple player', () => { beforeAll(async () => { await device.launchApp(); diff --git a/e2e/utils.js b/e2e/utils.js index 3039c3e..b8b5dc3 100644 --- a/e2e/utils.js +++ b/e2e/utils.js @@ -1,5 +1,7 @@ /* eslint-env detox/detox, jest */ +export const TIMEOUT = 30000; + export const expectNativePlayerToBeVisible = async () => { await waitFor( element( @@ -48,3 +50,9 @@ export const togglePlayPauseVideo = async () => { await element(by.id('playPauseButton')).tap(); }; + +export const scrollToModalBottom = async (scrollDown = 400) => { + await element(by.id('modalScrollView')).scroll(scrollDown, 'down'); + // wait for anim to finish ?? + await sleep(500); +}; \ No newline at end of file diff --git a/package.json b/package.json index a9c17da..4309cd6 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "e2e:test:ios": "detox test --configuration ios --take-screenshots failing --loglevel verbose", "e2e:build:android:release": "detox build --configuration android.emu.release", "e2e:build:ios:release": "detox build --configuration ios.sim.release", - "e2e:test:android:release": "yarn get:testbutler && detox test --configuration android.emu.release --record-logs all --loglevel trace", + "e2e:test:android:release": "yarn get:testbutler && detox test --configuration android.emu.release --take-screenshots failing", "e2e:test:ios:release": "detox test --configuration ios.sim.release --take-screenshots failing", "get:testbutler": "curl -f -o ./test-butler-app.apk https://repo1.maven.org/maven2/com/linkedin/testbutler/test-butler-app/2.2.1/test-butler-app-2.2.1.apk" },