Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
cb6d810
Automatically start (and kill) an android emulator on `npm run e2e`.
randytarampi Apr 18, 2019
46ec27a
Specify a `adbExecTimeout`
randytarampi Apr 21, 2019
741d133
Add a bunch of android specific timeouts. And make them long.
randytarampi Apr 21, 2019
0ee6df7
`emulator:android:create` script shouldn't specify a device.
randytarampi Apr 21, 2019
71fedc9
Add `deviceReadyTimeout` and note how it and `androidDeviceReadyTimeo…
randytarampi Apr 21, 2019
4cc3387
`disableWindowAnimation` on Android clients in `CI` or `HEADLESS` envs.
randytarampi Apr 21, 2019
5ff010e
Prefer `jest-circus/runner` so we bail out when `beforeAll` explodes.
randytarampi Apr 21, 2019
8c21d70
`emulator:android:create` shouldn't require any input.
randytarampi Apr 22, 2019
b78cf78
Give appium some more time to install the application by `sleep`ing f…
randytarampi Apr 21, 2019
796a557
Note where I left off with Travis. (#2)
randytarampi Apr 22, 2019
9a83559
Add a separate `DRIVER_INITIALIZATION_TIMEOUT`.
randytarampi Apr 22, 2019
733cf1f
Set `DRIVER_INITIALIZATION_TIMEOUT` on `Android (local)` to 5 minutes.
randytarampi Apr 22, 2019
2341c2b
Explicitly allow the `E2E` `Android (local)` job to fail.
randytarampi Apr 22, 2019
d19f7fa
Don't provide any default `IOS_DEVICE_NAME` or `IOS_PLATFORM_VERSION`.
randytarampi Apr 23, 2019
5ce8b32
Note where I left off with perfecto.io.
randytarampi Apr 23, 2019
da188e2
chore: Set BrowserStack `appium_version` properly.
randytarampi May 8, 2019
4521a3a
chore: Reinstate WDIO tests.
randytarampi May 8, 2019
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,6 @@ buck-out/

# Bundle artifact
*.jsbundle

# Installation artifacts
bin/android-wait-for-emulator
100 changes: 100 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
notifications:
email: false
os: linux
dist: trusty # NOTE-RT: This needs to be `trusty`, android builds just hang on `xenial` :\
env:
global:
- ANDROID_PLATFORM_VERSION=9
- ANDROID_API_VERSION=28
- ANDROID_TOOLS_BUILD_VERSION=4333796 # NOTE-RT: To match `ANDROID_API_VERSION`
- ANDROID_BUILD_TOOLS_VERSION=28.0.3
- ANDROID_EMULATOR_PLATFORM_VERSION=6.0
- ANDROID_EMULATOR_API_VERSION=23
- ANDROID_EMULATOR_ABI=armeabi-v7a
jobs:
allow_failures:
- stage: E2E
name: Android (local)
include:
- language: node_js
node_js: 10
cache: yarn
- stage: E2E
name: Android (local)
sudo: required
language: android
jdk: oraclejdk8
cache: yarn
android:
components:
- tools
- platform-tools
before_install:
- export ANDROID_HOME=~/android-sdk
- export ANDROID_SDK_ROOT=$ANDROID_HOME
- |
if [ $TRAVIS_OS_NAME == "linux" ]; then
export DISPLAY=:99.0;
sh -e /etc/init.d/xvfb start;
sleep 3; # give xvfb some time to start
fi
- wget -q "https://dl.google.com/android/repository/sdk-tools-linux-$ANDROID_TOOLS_BUILD_VERSION.zip" -O android-sdk-tools.zip
- unzip -q android-sdk-tools.zip -d ${ANDROID_HOME}
- rm android-sdk-tools.zip
- PATH=${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/tools/bin:${ANDROID_HOME}/platform-tools
- mkdir -p ~/.android
- touch ~/.android/repositories.cfg
- yes | $ANDROID_HOME/tools/bin/sdkmanager --licenses
- |
$ANDROID_HOME/tools/bin/sdkmanager \
"tools" \
"platform-tools" \
"emulator" \
"build-tools;$ANDROID_BUILD_TOOLS_VERSION" \
"system-images;android-$ANDROID_EMULATOR_API_VERSION;google_apis;$ANDROID_EMULATOR_ABI" \
"patcher;v4" \
"platforms;android-$ANDROID_API_VERSION" \
"platforms;android-$ANDROID_EMULATOR_API_VERSION" \
"extras;google;google_play_services" \
"extras;google;m2repository" \
"extras;android;m2repository" > /dev/null
- openssl aes-256-cbc -K $encrypted_78b6aab41b54_key -iv $encrypted_78b6aab41b54_iv -in android/app/my-release-key.keystore.enc -out android/app/my-release-key.keystore -d
- export NODE_VERSION=${TRAVIS_NODE_VERSION:=10}
- nvm install $NODE_VERSION
- npm install -g yarn
install:
- yarn install
- npm run build:android
before_script:
- echo no | $ANDROID_HOME/tools/bin/avdmanager create avd -n $(node -p 'require("./package.json").name') -k "system-images;android-$ANDROID_EMULATOR_API_VERSION;google_apis;$ANDROID_EMULATOR_ABI" # NOTE-RT: This should really be `yarn run emulator:android:create`
# NOTE-RT: The following here should really be `npm run pree2e`
- EMU_ADDITIONAL_OPTIONS="-no-boot-anim -no-audio -no-window -memory 2048"
- EMU_PARAMS="-gpu off"
- $ANDROID_HOME/emulator/emulator -avd $(node -p 'require("./package.json").name') $EMU_PARAMS $EMU_ADDITIONAL_OPTIONS ${EMU_DEBUG} & echo $! > /tmp/$(node -p 'require("./package.json").name').emulator.android.pid; echo "Started Android emulator (`cat /tmp/$(node -p 'require("./package.json").name').emulator.android.pid`) from /tmp/$(node -p 'require("./package.json").name').emulator.android.pid"
- ./bin/android-wait-for-emulator
- $ANDROID_HOME/platform-tools/adb devices
- $ANDROID_HOME/platform-tools/adb shell input keyevent 82 &
- npm run appium:kill
- npm run appium:start
script:
- ANDROID_PLATFORM_VERSION=$ANDROID_EMULATOR_PLATFORM_VERSION JEST_TIMEOUT=600000 DEVICE_TIMEOUT=300000 DRIVER_INITIALIZATION_TIMEOUT=30000 npm run appium:test
after_script:
- npm run poste2e # NOTE-RT: Make sure we kill things and clean up
- android delete avd -n $(node -p 'require("./package.json").name') # NOTE-RT: This should really be `npm run emulator:android:delete`
# - name: E2E tests (iOS)
# os: osx
# language: objective-c
# podfile: ios/Podfile
# osx_image: xcode10.2
# cache:
# - yarn
# - cocoapods
# before_install:
# - export NODE_VERSION=${TRAVIS_NODE_VERSION:=10}
# - nvm install $NODE_VERSION
# - npm install -g yarn
# install:
# - yarn install
# - pushd ios && pod install && popd ios


37 changes: 25 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
### Make it run with

```bash
npm install
yarn install
```

### Create your Signed APK (For android)
Expand All @@ -25,18 +25,23 @@ keytool -genkeypair -v -keystore my-release-key.keystore -alias my-key-alias -ke
mv my-release-key.keystore android/app/

# Build the APK
npm run bs:android
yarn run bs:android
```

