diff --git a/lib/devices/android/adb.js b/lib/devices/android/adb.js index af509ae2c7e..68185791961 100644 --- a/lib/devices/android/adb.js +++ b/lib/devices/android/adb.js @@ -591,6 +591,21 @@ ADB.prototype.isDeviceConnected = function(cb) { }); }; +/* + * Check whether the ADB connection is up + */ +ADB.prototype.ping = function(cb) { + this.shell("echo 'ping'", function(err, stdout) { + if (!err && stdout.indexOf("ping") === 0) { + cb(null, true); + } else if (err) { + cb(err); + } else { + cb(new Error("ADB ping failed, returned: " + stdout)); + } + }); +}; + ADB.prototype.setDeviceId = function(deviceId) { this.curDeviceId = deviceId; this.adbCmd = this.adb + " -s " + deviceId; @@ -661,6 +676,16 @@ ADB.prototype.restartAdb = function(cb) { }); }; + +ADB.prototype.restart = function(cb) { + async.series([ + this.stopLogcat.bind(this) + , this.restartAdb.bind(this) + , this.waitForDevice.bind(this) + , this.startLogcat.bind(this) + ], cb); +}; + ADB.prototype.startLogcat = function(cb) { if (this.logcat !== null) { cb(new Error("Trying to start logcat capture but it's already started!")); @@ -678,6 +703,7 @@ ADB.prototype.startLogcat = function(cb) { ADB.prototype.stopLogcat = function(cb) { if (this.logcat !== null) { this.logcat.stopCapture(cb); + this.logcat = null; } }; diff --git a/lib/devices/android/android-common.js b/lib/devices/android/android-common.js index 6c96f464de8..8427f938242 100644 --- a/lib/devices/android/android-common.js +++ b/lib/devices/android/android-common.js @@ -5,7 +5,9 @@ var logger = require('../../server/logger.js').get('appium') , fs = require('fs') , path = require('path') , md5 = require('md5calculator') - , async = require('async'); + , async = require('async') + , errors = require('../../server/errors.js') + , NotYetImplementedError = errors.NotYetImplementedError; var logTypesSupported = { 'logcat' : 'Logs for Android applications on real device and emulators ' + @@ -34,6 +36,163 @@ androidCommon.background = function(secs, cb) { }.bind(this)); }; +androidCommon.openSettingsActivity = function(setting, cb) { + this.adb.getFocusedPackageAndActivity(function(err, foundPackage, + foundActivity) { + var cmd = 'am start -a android.settings.' + setting; + this.adb.shell(cmd, function(err) { + if (err) { + cb(err); + } else { + this.adb.waitForNotActivity(foundPackage, foundActivity, 5000, cb); + } + }.bind(this)); + }.bind(this)); +}; + +androidCommon.toggleSetting = function(setting, preKeySeq, ocb) { + var doKey = function(key) { + return function(cb) { + setTimeout(function() { + this.adb.keyevent(key, cb); + }.bind(this), 2000); + }.bind(this); + }.bind(this); + + var settPkg, settAct; + + var back = function(cb) { + this.adb.back(function(err) { + if (err) { + cb(err); + } else { + this.adb.waitForNotActivity(settPkg, settAct, 5000, cb); + } + }.bind(this)); + }.bind(this); + + /* + * preKeySeq is the keyevent sequence to send over ADB in order + * to position the cursor on the right option. + * By default it's [up, up, down] because we usually target the 1st item in + * the screen, and sometimes when opening settings activities the cursor is + * already positionned on the 1st item, but we can't know for sure + */ + if (preKeySeq === null) preKeySeq = [19, 19, 20]; // up, up, down + + var sequence = [ + function(cb) { + this.openSettingsActivity(setting, cb); + }.bind(this) + ]; + var len = preKeySeq.length; + + for(var i = 0; i < len; i++) { + sequence.push(doKey(preKeySeq[i])); + } + + sequence.push( + function(cb) { + this.adb.getFocusedPackageAndActivity(function(err, foundPackage, + foundActivity) { + settPkg = foundPackage; + settAct = foundActivity; + cb(err); + }.bind(this)); + }.bind(this) + , function(cb) { + /* + * Click and handle potential ADB disconnect that occurs on official + * emulator when the network connection is disabled + */ + this.wrapActionAndHandleADBDisconnect(doKey(23), cb); + }.bind(this) + , function(cb) { + /* + * In one particular case (enable Location Services), a pop-up is + * displayed on some platforms so the user accepts or refuses that Google + * collects location data. So we wait for that pop-up to open, if it + * doesn't then proceed + */ + this.adb.waitForNotActivity(settPkg, settAct, 5000, function(err) { + if (err) { + cb(null); + } else { + // Click on right button, "Accept" + async.series([ + doKey(22) // right + , doKey(23) // click + , function(cb) { + // Wait for pop-up to close + this.adb.waitForActivity(settPkg, settAct, 5000, cb); + }.bind(this) + ], function(err) { + cb(err); + }.bind(this)); + } + }.bind(this)); + }.bind(this) + , back + ); + + async.series(sequence, function(err) { + if (err) return ocb(err); + ocb(null, { status: status.codes.Success.code }); + }.bind(this)); +}; + +androidCommon.toggleData = function(ocb) { + // up, up, down + this.toggleSetting('DATA_ROAMING_SETTINGS', [19, 19, 20], ocb); +}; + +androidCommon.toggleFlightMode = function(ocb) { + this.adb.getApiLevel(function(err, api) { + var seq = [19, 19]; // up, up + /* + * On Android 4.0 there's no "parent" button in the action bar, so we don't + * need to go down, the cursor is already at the top of the list + */ + if (api > 15) { + seq.push(20); // down + } + this.toggleSetting('AIRPLANE_MODE_SETTINGS', seq, ocb); + }.bind(this)); +}; + +androidCommon.toggleWiFi = function(ocb) { + // right, right + this.toggleSetting('WIFI_SETTINGS', [22, 22], ocb); +}; + +androidCommon.toggleLocationServices = function(ocb) { + this.adb.getApiLevel(function(err, api) { + if (api > 15) { + var seq = [19, 19]; // up, up + if (api === 16) { + // This version of Android has a "parent" button in its action bar + seq.push(20); // down + } else if (api >= 19) { + // Newer versions of Android have the toggle in the Action bar + seq = [22, 22]; // right, right + /* + * Once the Location services switch is OFF, it won't receive focus + * when going back to the Location Services settings screen unless we + * send a dummy keyevent (UP) *before* opening the settings screen + */ + this.adb.keyevent(19, function(err) { + this.toggleSetting('LOCATION_SOURCE_SETTINGS', seq, ocb); + }.bind(this)); + return; + } + this.toggleSetting('LOCATION_SOURCE_SETTINGS', seq, ocb); + } else { + // There's no global location services toggle on older Android versions + ocb(new NotYetImplementedError(), null); + } + }.bind(this)); +}; + androidCommon.prepareDevice = function(onReady) { logger.info("Preparing device for session"); async.series([ diff --git a/lib/devices/android/android.js b/lib/devices/android/android.js index e4fb228fa4a..c606f3bfeba 100644 --- a/lib/devices/android/android.js +++ b/lib/devices/android/android.js @@ -46,6 +46,8 @@ Android.prototype.initialize = function(opts) { this.shuttingDown = false; this.adb = null; this.uiautomator = null; + this.uiautomatorRestartOnExit = false; + this.uiautomatorIgnoreExit = false; this.swipeStepsPerSec = 28; this.dragStepsPerSec = 40; this.asyncWaitMs = 0; @@ -99,6 +101,7 @@ Android.prototype.start = function(cb, onDie) { this.launchCb(err); }.bind(this)); } else { + this.didLaunch = true; this.launchCb(); } }.bind(this)); @@ -135,6 +138,31 @@ Android.prototype.onLaunch = function(err) { } }; +Android.prototype.restartUiautomator = function(cb) { + async.series([ + this.forwardPort.bind(this) + , this.uiautomator.start.bind(this.uiautomator) + ], cb); +}; + +/* + * Execute an arbitrary function and handle potential ADB disconnection before + * proceeding + */ +Android.prototype.wrapActionAndHandleADBDisconnect = function(action, ocb) { + async.series([ + function(cb) { + this.uiautomatorIgnoreExit = true; + action(cb); + }.bind(this) + , this.adb.restart.bind(this.adb) + , this.restartUiautomator.bind(this) + ], function(err) { + this.uiautomatorIgnoreExit = false; + ocb(err); + }.bind(this)); +}; + Android.prototype.onUiautomatorExit = function() { var respondToClient = function() { this.cleanup(); @@ -153,11 +181,41 @@ Android.prototype.onUiautomatorExit = function() { }.bind(this); if (this.adb) { - logger.info("Attempting to uninstall app"); - this.uninstallApp(function() { - this.shuttingDown = false; - respondToClient(); - }.bind(this)); + var uninstall = function() { + logger.info("Attempting to uninstall app"); + this.uninstallApp(function() { + this.shuttingDown = false; + respondToClient(); + }.bind(this)); + }.bind(this); + + if (!this.uiautomatorIgnoreExit) { + this.adb.ping(function(err, ok) { + if (ok) { + uninstall(); + } else { + logger.debug(err); + this.adb.restart(function(err) { + if (err) { + logger.debug(err); + } + if (this.uiautomatorRestartOnExit) { + this.uiautomatorRestartOnExit = false; + this.restartUiautomator(function(err) { + if (err) { + logger.debug(err); + uninstall(); + } + }.bind(this)); + } else { + uninstall(); + } + }.bind(this.adb)); + } + }.bind(this)); + } else { + this.uiautomatorIgnoreExit = false; + } } else { logger.info("We're in uiautomator's exit callback but adb is gone already"); respondToClient(); diff --git a/lib/devices/android/logcat.js b/lib/devices/android/logcat.js index 43cb8278dbd..2b283a4019e 100644 --- a/lib/devices/android/logcat.js +++ b/lib/devices/android/logcat.js @@ -35,12 +35,21 @@ Logcat.prototype.startCapture = function(cb) { cb(err); } }.bind(this)); + this.proc.on('exit', function(code, signal) { + logger.debug('Logcat terminated with code ' + code + ', signal ' + signal); + this.proc = null; + }.bind(this)); this.proc.stdout.pipe(through(this.onStdout.bind(this))); this.proc.stderr.pipe(through(this.onStderr.bind(this))); }; Logcat.prototype.stopCapture = function(cb) { logger.info("Stopping logcat capture"); + if(this.proc === null) { + logger.debug("Logcat already stopped"); + cb(); + return; + } this.proc.on('exit', function() { cb(); }); diff --git a/lib/devices/android/selendroid.js b/lib/devices/android/selendroid.js index ace9a47d441..8ce1b0381c9 100644 --- a/lib/devices/android/selendroid.js +++ b/lib/devices/android/selendroid.js @@ -48,6 +48,10 @@ var Selendroid = function(opts) { , 'closeApp' , 'isAppInstalled' , 'launchApp' + , 'toggleData' + , 'toggleFlightMode' + , 'toggleWiFi' + , 'toggleLocationServices' ]; this.proxyHost = 'localhost'; this.proxyPort = opts.systemPort; @@ -180,6 +184,22 @@ Selendroid.prototype.keyevent = function(keycode, metastate, cb) { }); }; +/* + * Execute an arbitrary function and handle potential ADB disconnection before + * proceeding + */ +Selendroid.prototype.wrapActionAndHandleADBDisconnect = function(action, ocb) { + async.series([ + function(cb) { + action(cb); + }.bind(this) + , this.adb.restart.bind(this.adb) + , this.forwardPort.bind(this) + ], function(err) { + ocb(err); + }.bind(this)); +}; + Selendroid.prototype.ensureServerExists = function(cb) { logger.info("Checking whether selendroid is built yet"); var selBin = path.resolve(__dirname, "..", "..", "..", "build", "selendroid", diff --git a/lib/devices/android/uiautomator.js b/lib/devices/android/uiautomator.js index 4a75c66870d..a42c3c9e094 100644 --- a/lib/devices/android/uiautomator.js +++ b/lib/devices/android/uiautomator.js @@ -35,6 +35,7 @@ UiAutomator.prototype.start = function(readyCb) { return readyCb(new Error("Could not start adb, is it around?")); } + this.alreadyExited = false; this.onSocketReady = readyCb; this.proc.stdout.on('data', this.outputStreamHandler.bind(this)); diff --git a/lib/devices/ios/ios-controller.js b/lib/devices/ios/ios-controller.js index 4d442c8996c..752e34dcc0d 100644 --- a/lib/devices/ios/ios-controller.js +++ b/lib/devices/ios/ios-controller.js @@ -266,6 +266,22 @@ iOSController.touchLongClick = function(elementId, cb) { cb(new NotYetImplementedError(), null); }; +iOSController.toggleData = function(cb) { + cb(new NotYetImplementedError(), null); +}; + +iOSController.toggleFlightMode = function(cb) { + cb(new NotYetImplementedError(), null); +}; + +iOSController.toggleWiFi = function(cb) { + cb(new NotYetImplementedError(), null); +}; + +iOSController.toggleLocationServices = function(cb) { + cb(new NotYetImplementedError(), null); +}; + iOSController.getStrings = function(cb) { var strings = this.localizableStrings; if (strings && strings.length >= 1) strings = strings[0]; diff --git a/lib/server/controller.js b/lib/server/controller.js index c3595aea218..04f9afb3e01 100644 --- a/lib/server/controller.js +++ b/lib/server/controller.js @@ -924,6 +924,22 @@ exports.localScreenshot = function(req, res) { } }; +exports.toggleData = function(req, res) { + req.device.toggleData(getResponseHandler(req, res)); +}; + +exports.toggleFlightMode = function(req, res) { + req.device.toggleFlightMode(getResponseHandler(req, res)); +}; + +exports.toggleWiFi = function(req, res) { + req.device.toggleWiFi(getResponseHandler(req, res)); +}; + +exports.toggleLocationServices = function(req, res) { + req.device.toggleLocationServices(getResponseHandler(req, res)); +}; + exports.notYetImplemented = notYetImplemented; var mobileCmdMap = { 'tap': exports.mobileTap @@ -960,6 +976,10 @@ var mobileCmdMap = { , 'pinchOpen': exports.mobilePinchOpen , 'localScreenshot': exports.localScreenshot , 'getStrings': exports.getStrings + , 'toggleData': exports.toggleData + , 'toggleFlightMode': exports.toggleFlightMode + , 'toggleWiFi': exports.toggleWiFi + , 'toggleLocationServices': exports.toggleLocationServices }; exports.produceError = function(req, res) { diff --git a/sample-code/apps/ToggleTest/.classpath b/sample-code/apps/ToggleTest/.classpath new file mode 100644 index 00000000000..7bc01d9a9c6 --- /dev/null +++ b/sample-code/apps/ToggleTest/.classpath @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/sample-code/apps/ToggleTest/.gitignore b/sample-code/apps/ToggleTest/.gitignore new file mode 100644 index 00000000000..48d54d91eeb --- /dev/null +++ b/sample-code/apps/ToggleTest/.gitignore @@ -0,0 +1,17 @@ +ant.properties +bin +build.xml +gen +local.properties +proguard-project.txt +project.properties +tests/ant.properties +tests/build.xml +tests/local.properties +tests/proguard-project.txt +tests/project.properties +out +.settings/ +*.apk +lint.xml +.idea/* \ No newline at end of file diff --git a/sample-code/apps/ToggleTest/.project b/sample-code/apps/ToggleTest/.project new file mode 100644 index 00000000000..9d1b251e619 --- /dev/null +++ b/sample-code/apps/ToggleTest/.project @@ -0,0 +1,33 @@ + + + ToggleTest + + + + + + com.android.ide.eclipse.adt.ResourceManagerBuilder + + + + + com.android.ide.eclipse.adt.PreCompilerBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.android.ide.eclipse.adt.ApkBuilder + + + + + + com.android.ide.eclipse.adt.AndroidNature + org.eclipse.jdt.core.javanature + + diff --git a/sample-code/apps/ToggleTest/AndroidManifest.xml b/sample-code/apps/ToggleTest/AndroidManifest.xml new file mode 100644 index 00000000000..62fa2eff4a9 --- /dev/null +++ b/sample-code/apps/ToggleTest/AndroidManifest.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + diff --git a/sample-code/apps/ToggleTest/ic_launcher-web.png b/sample-code/apps/ToggleTest/ic_launcher-web.png new file mode 100644 index 00000000000..a18cbb48c43 Binary files /dev/null and b/sample-code/apps/ToggleTest/ic_launcher-web.png differ diff --git a/sample-code/apps/ToggleTest/libs/android-support-v4.jar b/sample-code/apps/ToggleTest/libs/android-support-v4.jar new file mode 100644 index 00000000000..96644edbead Binary files /dev/null and b/sample-code/apps/ToggleTest/libs/android-support-v4.jar differ diff --git a/sample-code/apps/ToggleTest/res/drawable-hdpi/ic_launcher.png b/sample-code/apps/ToggleTest/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 00000000000..288b66551d1 Binary files /dev/null and b/sample-code/apps/ToggleTest/res/drawable-hdpi/ic_launcher.png differ diff --git a/sample-code/apps/ToggleTest/res/drawable-mdpi/ic_launcher.png b/sample-code/apps/ToggleTest/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 00000000000..6ae570b4db4 Binary files /dev/null and b/sample-code/apps/ToggleTest/res/drawable-mdpi/ic_launcher.png differ diff --git a/sample-code/apps/ToggleTest/res/drawable-xhdpi/ic_launcher.png b/sample-code/apps/ToggleTest/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 00000000000..d4fb7cd9d86 Binary files /dev/null and b/sample-code/apps/ToggleTest/res/drawable-xhdpi/ic_launcher.png differ diff --git a/sample-code/apps/ToggleTest/res/drawable-xxhdpi/ic_launcher.png b/sample-code/apps/ToggleTest/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 00000000000..85a6081587e Binary files /dev/null and b/sample-code/apps/ToggleTest/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/sample-code/apps/ToggleTest/res/layout/activity_main.xml b/sample-code/apps/ToggleTest/res/layout/activity_main.xml new file mode 100644 index 00000000000..bc8f0a25c56 --- /dev/null +++ b/sample-code/apps/ToggleTest/res/layout/activity_main.xml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sample-code/apps/ToggleTest/res/menu/main.xml b/sample-code/apps/ToggleTest/res/menu/main.xml new file mode 100644 index 00000000000..c0020282304 --- /dev/null +++ b/sample-code/apps/ToggleTest/res/menu/main.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/sample-code/apps/ToggleTest/res/values-sw600dp/dimens.xml b/sample-code/apps/ToggleTest/res/values-sw600dp/dimens.xml new file mode 100644 index 00000000000..44f01db75f0 --- /dev/null +++ b/sample-code/apps/ToggleTest/res/values-sw600dp/dimens.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/sample-code/apps/ToggleTest/res/values-sw720dp-land/dimens.xml b/sample-code/apps/ToggleTest/res/values-sw720dp-land/dimens.xml new file mode 100644 index 00000000000..61e3fa8fbca --- /dev/null +++ b/sample-code/apps/ToggleTest/res/values-sw720dp-land/dimens.xml @@ -0,0 +1,9 @@ + + + + 128dp + + diff --git a/sample-code/apps/ToggleTest/res/values-v11/styles.xml b/sample-code/apps/ToggleTest/res/values-v11/styles.xml new file mode 100644 index 00000000000..3c02242ad04 --- /dev/null +++ b/sample-code/apps/ToggleTest/res/values-v11/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/sample-code/apps/ToggleTest/res/values-v14/styles.xml b/sample-code/apps/ToggleTest/res/values-v14/styles.xml new file mode 100644 index 00000000000..a91fd0372b2 --- /dev/null +++ b/sample-code/apps/ToggleTest/res/values-v14/styles.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/sample-code/apps/ToggleTest/res/values/dimens.xml b/sample-code/apps/ToggleTest/res/values/dimens.xml new file mode 100644 index 00000000000..55c1e5908c7 --- /dev/null +++ b/sample-code/apps/ToggleTest/res/values/dimens.xml @@ -0,0 +1,7 @@ + + + + 16dp + 16dp + + diff --git a/sample-code/apps/ToggleTest/res/values/strings.xml b/sample-code/apps/ToggleTest/res/values/strings.xml new file mode 100644 index 00000000000..fb6e387fbd7 --- /dev/null +++ b/sample-code/apps/ToggleTest/res/values/strings.xml @@ -0,0 +1,8 @@ + + + + ToggleTest + Settings + Hello world! + + diff --git a/sample-code/apps/ToggleTest/res/values/styles.xml b/sample-code/apps/ToggleTest/res/values/styles.xml new file mode 100644 index 00000000000..6ce89c7ba43 --- /dev/null +++ b/sample-code/apps/ToggleTest/res/values/styles.xml @@ -0,0 +1,20 @@ + + + + + + + + + diff --git a/sample-code/apps/ToggleTest/src/com/example/toggletest/MainActivity.java b/sample-code/apps/ToggleTest/src/com/example/toggletest/MainActivity.java new file mode 100644 index 00000000000..bdc91acfa00 --- /dev/null +++ b/sample-code/apps/ToggleTest/src/com/example/toggletest/MainActivity.java @@ -0,0 +1,72 @@ +package com.example.toggletest; + +import android.location.LocationManager; +import android.net.wifi.WifiManager; +import android.os.Build; +import android.os.Bundle; +import android.provider.Settings; +import android.telephony.TelephonyManager; +import android.widget.ToggleButton; +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.Context; + +public class MainActivity extends Activity { + private ToggleButton mWifiToggle; + private ToggleButton mDataToggle; + private ToggleButton mFlightModeToggle; + private ToggleButton mGPSToggle; + private WifiManager mWifiManager; + private TelephonyManager mTelephonyManager; + private LocationManager mLocationManager; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + mWifiManager = (WifiManager)getSystemService(Context.WIFI_SERVICE); + mTelephonyManager = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE); + mLocationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE); + + mWifiToggle = (ToggleButton)findViewById(R.id.wifi_toggle); + mDataToggle = (ToggleButton)findViewById(R.id.data_toggle); + mFlightModeToggle = (ToggleButton)findViewById(R.id.flight_toggle); + mGPSToggle = (ToggleButton)findViewById(R.id.gps_toggle); + } + + @Override + protected void onResume() { + super.onResume(); + + mWifiToggle.setChecked(mWifiManager.isWifiEnabled()); + mDataToggle.setChecked(mTelephonyManager.getDataState() == TelephonyManager.DATA_CONNECTED); + mFlightModeToggle.setChecked(FlightMode.getInstance().isEnabled(this)); + mGPSToggle.setChecked(mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) + || mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)); + } + + static class FlightMode { + public static FlightMode getInstance() { + if(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { + return new FlightMode(); + } else { + return new FlightModeJBMR1(); + } + } + + public boolean isEnabled(Context context) { + return Settings.System.getInt(context.getContentResolver(), + Settings.System.AIRPLANE_MODE_ON, 0) != 0; + } + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) + static class FlightModeJBMR1 extends FlightMode { + @Override + public boolean isEnabled(Context context) { + return Settings.Global.getInt(context.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, 0) != 0; + } + } +} diff --git a/test/functional/testapp/toggle.js b/test/functional/testapp/toggle.js new file mode 100644 index 00000000000..d1a79f351c8 --- /dev/null +++ b/test/functional/testapp/toggle.js @@ -0,0 +1,52 @@ +"use strict"; + +var driverBlock = require("../../helpers/driverblock.js") + , Q = driverBlock.Q + , path = require('path') + , appPath = path.resolve(__dirname, "../../../sample-code/apps/ToggleTest/bin/ToggleTest.apk") + , appPkg = "com.example.toggletest" + , appAct = ".MainActivity" + , it = driverBlock.it; + +var toggleTest = function(h, displayName, toggleElementName, toggleMethod) { + return function() { + var initialValue; + it('should toggle ' + displayName, function(done) { + h.driver + .elementByName(toggleElementName).text().then(function(txt) { + initialValue = txt; + return h.driver.execute("mobile: " + toggleMethod); + }) + .then(function() { + return h.driver.elementByName(toggleElementName).text().then(function(txt) { + txt.should.equal(initialValue === "ON" ? "OFF" : "ON"); + }); + }) + .nodeify(done); + }); + + it('should toggle ' + displayName + ' back to initial value', function(done) { + h.driver.execute("mobile: " + toggleMethod) + .then(function() { + return h.driver.elementByName(toggleElementName).text().then(function(txt) { + txt.should.equal(initialValue); + }); + }) + .nodeify(done); + }); + }; +}; + +var runForPlatform = function(platform) { + var describeWd = driverBlock.describeForApp(appPath, platform, appPkg, appAct); + + describeWd('toggles', function(h) { + describe('toggle cellular data', toggleTest(h, "cellular data", "data_toggle", "toggleData")); + describe('toggle Flight Mode', toggleTest(h, "Flight Mode", "flight_toggle", "toggleFlightMode")); + describe('toggle Wi-Fi', toggleTest(h, "Wi-Fi", "wifi_toggle", "toggleWiFi")); + describe('toggle Location Services', toggleTest(h, "Location Services", "gps_toggle", "toggleLocationServices")); + }, null, null, {newCommandTimeout: 90}); +}; + +module.exports.runForPlatform = runForPlatform; + diff --git a/test/functional/testapp/toggleAndroid.js b/test/functional/testapp/toggleAndroid.js new file mode 100644 index 00000000000..4f5c81108b5 --- /dev/null +++ b/test/functional/testapp/toggleAndroid.js @@ -0,0 +1,6 @@ +"use strict"; + +var test = require("./toggle.js"); + +test.runForPlatform("android"); + diff --git a/test/functional/testapp/toggleSelendroid.js b/test/functional/testapp/toggleSelendroid.js new file mode 100644 index 00000000000..3ca77a70245 --- /dev/null +++ b/test/functional/testapp/toggleSelendroid.js @@ -0,0 +1,6 @@ +"use strict"; + +var test = require("./toggle.js"); + +test.runForPlatform("selendroid"); +