### Run tests
### Run tests locally
```bash
# Run unit & integration tests
npm test
yarn test

# Run E2E tests locally
npm run test:e2e
# Create an AVD to run tests against locally
yarn run emulator:android:create

# Run E2E tests locally
yarn run test:e2e
```

### Run tests in the cloud
```bash
# Setup for uploading to cloud services
export APP_PATH=$(pwd)/android/app/build/outputs/apk/release/app-release.apk
export APP_EXTENSION=apk
Expand All @@ -51,14 +56,22 @@ export TESTOBJECT_ACCESS_KEY=
export BROWSERSTACK_USERNAME=
export BROWSERSTACK_ACCESS_KEY=

# BrowserStack free trial per https://www.perfecto.io/perfecto-free-trial/
export PERFECTO_USERNAME=
export PERFECTO_PASSWORD=

# Upload and run tests on Sauce Labs (emulated)
npm run upload:e2e:sauce
npm run test:e2e:sauce
yarn run upload:e2e:sauce
yarn run test:e2e:sauce
# Upload and run tests on Sauce Labs (real devices)
npm run upload:e2e:testobject
npm run test:e2e:testobject
yarn run upload:e2e:testobject
yarn run test:e2e:testobject

# Run it on BrowserStack
npm run upload:e2e:browserstack
npm run test:e2e:browserstack
yarn run upload:e2e:browserstack
yarn run test:e2e:browserstack

# Run it on Perfecto
yarn run upload:e2e:perfecto
yarn run test:e2e:perfecto
```
Binary file added android/app/my-release-key.keystore.enc
Binary file not shown.
7 changes: 4 additions & 3 deletions e2e/__tests__/appium-test-wd.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import path from 'path';
import * as wd from 'wd';
import * as capabilities from '../capabilities';
import {DEVICE_TIMEOUT, JEST_TIMEOUT, TARGET_PLATFORM} from '../constants';
import {DEVICE_TIMEOUT, DRIVER_INITIALIZATION_TIMEOUT, JEST_TIMEOUT, TARGET_PLATFORM} from '../constants';

jest.setTimeout(JEST_TIMEOUT);

describe('Create Android session (wd)', () => {
let driver;

beforeAll(async () => {
jest.setTimeout(JEST_TIMEOUT);

const {capabilities: deviceConfig, ...serverConfig} = capabilities[TARGET_PLATFORM];

Expand All @@ -23,7 +24,7 @@ describe('Create Android session (wd)', () => {
// Start the session
await driver.init(deviceConfig)
.setImplicitWaitTimeout(DEVICE_TIMEOUT)
.sleep(5000);
.sleep(DRIVER_INITIALIZATION_TIMEOUT);

console.info('[beforeAll] driver initialized %j', driver);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as wdio from 'webdriverio';
import * as capabilities from '../capabilities';
import {JEST_TIMEOUT, TARGET_PLATFORM} from '../constants';
import path from 'path';
import path from 'path'
import * as wdio from 'webdriverio'
import * as capabilities from '../capabilities'
import {JEST_TIMEOUT, TARGET_PLATFORM} from '../constants'

xdescribe('Create Android session (wdio)', () => {
let client;
Expand Down
79 changes: 74 additions & 5 deletions e2e/capabilities.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import {version as appiumVersion} from 'appium/package.json'
import {name} from '../package.json'
import {
ANDROID_APPLICATION_PATH,
ANDROID_AVD_ARGS,
ANDROID_AVD_NAME,
ANDROID_DEVICE_NAME,
ANDROID_PLATFORM_VERSION,
APPIUM_HOST,
Expand All @@ -12,9 +16,7 @@ import {
IOS_DEVICE_NAME,
IOS_PLATFORM_VERSION,
TARGET_PLATFORM
} from './constants';
import {name} from '../package.json';
import {version as appiumVersion} from 'appium/package.json';
} from './constants'

const common = {
hostname: APPIUM_HOST,
Expand All @@ -26,7 +28,8 @@ const common = {
appiumVersion,
waitforTimeout: DEVICE_TIMEOUT,
commandTimeout: DEVICE_TIMEOUT,
newCommandTimeout: DEVICE_TIMEOUT
newCommandTimeout: DEVICE_TIMEOUT,
isHeadless: process.env.HEADLESS || process.env.CI || false
}
};

Expand All @@ -38,6 +41,8 @@ if (process.env.BROWSERSTACK) {
common.capabilities['browserstack.debug'] = true;
common.capabilities['browserstack.video'] = true;
common.capabilities['browserstack.networkLogs'] = true;
common.capabilities.appiumVersion = '1.9.1' // FIXME-RT: Ugh. 🤞 this doesn't break 'cause we're actually on 1.12.1
common.capabilities['browserstack.appium_version'] = common.capabilities.appiumVersion
common.capabilities.build = `${name}:${TARGET_PLATFORM}`;
}

Expand All @@ -58,6 +63,12 @@ if (process.env.TESTOBJECT) {
common.capabilities.privateDevicesOnly = process.env.TESTOBJECT_PRIVATE_DEVICES_ONLY ? JSON.parse(process.env.TESTOBJECT_PRIVATE_DEVICES_ONLY) : false;
}

if (process.env.PERFECTO) {
// NOTE-RT: These seem to be a constant requirement, but my testing seems to indicate that it's not required for auth. And still the expected `user` value isn't consistent.
common.capabilities.user = common.user; // NOTE-RT: Per https://developers.perfectomobile.com/pages/viewpage.action?pageId=11174626, but their broken JS WDIO example wants a `@perfectomobile.com` appended to the end... https://github.com/PerfectoCode/Samples/tree/master/Appium/JavaScript#getting-started
common.capabilities.password = common.pwd;
}

export const android = {
...common,
capabilities: {
Expand All @@ -66,7 +77,19 @@ export const android = {
automationName: 'UiAutomator2',
deviceName: ANDROID_DEVICE_NAME,
platformVersion: ANDROID_PLATFORM_VERSION,
app: ANDROID_APPLICATION_PATH
app: ANDROID_APPLICATION_PATH,
adbExecTimeout: 30,
deviceReadyTimeout: DEVICE_TIMEOUT / 1000, // NOTE-RT: This is actually just in seconds
androidDeviceReadyTimeout: DEVICE_TIMEOUT / 1000, // NOTE-RT: This is actually just in seconds
androidInstallTimeout: DEVICE_TIMEOUT,
appWaitDuration: 10,
avdLaunchTimeout: DEVICE_TIMEOUT,
avdReadyTimeout: DEVICE_TIMEOUT,
uiautomator2ServerInstallTimeout: DEVICE_TIMEOUT,
uiautomator2ServerLaunchTimeout: DEVICE_TIMEOUT,
disableWindowAnimation: process.env.HEADLESS || process.env.CI || false,
avd: ANDROID_AVD_NAME,
avdArgs: ANDROID_AVD_ARGS
}
};

Expand Down Expand Up @@ -94,11 +117,22 @@ if (process.env.SAUCE) {
delete android.capabilities.waitforTimeout;
delete android.capabilities.commandTimeout;
delete android.capabilities.newCommandTimeout;
delete android.capabilities.adbExecTimeout;
delete android.capabilities.deviceReadyTimeout;
delete android.capabilities.androidDeviceReadyTimeout;
delete android.capabilities.androidInstallTimeout;
delete android.capabilities.appWaitDuration;
delete android.capabilities.avdLaunchTimeout;
delete android.capabilities.avdReadyTimeout;
delete android.capabilities.uiautomator2ServerInstallTimeout;
delete android.capabilities.uiautomator2ServerLaunchTimeout;
delete android.capabilities.disableWindowAnimation;
android.capabilities.automationName = android.capabilities.automationName.toLowerCase(); // NOTE-RT: Sauce Labs wants this to be lowercase for some reason

delete ios.capabilities.waitforTimeout;
delete ios.capabilities.commandTimeout;
delete ios.capabilities.newCommandTimeout;
delete ios.capabilities.adbExecTimeout;
}

if (process.env.TESTOBJECT) {
Expand All @@ -107,4 +141,39 @@ if (process.env.TESTOBJECT) {

delete ios.capabilities.deviceName; // FIXME-RT: Per the above
delete ios.capabilities.platformVersion; // FIXME-RT: Per the above
}

if (process.env.PERFECTO) {
delete android.capabilities.appiumVersion;
delete android.capabilities.waitforTimeout;
delete android.capabilities.commandTimeout;
delete android.capabilities.newCommandTimeout;
delete android.capabilities.adbExecTimeout;
delete android.capabilities.deviceReadyTimeout;
delete android.capabilities.androidDeviceReadyTimeout;
delete android.capabilities.androidInstallTimeout;
delete android.capabilities.appWaitDuration;
delete android.capabilities.avdLaunchTimeout;
delete android.capabilities.avdReadyTimeout;
delete android.capabilities.uiautomator2ServerInstallTimeout;
delete android.capabilities.uiautomator2ServerLaunchTimeout;
delete android.capabilities.disableWindowAnimation;
delete android.capabilities.deviceName;
delete android.capabilities.isHeadless;
android.capabilities.automationInfrastructure = 'UIAutomator2'; // NOTE-RT: https://developers.perfectomobile.com/display/TT/Select+a+Device+Based+on+Automation+Infrastructure differs from https://developers.perfectomobile.com/display/PD/Supported+Appium+Capabilities...
android.capabilities.automationName = android.capabilities.automationInfrastructure; // NOTE-RT: Per https://developers.perfectomobile.com/display/PD/Supported+Appium+Capabilities
android.capabilities.appPackage = 'com.apktest'; // NOTE-RT: Per https://developers.perfectomobile.com/display/PD/Supported+Appium+Capabilities#SupportedAppiumCapabilities-Android
android.capabilities.appActivity = '.MainActivity'; // NOTE-RT: Per https://developers.perfectomobile.com/display/PD/Supported+Appium+Capabilities#SupportedAppiumCapabilities-Android
// NOTE-RT: I shouldn't need these according to https://developers.perfectomobile.com/pages/viewpage.action?pageId=11174626 I can't get a matching capability
// android.capabilities.manufacturer = "Samsung";
// android.capabilities.model = "Galaxy S10e";
// android.capabilities.resolution = "1080x2280";
// android.capabilities.deviceName = 'R58M21YXQBL';

delete ios.capabilities.appiumVersion;
delete ios.capabilities.waitforTimeout;
delete ios.capabilities.commandTimeout;
delete ios.capabilities.newCommandTimeout;
delete ios.capabilities.adbExecTimeout;
// ios.capabilities.deviceName = '17E62A82172A36293D316B522623430FD9D7EA0C';
}
21 changes: 13 additions & 8 deletions e2e/constants.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
import path from 'path';
import {name} from '../package.json';

export const ANDROID_DEVICE_NAME = process.env.ANDROID_DEVICE_NAME || 'Nexus_5X_API_28';
export const ANDROID_PLATFORM_VERSION = process.env.ANDROID_PLATFORM_VERSION || '9';
export const ANDROID_AVD_NAME = process.env.ANDROID_AVD_NAME;
export const ANDROID_AVD_ARGS = process.env.ANDROID_AVD_ARGS;
export const ANDROID_DEVICE_NAME = process.env.ANDROID_DEVICE_NAME || ANDROID_AVD_NAME || name;
export const ANDROID_PLATFORM_VERSION = process.env.ANDROID_PLATFORM_VERSION;
export const ANDROID_APPLICATION_PATH = process.env.ANDROID_APPLICATION_PATH || path.resolve(__dirname, '../android/app/build/outputs/apk/release/app-release.apk');

export const DEVICE_TIMEOUT = process.env.DEVICE_TIMEOUT || 90 * 1000;
export const DEVICE_TIMEOUT = process.env.DEVICE_TIMEOUT || 1.5 * 60 * 1000;

export const IOS_DEVICE_NAME = process.env.IOS_DEVICE_NAME || 'iPhone SE';
export const IOS_PLATFORM_VERSION = process.env.IOS_PLATFORM_VERSION || '12.1';
export const DRIVER_INITIALIZATION_TIMEOUT = process.env.DRIVER_INITIALIZATION_TIMEOUT || 5 * 1000;

export const IOS_DEVICE_NAME = process.env.IOS_DEVICE_NAME;
export const IOS_PLATFORM_VERSION = process.env.IOS_PLATFORM_VERSION;
export const IOS_APPLICATION_PATH = process.env.IOS_APPLICATION_PATH; // FIXME-RT: Provide a real directory here

export const APPIUM_HOST = process.env.APPIUM_HOST || 'localhost';
export const APPIUM_LOG_LEVEL = process.env.APPIUM_LOG_LEVEL || 'debug';
export const APPIUM_PASSWORD = process.env.APPIUM_PASSWORD || undefined;
export const APPIUM_PASSWORD = process.env.APPIUM_PASSWORD;
export const APPIUM_PORT = process.env.APPIUM_PORT && Number(process.env.APPIUM_PORT) || 4723;
export const APPIUM_USER = process.env.APPIUM_USER || undefined;
export const APPIUM_USER = process.env.APPIUM_USER;

export const TARGET_PLATFORM = process.env.TARGET_PLATFORM || 'android';

export const JEST_TIMEOUT = process.env.JEST_TIMEOUT || 180 * 1000;
export const JEST_TIMEOUT = process.env.JEST_TIMEOUT || 3 * 60 * 1000;
1 change: 1 addition & 0 deletions e2e/jest.config.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"testRunner": "jest-circus/runner",
"verbose": true
}
1 change: 1 addition & 0 deletions jest.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
"/node_modules/",
"/e2e/"
],
"testRunner": "jest-circus/runner",
"verbose": true
}
Loading