You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by ja...@apache.org on 2019/06/20 11:32:34 UTC

[cordova-plugin-camera] branch janpio-add_back_appium created (now 08e0c94)

This is an automated email from the ASF dual-hosted git repository.

janpio pushed a change to branch janpio-add_back_appium
in repository https://gitbox.apache.org/repos/asf/cordova-plugin-camera.git.


      at 08e0c94  Revert "fix: temporarily remove Appium tests (#468)"

This branch includes the following new commits:

     new 08e0c94  Revert "fix: temporarily remove Appium tests (#468)"

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org


[cordova-plugin-camera] 01/01: Revert "fix: temporarily remove Appium tests (#468)"

Posted by ja...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

janpio pushed a commit to branch janpio-add_back_appium
in repository https://gitbox.apache.org/repos/asf/cordova-plugin-camera.git

commit 08e0c94b82a34a831f5bde610959bf77359cf9cb
Author: Jan Piotrowski <pi...@gmail.com>
AuthorDate: Thu Jun 20 13:32:13 2019 +0200

    Revert "fix: temporarily remove Appium tests (#468)"
    
    This reverts commit 19d8e2f6ff230c1de51f247cec30b71134971507.
---
 appium-tests/android/android.spec.js | 751 +++++++++++++++++++++++++++++++++++
 appium-tests/helpers/cameraHelper.js | 311 +++++++++++++++
 appium-tests/ios/ios.spec.js         | 512 ++++++++++++++++++++++++
 3 files changed, 1574 insertions(+)

diff --git a/appium-tests/android/android.spec.js b/appium-tests/android/android.spec.js
new file mode 100644
index 0000000..b756987
--- /dev/null
+++ b/appium-tests/android/android.spec.js
@@ -0,0 +1,751 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+// these tests are meant to be executed by Cordova ParaMedic Appium runner
+// you can find it here: https://github.com/apache/cordova-paramedic/
+// it is not necessary to do a full CI setup to run these tests
+// Run:
+//      node cordova-paramedic/main.js --platform android --plugin cordova-plugin-camera --skipMainTests --target <emulator name>
+// Please note only Android 5.1 and 4.4 are supported at this point.
+
+'use strict';
+
+var wdHelper = global.WD_HELPER;
+var screenshotHelper = global.SCREENSHOT_HELPER;
+var wd = wdHelper.getWD();
+var cameraConstants = require('../../www/CameraConstants');
+var cameraHelper = require('../helpers/cameraHelper');
+
+var MINUTE = 60 * 1000;
+var BACK_BUTTON = 4;
+var DEFAULT_SCREEN_WIDTH = 360;
+var DEFAULT_SCREEN_HEIGHT = 567;
+var DEFAULT_WEBVIEW_CONTEXT = 'WEBVIEW';
+var PROMISE_PREFIX = 'appium_camera_promise_';
+var CONTEXT_NATIVE_APP = 'NATIVE_APP';
+
+describe('Camera tests Android.', function () {
+    var driver;
+    // the name of webview context, it will be changed to match needed context if there are named ones:
+    var webviewContext = DEFAULT_WEBVIEW_CONTEXT;
+    // this indicates that the device library has the test picture:
+    var isTestPictureSaved = false;
+    // we need to know the screen width and height to properly click on an image in the gallery:
+    var screenWidth = DEFAULT_SCREEN_WIDTH;
+    var screenHeight = DEFAULT_SCREEN_HEIGHT;
+    // promise count to use in promise ID
+    var promiseCount = 0;
+    // determine if Appium session is created successfully
+    var appiumSessionStarted = false;
+    // determine if camera is present on the device/emulator
+    var cameraAvailable = false;
+    // determine if emulator is within a range of acceptable resolutions able to run these tests
+    var isResolutionBad = true;
+    // a path to the image we add to the gallery before test run
+    var fillerImagePath;
+    var isAndroid7 = getIsAndroid7();
+
+    function getIsAndroid7() {
+        if (global.USE_SAUCE) {
+            return global.SAUCE_CAPS && (parseFloat(global.SAUCE_CAPS.platformVersion) >= 7);
+        } else {
+            // this is most likely null, meaning we cannot determine if it is Android 7 or not
+            // paramedic needs to be modified to receive and pass the platform version when testing locally
+            return global.PLATFORM_VERSION && (parseFloat(global.PLATFORM_VERSION) >= 7);
+        }
+    }
+
+    function getNextPromiseId() {
+        promiseCount += 1;
+        return getCurrentPromiseId();
+    }
+
+    function getCurrentPromiseId() {
+        return PROMISE_PREFIX + promiseCount;
+    }
+
+    function gracefullyFail(error) {
+        fail(error);
+        return driver
+            .quit()
+            .then(function () {
+                return getDriver();
+            });
+    }
+
+    // combinines specified options in all possible variations
+    // you can add more options to test more scenarios
+    function generateOptions() {
+        var sourceTypes = [
+                cameraConstants.PictureSourceType.CAMERA,
+                cameraConstants.PictureSourceType.PHOTOLIBRARY
+            ];
+        var destinationTypes = cameraConstants.DestinationType;
+        var encodingTypes = cameraConstants.EncodingType;
+        var allowEditOptions = [ true, false ];
+        var correctOrientationOptions = [ true, false ];
+
+        return cameraHelper.generateSpecs(sourceTypes, destinationTypes, encodingTypes, allowEditOptions, correctOrientationOptions);
+    }
+
+    // invokes Camera.getPicture() with the specified options
+    // and goes through all UI interactions unless 'skipUiInteractions' is true
+    function getPicture(options, skipUiInteractions) {
+        var promiseId = getNextPromiseId();
+        if (!options) {
+            options = {};
+        }
+        // assign default values
+        if (!options.hasOwnProperty('allowEdit')) {
+            options.allowEdit = true;
+        }
+        if (!options.hasOwnProperty('destinationType')) {
+            options.destinationType = cameraConstants.DestinationType.FILE_URI;
+        }
+        if (!options.hasOwnProperty('sourceType')) {
+            options.destinationType = cameraConstants.PictureSourceType.CAMERA;
+        }
+
+        return driver
+            .context(webviewContext)
+            .execute(cameraHelper.getPicture, [options, promiseId])
+            .context(CONTEXT_NATIVE_APP)
+            .then(function () {
+                if (skipUiInteractions) {
+                    return;
+                }
+                // selecting a picture from gallery
+                if (options.hasOwnProperty('sourceType') &&
+                        (options.sourceType === cameraConstants.PictureSourceType.PHOTOLIBRARY ||
+                        options.sourceType === cameraConstants.PictureSourceType.SAVEDPHOTOALBUM)) {
+                    var tapTile = new wd.TouchAction();
+                    var swipeRight = new wd.TouchAction();
+                    tapTile
+                        .tap({
+                            x: Math.round(screenWidth / 4),
+                            y: Math.round(screenHeight / 4)
+                        });
+                    swipeRight
+                        .press({x: 10, y: Math.round(screenHeight / 4)})
+                        .wait(300)
+                        .moveTo({x: Math.round(screenWidth - (screenWidth / 8)), y: 0})
+                        .wait(1500)
+                        .release()
+                        .wait(1000);
+                    if (options.allowEdit) {
+                        return driver
+                            // always wait before performing touchAction
+                            .sleep(7000)
+                            .performTouchAction(tapTile);
+                    }
+                    return driver
+                        .waitForElementByAndroidUIAutomator('new UiSelector().text("Gallery");', 20000)
+                        .fail(function () {
+                            // If the Gallery button is not present, swipe right to reveal the Gallery button!
+                            return driver
+                                .performTouchAction(swipeRight)
+                                .waitForElementByAndroidUIAutomator('new UiSelector().text("Gallery");', 20000)
+                        })
+                        .click()
+                        // always wait before performing touchAction
+                        .sleep(7000)
+                        .performTouchAction(tapTile);
+                }
+                // taking a picture from camera
+                return driver
+                    .waitForElementByAndroidUIAutomator('new UiSelector().resourceIdMatches(".*shutter.*")', MINUTE / 2)
+                    .click()
+                    .waitForElementByAndroidUIAutomator('new UiSelector().resourceIdMatches(".*done.*")', MINUTE / 2)
+                    .click()
+                    .then(function () {
+                        if (isAndroid7 && options.allowEdit) {
+                            return driver
+                                .elementByAndroidUIAutomator('new UiSelector().text("Crop picture");', 20000)
+                                .click()
+                                .fail(function () {
+                                    // don't freak out just yet...
+                                    return driver;
+                                })
+                                .elementByAndroidUIAutomator('new UiSelector().text("JUST ONCE");', 20000)
+                                .click()
+                                .fail(function () {
+                                    // maybe someone's hit that "ALWAYS" button?
+                                    return driver;
+                                });
+                        }
+                        return driver;
+                    });
+            })
+            .then(function () {
+                if (skipUiInteractions) {
+                    return;
+                }
+                if (options.allowEdit) {
+                    var saveText = isAndroid7 ? 'SAVE' : 'Save';
+                    return driver
+                        .waitForElementByAndroidUIAutomator('new UiSelector().text("' + saveText + '")', MINUTE)
+                        .click();
+                }
+            })
+            .fail(function (failure) {
+                throw failure;
+            });
+    }
+
+    // checks if the picture was successfully taken
+    // if shouldLoad is falsy, ensures that the error callback was called
+    function checkPicture(shouldLoad, options) {
+        if (!options) {
+            options = {};
+        }
+        return driver
+            .context(webviewContext)
+            .setAsyncScriptTimeout(MINUTE / 2)
+            .executeAsync(cameraHelper.checkPicture, [getCurrentPromiseId(), options, isAndroid7])
+            .then(function (result) {
+                if (shouldLoad) {
+                    if (result !== 'OK') {
+                        fail(result);
+                    }
+                } else if (result.indexOf('ERROR') === -1) {
+                    throw 'Unexpected success callback with result: ' + result;
+                }
+            });
+    }
+
+    // deletes the latest image from the gallery
+    function deleteImage() {
+        var holdTile = new wd.TouchAction();
+        holdTile
+            .press({x: Math.round(screenWidth / 4), y: Math.round(screenHeight / 5)})
+            .wait(1000)
+            .release();
+        return driver
+            // always wait before performing touchAction
+            .sleep(7000)
+            .performTouchAction(holdTile)
+            .elementByAndroidUIAutomator('new UiSelector().text("Delete")')
+            .then(function (element) {
+                return element
+                    .click()
+                    .elementByAndroidUIAutomator('new UiSelector().text("OK")')
+                    .click();
+            }, function () {
+                // couldn't find Delete menu item. Possibly there is no image.
+                return driver;
+            });
+    }
+
+    function getDriver() {
+        driver = wdHelper.getDriver('Android');
+        return driver.getWebviewContext()
+            .then(function(context) {
+                webviewContext = context;
+                return driver.context(webviewContext);
+            })
+            .waitForDeviceReady()
+            .injectLibraries()
+            .then(function () {
+                var options = {
+                    quality: 50,
+                    allowEdit: false,
+                    sourceType: cameraConstants.PictureSourceType.SAVEDPHOTOALBUM,
+                    saveToPhotoAlbum: false,
+                    targetWidth: 210,
+                    targetHeight: 210
+                };
+                return driver
+                    .then(function () { return getPicture(options, true); })
+                    .context(CONTEXT_NATIVE_APP)
+                    // case insensitive select, will be handy with Android 7 support
+                    .elementByXPath('//android.widget.Button[translate(@text, "alow", "ALOW")="ALLOW"]')
+                    .click()
+                    .fail(function noAlert() { })
+                    .deviceKeyEvent(BACK_BUTTON)
+                    .sleep(2000)
+                    .elementById('action_bar_title')
+                    .then(function () {
+                        // success means we're still in native app
+                        return driver
+                            .deviceKeyEvent(BACK_BUTTON);
+                        }, function () {
+                            // error means we're already in webview
+                            return driver;
+                        });
+            })
+            .then(function () {
+                // doing it inside a function because otherwise 
+                // it would not hook up to the webviewContext var change
+                // in the first methods of this chain
+                return driver.context(webviewContext);
+            })
+            .deleteFillerImage(fillerImagePath)
+            .then(function () {
+                fillerImagePath = null;
+            })
+            .addFillerImage()
+            .then(function (result) {
+                if (result && result.indexOf('ERROR:') === 0) {
+                    throw new Error(result);
+                } else {
+                    fillerImagePath = result;
+                }
+            });
+    }
+
+    function recreateSession() {
+        return driver
+            .quit()
+            .finally(function () {
+                return getDriver();
+            });
+    }
+
+    function tryRunSpec(spec) {
+        return driver
+            .then(spec)
+            .fail(function () {
+                return recreateSession()
+                    .then(spec)
+                    .fail(function() {
+                        return recreateSession()
+                            .then(spec);
+                    });
+            })
+            .fail(gracefullyFail);
+    }
+
+    // produces a generic spec function which
+    // takes a picture with specified options
+    // and then verifies it
+    function generateSpec(options) {
+        return function () {
+            return driver
+                .then(function () {
+                    return getPicture(options);
+                })
+                .then(function () {
+                    return checkPicture(true, options);
+                });
+        };
+    }
+
+    function checkSession(done, skipResolutionCheck) {
+        if (!appiumSessionStarted) {
+            fail('Failed to start a session ' + (lastFailureReason ? lastFailureReason : ''));
+            done();
+        }
+        if (!skipResolutionCheck && isResolutionBad) {
+            fail('The resolution of this target device is not within the appropriate range of width: blah-blah and height: bleh-bleh. The target\'s current resolution is: ' + isResolutionBad);
+        }
+    }
+
+    function checkCamera(options, pending) {
+        if (!cameraAvailable) {
+            pending('Skipping because this test requires a functioning camera on the Android device/emulator, and this test suite\'s functional camera test failed on your target environment.');
+        } else if (isAndroid7 && options.allowEdit) {
+            // TODO: Check if it is fixed some day
+            pending('Skipping because can\'t test with allowEdit=true on Android 7: getting unexpected "Camera cancelled" message.');
+        } else if (isAndroid7 && (options.sourceType !== cameraConstants.PictureSourceType.CAMERA)) {
+            pending('Skipping because can\'t click on the gallery tile on Android 7.');
+        }
+    }
+
+    afterAll(function (done) {
+        checkSession(done);
+        driver
+            .quit()
+            .done(done);
+    }, MINUTE);
+
+    it('camera.ui.util configuring driver and starting a session', function (done) {
+        // retry up to 3 times
+        getDriver()
+            .fail(function () {
+                return getDriver()
+                    .fail(function () {
+                        return getDriver()
+                            .fail(fail);
+                    });
+            })
+            .then(function () {
+                appiumSessionStarted = true;
+            })
+            .done(done);
+    }, 30 * MINUTE);
+
+    it('camera.ui.util determine screen dimensions', function (done) {
+        checkSession(done, /*skipResolutionCheck?*/ true); // skip the resolution check here since we are about to find out in this spec!
+        driver
+            .context(CONTEXT_NATIVE_APP)
+            .getWindowSize()
+            .then(function (size) {
+                screenWidth = Number(size.width);
+                screenHeight = Number(size.height);
+                isResolutionBad = false;
+                /*
+                TODO: what are acceptable resolution values?
+                need to check what the emulators used in CI return.
+                and also what local device definitions work and dont
+                */
+            })
+            .done(done);
+    }, MINUTE);
+
+    it('camera.ui.util determine camera availability', function (done) {
+        checkSession(done);
+        var opts = {
+            sourceType: cameraConstants.PictureSourceType.CAMERA,
+            saveToPhotoAlbum: false
+        };
+
+        return driver
+            .then(function () {
+                return getPicture(opts);
+            })
+            .then(function () {
+                cameraAvailable = true;
+            }, function () {
+                return recreateSession();
+            })
+            .done(done);
+    }, 5 * MINUTE);
+
+    describe('Specs.', function () {
+        // getPicture() with saveToPhotoLibrary = true
+        it('camera.ui.spec.1 Saving a picture to the photo library', function (done) {
+            var opts = {
+                quality: 50,
+                allowEdit: false,
+                sourceType: cameraConstants.PictureSourceType.CAMERA,
+                saveToPhotoAlbum: true
+            };
+            checkSession(done);
+            checkCamera(opts, pending);
+
+            var spec = generateSpec(opts);
+            tryRunSpec(spec)
+                .then(function () {
+                    isTestPictureSaved = true;
+                })
+                .done(done);
+        }, 10 * MINUTE);
+
+        // getPicture() with mediaType: VIDEO, sourceType: PHOTOLIBRARY
+        it('camera.ui.spec.2 Selecting only videos', function (done) {
+            checkSession(done);
+            var spec = function () {
+                var options = { sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
+                                mediaType: cameraConstants.MediaType.VIDEO };
+                return driver
+                    .then(function () {
+                        return getPicture(options, true);
+                    })
+                    .context(CONTEXT_NATIVE_APP)
+                    .then(function () {
+                        // try to find "Gallery" menu item
+                        // if there's none, the gallery should be already opened
+                        return driver
+                            .waitForElementByAndroidUIAutomator('new UiSelector().text("Gallery")', 20000)
+                            .then(function (element) {
+                                return element.click();
+                            }, function () {
+                                return driver;
+                            });
+                    })
+                    .then(function () {
+                        // if the gallery is opened on the videos page,
+                        // there should be a "Choose video" or "Select video" caption
+                        var videoSelector = isAndroid7 ? 'new UiSelector().text("Select video")' : 'new UiSelector().text("Choose video")';
+                        return driver
+                            .elementByAndroidUIAutomator(videoSelector)
+                            .fail(function () {
+                                throw 'Couldn\'t find a "Choose/select video" element.';
+                            });
+                    })
+                    .deviceKeyEvent(BACK_BUTTON)
+                    .elementByAndroidUIAutomator('new UiSelector().text("Gallery")')
+                    .deviceKeyEvent(BACK_BUTTON)
+                    .finally(function () {
+                        return driver
+                            .elementById('action_bar_title')
+                            .then(function () {
+                                // success means we're still in native app
+                                return driver
+                                    .deviceKeyEvent(BACK_BUTTON)
+                                    // give native app some time to close
+                                    .sleep(2000)
+                                    // try again! because every ~30th build
+                                    // on Sauce Labs this backbutton doesn't work
+                                    .elementById('action_bar_title')
+                                    .then(function () {
+                                        // success means we're still in native app
+                                        return driver
+                                            .deviceKeyEvent(BACK_BUTTON);
+                                        }, function () {
+                                            // error means we're already in webview
+                                            return driver;
+                                        });
+                            }, function () {
+                                // error means we're already in webview
+                                return driver;
+                            });
+                    });
+            };
+            tryRunSpec(spec).done(done);
+        }, 10 * MINUTE);
+
+        // getPicture(), then dismiss
+        // wait for the error callback to be called
+        it('camera.ui.spec.3 Dismissing the camera', function (done) {
+            var options = {
+                quality: 50,
+                allowEdit: true,
+                sourceType: cameraConstants.PictureSourceType.CAMERA,
+                destinationType: cameraConstants.DestinationType.FILE_URI
+            };
+            checkSession(done);
+            checkCamera(options, pending);
+            var spec = function () {
+                return driver
+                    .then(function () {
+                        return getPicture(options, true);
+                    })
+                    .context(CONTEXT_NATIVE_APP)
+                    .waitForElementByAndroidUIAutomator('new UiSelector().resourceIdMatches(".*cancel.*")', MINUTE / 2)
+                    .click()
+                    .then(function () {
+                        return checkPicture(false);
+                    });
+            };
+
+            tryRunSpec(spec).done(done);
+        }, 10 * MINUTE);
+
+        // getPicture(), then take picture but dismiss the edit
+        // wait for the error callback to be called
+        it('camera.ui.spec.4 Dismissing the edit', function (done) {
+            var options = {
+                quality: 50,
+                allowEdit: true,
+                sourceType: cameraConstants.PictureSourceType.CAMERA,
+                destinationType: cameraConstants.DestinationType.FILE_URI
+            };
+            checkSession(done);
+            checkCamera(options, pending);
+            var spec = function () {
+                return driver
+                    .then(function () {
+                        return getPicture(options, true);
+                    })
+                    .waitForElementByAndroidUIAutomator('new UiSelector().resourceIdMatches(".*shutter.*")', MINUTE / 2)
+                    .click()
+                    .waitForElementByAndroidUIAutomator('new UiSelector().resourceIdMatches(".*done.*")', MINUTE / 2)
+                    .click()
+                    .then(function () {
+                        if (isAndroid7 && options.allowEdit) {
+                            return driver
+                                .waitForElementByAndroidUIAutomator('new UiSelector().text("Crop picture");', 20000)
+                                .click()
+                                .waitForElementByAndroidUIAutomator('new UiSelector().text("JUST ONCE");', 20000)
+                                .click()
+                                .deviceKeyEvent(BACK_BUTTON);
+                        }
+                        return driver
+                            .waitForElementByAndroidUIAutomator('new UiSelector().resourceIdMatches(".*discard.*")', MINUTE / 2)
+                            .click();
+                    })
+                    .then(function () {
+                        return checkPicture(false);
+                    });
+            };
+
+            tryRunSpec(spec).done(done);
+        }, 10 * MINUTE);
+
+        it('camera.ui.spec.5 Verifying target image size, sourceType=CAMERA', function (done) {
+            var opts = {
+                quality: 50,
+                allowEdit: false,
+                sourceType: cameraConstants.PictureSourceType.CAMERA,
+                saveToPhotoAlbum: false,
+                targetWidth: 210,
+                targetHeight: 210
+            };
+            checkSession(done);
+            checkCamera(opts, pending);
+            var spec = generateSpec(opts);
+
+            tryRunSpec(spec).done(done);
+        }, 10 * MINUTE);
+
+        it('camera.ui.spec.6 Verifying target image size, sourceType=PHOTOLIBRARY', function (done) {
+            var opts = {
+                quality: 50,
+                allowEdit: false,
+                sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
+                saveToPhotoAlbum: false,
+                targetWidth: 210,
+                targetHeight: 210
+            };
+            checkSession(done);
+            checkCamera(opts, pending);
+            var spec = generateSpec(opts);
+
+            tryRunSpec(spec).done(done);
+        }, 10 * MINUTE);
+
+        it('camera.ui.spec.7 Verifying target image size, sourceType=CAMERA, DestinationType=NATIVE_URI', function (done) {
+            var opts = {
+                quality: 50,
+                allowEdit: true,
+                sourceType: cameraConstants.PictureSourceType.CAMERA,
+                destinationType: cameraConstants.DestinationType.NATIVE_URI,
+                saveToPhotoAlbum: false,
+                targetWidth: 210,
+                targetHeight: 210
+            };
+            checkSession(done);
+            checkCamera(opts, pending);
+            var spec = generateSpec(opts);
+
+            tryRunSpec(spec).done(done);
+        }, 10 * MINUTE);
+
+        it('camera.ui.spec.8 Verifying target image size, sourceType=PHOTOLIBRARY, DestinationType=NATIVE_URI', function (done) {
+            var opts = {
+                quality: 50,
+                allowEdit: false,
+                sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
+                destinationType: cameraConstants.DestinationType.NATIVE_URI,
+                saveToPhotoAlbum: false,
+                targetWidth: 210,
+                targetHeight: 210
+            };
+            checkSession(done);
+            checkCamera(opts, pending);
+
+            var spec = generateSpec(opts);
+            tryRunSpec(spec).done(done);
+        }, 10 * MINUTE);
+
+        it('camera.ui.spec.9 Verifying target image size, sourceType=CAMERA, DestinationType=NATIVE_URI, quality=100', function (done) {
+            var opts = {
+                quality: 50,
+                allowEdit: true,
+                sourceType: cameraConstants.PictureSourceType.CAMERA,
+                destinationType: cameraConstants.DestinationType.NATIVE_URI,
+                saveToPhotoAlbum: false,
+                targetWidth: 305,
+                targetHeight: 305
+            };
+            checkSession(done);
+            checkCamera(opts, pending);
+            var spec = generateSpec(opts);
+
+            tryRunSpec(spec).done(done);
+        }, 10 * MINUTE);
+
+        it('camera.ui.spec.10 Verifying target image size, sourceType=PHOTOLIBRARY, DestinationType=NATIVE_URI, quality=100', function (done) {
+            var opts = {
+                quality: 100,
+                allowEdit: true,
+                sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
+                destinationType: cameraConstants.DestinationType.NATIVE_URI,
+                saveToPhotoAlbum: false,
+                targetWidth: 305,
+                targetHeight: 305
+            };
+            checkSession(done);
+            checkCamera(opts, pending);
+            var spec = generateSpec(opts);
+
+            tryRunSpec(spec).done(done);
+        }, 10 * MINUTE);
+
+        // combine various options for getPicture()
+        generateOptions().forEach(function (spec) {
+            it('camera.ui.spec.11.' + spec.id + ' Combining options. ' + spec.description, function (done) {
+                checkSession(done);
+                checkCamera(spec.options, pending);
+
+                var s = generateSpec(spec.options);
+                tryRunSpec(s).done(done);
+            }, 10 * MINUTE);
+        });
+
+        it('camera.ui.util Delete filler picture from device library', function (done) {
+            if (isAndroid7 || global.USE_SAUCE) {
+                pending();
+            }
+            driver
+                .context(webviewContext)
+                .deleteFillerImage(fillerImagePath)
+                .done(done);
+        }, MINUTE);
+
+        it('camera.ui.util Delete taken picture from device library', function (done) {
+            if (isAndroid7 || global.USE_SAUCE) {
+                pending();
+            }
+            checkSession(done);
+            if (!isTestPictureSaved) {
+                // couldn't save test picture earlier, so nothing to delete here
+                done();
+                return;
+            }
+            // delete exactly one latest picture
+            // this should be the picture we've taken in the first spec
+            driver
+                .context(CONTEXT_NATIVE_APP)
+                .deviceKeyEvent(BACK_BUTTON)
+                .sleep(1000)
+                .deviceKeyEvent(BACK_BUTTON)
+                .sleep(1000)
+                .deviceKeyEvent(BACK_BUTTON)
+                .elementById('Apps')
+                .click()
+                .then(function () {
+                    return driver
+                        .elementByXPath('//android.widget.Button[@text="OK"]')
+                        .click()
+                        .fail(function () {
+                            // no cling is all right
+                            // it is not a brand new emulator, then
+                        });
+                })
+                .elementByAndroidUIAutomator('new UiSelector().text("Gallery")')
+                .click()
+                .elementByAndroidUIAutomator('new UiSelector().textContains("Pictures")')
+                .click()
+                .then(deleteImage)
+                .deviceKeyEvent(BACK_BUTTON)
+                .sleep(1000)
+                .deviceKeyEvent(BACK_BUTTON)
+                .sleep(1000)
+                .deviceKeyEvent(BACK_BUTTON)
+                .fail(fail)
+                .finally(done);
+        }, 3 * MINUTE);
+    });
+
+});
+
diff --git a/appium-tests/helpers/cameraHelper.js b/appium-tests/helpers/cameraHelper.js
new file mode 100644
index 0000000..72f7a27
--- /dev/null
+++ b/appium-tests/helpers/cameraHelper.js
@@ -0,0 +1,311 @@
+/* global Q, resolveLocalFileSystemURL, Camera, cordova */
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+'use strict';
+
+var cameraConstants = require('../../www/CameraConstants');
+
+function findKeyByValue(set, value) {
+   for (var k in set) {
+      if (set.hasOwnProperty(k)) {
+         if (set[k] == value) {
+            return k;
+         }
+      }
+   }
+   return undefined;
+}
+
+function getDescription(spec) {
+    var desc = '';
+
+    desc += 'sourceType: ' + findKeyByValue(cameraConstants.PictureSourceType, spec.options.sourceType);
+    desc += ', destinationType: ' + findKeyByValue(cameraConstants.DestinationType, spec.options.destinationType);
+    desc += ', encodingType: ' + findKeyByValue(cameraConstants.EncodingType, spec.options.encodingType);
+    desc += ', allowEdit: ' + spec.options.allowEdit.toString();
+    desc += ', correctOrientation: ' + spec.options.correctOrientation.toString();
+
+    return desc;
+}
+
+module.exports.generateSpecs = function (sourceTypes, destinationTypes, encodingTypes, allowEditOptions, correctOrientationOptions) {
+    var destinationType,
+        sourceType,
+        encodingType,
+        allowEdit,
+        correctOrientation,
+        specs = [],
+        id = 1;
+    for (destinationType in destinationTypes) {
+        if (destinationTypes.hasOwnProperty(destinationType)) {
+            for (sourceType in sourceTypes) {
+                if (sourceTypes.hasOwnProperty(sourceType)) {
+                    for (encodingType in encodingTypes) {
+                        if (encodingTypes.hasOwnProperty(encodingType)) {
+                            for (allowEdit in allowEditOptions) {
+                                if (allowEditOptions.hasOwnProperty(allowEdit)) {
+                                    for (correctOrientation in correctOrientationOptions) {
+                                        // if taking picture from photolibrary, don't vary 'correctOrientation' option
+                                        if ((sourceTypes[sourceType] === cameraConstants.PictureSourceType.PHOTOLIBRARY ||
+                                            sourceTypes[sourceType] === cameraConstants.PictureSourceType.SAVEDPHOTOALBUM) &&
+                                            correctOrientation === true) { continue; }
+                                        var spec = {
+                                            'id': id++,
+                                            'options': {
+                                                'destinationType': destinationTypes[destinationType],
+                                                'sourceType': sourceTypes[sourceType],
+                                                'encodingType': encodingTypes[encodingType],
+                                                'allowEdit': allowEditOptions[allowEdit],
+                                                'saveToPhotoAlbum': false,
+                                                'correctOrientation': correctOrientationOptions[correctOrientation]
+                                            }
+                                        };
+                                        spec.description = getDescription(spec);
+                                        specs.push(spec);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return specs;
+};
+
+// calls getPicture() and saves the result in promise
+// note that this function is executed in the context of tested app
+// and not in the context of tests
+module.exports.getPicture = function (opts, pid) {
+    if (navigator._appiumPromises[pid - 1]) {
+        navigator._appiumPromises[pid - 1] = null;
+    }
+    navigator._appiumPromises[pid] = Q.defer();
+    navigator.camera.getPicture(function (result) {
+        navigator._appiumPromises[pid].resolve(result);
+    }, function (err) {
+        navigator._appiumPromises[pid].reject(err);
+    }, opts);
+};
+
+// verifies taken picture when the promise is resolved,
+// calls a callback with 'OK' if everything is good,
+// calls a callback with 'ERROR: <error message>' if something is wrong
+// note that this function is executed in the context of tested app
+// and not in the context of tests
+module.exports.checkPicture = function (pid, options, skipContentCheck, cb) {
+    var isIos = cordova.platformId === "ios";
+    var isAndroid = cordova.platformId === "android";
+    // skip image type check if it's unmodified on Android:
+    // https://github.com/apache/cordova-plugin-camera/#android-quirks-1
+    var skipFileTypeCheckAndroid = isAndroid && options.quality === 100 &&
+        !options.targetWidth && !options.targetHeight &&
+        !options.correctOrientation;
+
+    // Skip image type check if destination is NATIVE_URI and source - device's photoalbum
+    // https://github.com/apache/cordova-plugin-camera/#ios-quirks-1
+    var skipFileTypeCheckiOS = isIos && options.destinationType === Camera.DestinationType.NATIVE_URI &&
+        (options.sourceType === Camera.PictureSourceType.PHOTOLIBRARY ||
+         options.sourceType === Camera.PictureSourceType.SAVEDPHOTOALBUM);
+
+    var skipFileTypeCheck = skipFileTypeCheckAndroid || skipFileTypeCheckiOS;
+
+    var desiredType = 'JPEG';
+    var mimeType = 'image/jpeg';
+    if (options.encodingType === Camera.EncodingType.PNG) {
+        desiredType = 'PNG';
+        mimeType = 'image/png';
+    }
+
+    function errorCallback(msg) {
+        if (msg.hasOwnProperty('message')) {
+            msg = msg.message;
+        }
+        cb('ERROR: ' + msg);
+    }
+
+    // verifies the image we get from plugin
+    function verifyResult(result) {
+        if (result.length === 0) {
+            errorCallback('The result is empty.');
+            return;
+        } else if (isIos && options.destinationType === Camera.DestinationType.NATIVE_URI && result.indexOf('assets-library:') !== 0) {
+            errorCallback('Expected "' + result.substring(0, 150) + '"to start with "assets-library:"');
+            return;
+        } else if (isIos && options.destinationType === Camera.DestinationType.FILE_URI && result.indexOf('file:') !== 0) {
+            errorCallback('Expected "' + result.substring(0, 150) + '"to start with "file:"');
+            return;
+        }
+
+        try {
+            window.atob(result);
+            // if we got here it is a base64 string (DATA_URL)
+            result = "data:" + mimeType + ";base64," + result;
+        } catch (e) {
+            // not DATA_URL
+            if (options.destinationType === Camera.DestinationType.DATA_URL) {
+                errorCallback('Expected ' + result.substring(0, 150) + 'not to be DATA_URL');
+                return;
+            }
+        }
+
+        try {
+            if (result.indexOf('file:') === 0 ||
+                result.indexOf('content:') === 0 ||
+                result.indexOf('assets-library:') === 0) {
+
+                if (!window.resolveLocalFileSystemURL) {
+                    errorCallback('Cannot read file. Please install cordova-plugin-file to fix this.');
+                    return;
+                }
+                if (skipContentCheck) {
+                    cb('OK');
+                    return;
+                }
+                resolveLocalFileSystemURL(result, function (entry) {
+                    if (skipFileTypeCheck) {
+                        displayFile(entry);
+                    } else {
+                        verifyFile(entry);
+                    }
+                }, function (err) {
+                    errorCallback(err);
+                });
+            } else {
+                displayImage(result);
+            }
+        } catch (e) {
+            errorCallback(e);
+        }
+    }
+
+    // verifies that the file type matches the requested type
+    function verifyFile(entry) {
+        try {
+            var reader = new FileReader();
+            reader.onloadend = function(e) {
+                var arr = (new Uint8Array(e.target.result)).subarray(0, 4);
+                var header = '';
+                for(var i = 0; i < arr.length; i++) {
+                    header += arr[i].toString(16);
+                }
+                var actualType = 'unknown';
+
+                switch (header) {
+                    case "89504e47":
+                        actualType = 'PNG';
+                        break;
+                    case 'ffd8ffe0':
+                    case 'ffd8ffe1':
+                    case 'ffd8ffe2':
+                        actualType = 'JPEG';
+                        break;
+                }
+
+                if (actualType === desiredType) {
+                    displayFile(entry);
+                } else {
+                    errorCallback('File type mismatch. Expected ' + desiredType + ', got ' + actualType);
+                }
+            };
+            reader.onerror = function (e) {
+                errorCallback(e);
+            };
+            entry.file(function (file) {
+                reader.readAsArrayBuffer(file);
+            }, function (e) {
+                errorCallback(e);
+            });
+        } catch (e) {
+            errorCallback(e);
+        }
+    }
+
+    // reads the file, then displays the image
+    function displayFile(entry) {
+        function onFileReceived(file) {
+            var reader = new FileReader();
+            reader.onerror = function (e) {
+                errorCallback(e);
+            };
+            reader.onloadend = function (evt) {
+                displayImage(evt.target.result);
+            };
+            reader.readAsDataURL(file);
+        }
+
+        entry.file(onFileReceived, function (e) {
+            errorCallback(e);
+        });
+    }
+
+    function displayImage(image) {
+        try {
+            var imgEl = document.getElementById('camera_test_image');
+            if (!imgEl) {
+                imgEl = document.createElement('img');
+                imgEl.id = 'camera_test_image';
+                document.body.appendChild(imgEl);
+            }
+            var timedOut = false;
+            var loadTimeout = setTimeout(function () {
+                timedOut = true;
+                imgEl.src = '';
+                errorCallback('The image did not load: ' + image.substring(0, 150));
+            }, 10000);
+            var done = function (status) {
+                if (!timedOut) {
+                    clearTimeout(loadTimeout);
+                    imgEl.src = '';
+                    cb(status);
+                }
+            };
+            imgEl.onload = function () {
+                try {
+                    // aspect ratio is preserved so only one dimension should match
+                    if ((typeof options.targetWidth === 'number' && imgEl.naturalWidth !== options.targetWidth) &&
+                        (typeof options.targetHeight === 'number' && imgEl.naturalHeight !== options.targetHeight))
+                    {
+                        done('ERROR: Wrong image size: ' + imgEl.naturalWidth + 'x' + imgEl.naturalHeight +
+                            '. Requested size: ' + options.targetWidth + 'x' + options.targetHeight);
+                    } else {
+                        done('OK');
+                    }
+                } catch (e) {
+                    errorCallback(e);
+                }
+            };
+            imgEl.src = image;
+        } catch (e) {
+            errorCallback(e);
+        }
+    }
+
+    navigator._appiumPromises[pid].promise
+        .then(function (result) {
+            verifyResult(result);
+        })
+        .fail(function (e) {
+            errorCallback(e);
+        });
+};
diff --git a/appium-tests/ios/ios.spec.js b/appium-tests/ios/ios.spec.js
new file mode 100644
index 0000000..d4eebde
--- /dev/null
+++ b/appium-tests/ios/ios.spec.js
@@ -0,0 +1,512 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+// these tests are meant to be executed by Cordova Paramedic test runner
+// you can find it here: https://github.com/apache/cordova-paramedic/
+// it is not necessary to do a full CI setup to run these tests
+// just run "node cordova-paramedic/main.js --platform ios --plugin cordova-plugin-camera"
+
+'use strict';
+
+var wdHelper = global.WD_HELPER;
+var screenshotHelper = global.SCREENSHOT_HELPER;
+var isDevice = global.DEVICE;
+var cameraConstants = require('../../www/CameraConstants');
+var cameraHelper = require('../helpers/cameraHelper');
+
+var MINUTE = 60 * 1000;
+var DEFAULT_WEBVIEW_CONTEXT = 'WEBVIEW_1';
+var PROMISE_PREFIX = 'appium_camera_promise_';
+var CONTEXT_NATIVE_APP = 'NATIVE_APP';
+
+describe('Camera tests iOS.', function () {
+    var driver;
+    var webviewContext = DEFAULT_WEBVIEW_CONTEXT;
+    // promise count to use in promise ID
+    var promiseCount = 0;
+    // going to set this to false if session is created successfully
+    var failedToStart = true;
+    // points out which UI automation to use
+    var isXCUI = false;
+    // spec counter to restart the session
+    var specsRun = 0;
+
+    function getNextPromiseId() {
+        promiseCount += 1;
+        return getCurrentPromiseId();
+    }
+
+    function getCurrentPromiseId() {
+        return PROMISE_PREFIX + promiseCount;
+    }
+
+    function gracefullyFail(error) {
+        fail(error);
+        return driver
+            .quit()
+            .then(function () {
+                return getDriver();
+            });
+    }
+
+    // generates test specs by combining all the specified options
+    // you can add more options to test more scenarios
+    function generateOptions() {
+        var sourceTypes = cameraConstants.PictureSourceType;
+        var destinationTypes = cameraConstants.DestinationType;
+        var encodingTypes = cameraConstants.EncodingType;
+        var allowEditOptions = [ true, false ];
+        var correctOrientationOptions = [ true, false ];
+
+        return cameraHelper.generateSpecs(sourceTypes, destinationTypes, encodingTypes, allowEditOptions, correctOrientationOptions);
+    }
+
+    function usePicture(allowEdit) {
+        return driver
+            .sleep(10)
+            .then(function () {
+                if (isXCUI) {
+                    return driver.waitForElementByAccessibilityId('Choose', MINUTE / 3).click();
+                } else {
+                    if (allowEdit) {
+                        return wdHelper.tapElementByXPath('//UIAButton[@label="Choose"]', driver);
+                    }
+                    return driver.elementByXPath('//*[@label="Use"]').click();
+                }
+            });
+    }
+
+    function clickPhoto() {
+        if (isXCUI) {
+            // iOS >=10
+            return driver
+                .context(CONTEXT_NATIVE_APP)
+                .elementsByXPath('//XCUIElementTypeCell')
+                .then(function(photos) {
+                    if (photos.length == 0) {
+                        return driver
+                            .sleep(0) // driver.source is not a function o.O
+                            .source()
+                            .then(function (src) {
+                                console.log(src);
+                                gracefullyFail('Couldn\'t find an image to click');
+                            });
+                    }
+                    // intentionally clicking the second photo here
+                    // the first one is not clickable for some reason
+                    return photos[1].click();
+                });
+        }
+        // iOS <10
+        return driver
+            .elementByXPath('//UIACollectionCell')
+            .click();
+    }
+
+    function getPicture(options, cancelCamera, skipUiInteractions) {
+        var promiseId = getNextPromiseId();
+        if (!options) {
+            options = {};
+        }
+        // assign defaults
+        if (!options.hasOwnProperty('allowEdit')) {
+            options.allowEdit = true;
+        }
+        if (!options.hasOwnProperty('destinationType')) {
+            options.destinationType = cameraConstants.DestinationType.FILE_URI;
+        }
+        if (!options.hasOwnProperty('sourceType')) {
+            options.destinationType = cameraConstants.PictureSourceType.CAMERA;
+        }
+
+        return driver
+            .context(webviewContext)
+            .execute(cameraHelper.getPicture, [options, promiseId])
+            .context(CONTEXT_NATIVE_APP)
+            .then(function () {
+                if (skipUiInteractions) {
+                    return;
+                }
+                if (options.hasOwnProperty('sourceType') && options.sourceType === cameraConstants.PictureSourceType.PHOTOLIBRARY) {
+                    return driver
+                        .waitForElementByAccessibilityId('Camera Roll', MINUTE / 2)
+                        .click()
+                        .then(function () {
+                            return clickPhoto();
+                        })
+                        .then(function () {
+                            if (!options.allowEdit) {
+                                return driver;
+                            }
+                            return usePicture(options.allowEdit);
+                        });
+                }
+                if (options.hasOwnProperty('sourceType') && options.sourceType === cameraConstants.PictureSourceType.SAVEDPHOTOALBUM) {
+                    return clickPhoto()
+                        .then(function () {
+                            if (!options.allowEdit) {
+                                return driver;
+                            }
+                            return usePicture(options.allowEdit);
+                        });
+                }
+                if (cancelCamera) {
+                    return driver
+                        .waitForElementByAccessibilityId('Cancel', MINUTE / 2)
+                        .click();
+                }
+                return driver
+                    .waitForElementByAccessibilityId('Take Picture', MINUTE / 2)
+                    .click()
+                    .waitForElementByAccessibilityId('Use Photo', MINUTE / 2)
+                    .click();
+            })
+            .fail(fail);
+    }
+
+    // checks if the picture was successfully taken
+    // if shouldLoad is falsy, ensures that the error callback was called
+    function checkPicture(shouldLoad, options) {
+        if (!options) {
+            options = {};
+        }
+        return driver
+            .context(webviewContext)
+            .setAsyncScriptTimeout(MINUTE / 2)
+            .executeAsync(cameraHelper.checkPicture, [getCurrentPromiseId(), options, false])
+            .then(function (result) {
+                if (shouldLoad) {
+                    if (result !== 'OK') {
+                        fail(result);
+                    }
+                } else if (result.indexOf('ERROR') === -1) {
+                    throw 'Unexpected success callback with result: ' + result;
+                }
+            });
+    }
+
+    // takes a picture with the specified options
+    // and then verifies it
+    function runSpec(options, done, pending) {
+        if (options.sourceType === cameraConstants.PictureSourceType.CAMERA && !isDevice) {
+            pending('Camera is not available on iOS simulator');
+        }
+        checkSession(done);
+        specsRun += 1;
+        return driver
+            .then(function () {
+                return getPicture(options);
+            })
+            .then(function () {
+                return checkPicture(true, options);
+            })
+            .fail(gracefullyFail);
+    }
+
+    function getDriver() {
+        failedToStart = true;
+        driver = wdHelper.getDriver('iOS');
+        return wdHelper.getWebviewContext(driver)
+            .then(function(context) {
+                webviewContext = context;
+                return driver.context(webviewContext);
+            })
+            .then(function () {
+                return wdHelper.waitForDeviceReady(driver);
+            })
+            .then(function () {
+                return wdHelper.injectLibraries(driver);
+            })
+            .sessionCapabilities()
+            .then(function (caps) {
+                var platformVersion = parseFloat(caps.platformVersion);
+                isXCUI = platformVersion >= 10.0;
+            })
+            .then(function () {
+                var options = {
+                    quality: 50,
+                    allowEdit: false,
+                    sourceType: cameraConstants.PictureSourceType.SAVEDPHOTOALBUM,
+                    saveToPhotoAlbum: false,
+                    targetWidth: 210,
+                    targetHeight: 210
+                };
+                return driver
+                    .then(function () { return getPicture(options, false, true); })
+                    .context(CONTEXT_NATIVE_APP)
+                    .acceptAlert()
+                    .then(function alertDismissed() {
+                        // TODO: once we move to only XCUITest-based (which is force on you in either iOS 10+ or Xcode 8+)
+                        // UI tests, we will have to:
+                        // a) remove use of autoAcceptAlerts appium capability since it no longer functions in XCUITest
+                        // b) can remove this entire then() clause, as we do not need to explicitly handle the acceptAlert
+                        //    failure callback, since we will be guaranteed to hit the permission dialog on startup.
+                    }, function noAlert() {
+                        // in case the contacts permission alert never showed up: no problem, don't freak out.
+                        // This can happen if:
+                        // a) The application-under-test already had photos permissions granted to it
+                        // b) Appium's autoAcceptAlerts capability is provided (and functioning)
+                    })
+                    .elementByAccessibilityId('Cancel', 10000)
+                    .click();
+            })
+            .then(function () {
+                failedToStart = false;
+            });
+    }
+
+    function checkSession(done) {
+        if (failedToStart) {
+            fail('Failed to start a session');
+            done();
+        }
+    }
+
+    it('camera.ui.util configure driver and start a session', function (done) {
+        // retry up to 3 times
+        getDriver()
+            .fail(function () {
+                return getDriver()
+                    .fail(function () {
+                        return getDriver()
+                            .fail(fail);
+                    });
+            })
+            .fail(fail)
+            .done(done);
+    }, 30 * MINUTE);
+
+    describe('Specs.', function () {
+        afterEach(function (done) {
+            if (specsRun >= 19) {
+                specsRun = 0;
+                // we need to restart the session regularly because for some reason
+                // when running against iOS 10 simulator on SauceLabs, 
+                // Appium cannot handle more than ~20 specs at one session
+                // the error would be as follows:
+                // "Could not proxy command to remote server. Original error: Error: connect ECONNREFUSED 127.0.0.1:8100"
+                checkSession(done);
+                return driver
+                    .quit()
+                    .then(function () {
+                        return getDriver()
+                            .fail(function () {
+                                return getDriver()
+                                    .fail(function () {
+                                        return getDriver()
+                                            .fail(fail);
+                                    });
+                            });
+                    })
+                    .done(done);
+            } else {
+                done();
+            }
+        }, 30 * MINUTE);
+
+        // getPicture() with mediaType: VIDEO, sourceType: PHOTOLIBRARY
+        it('camera.ui.spec.1 Selecting only videos', function (done) {
+            checkSession(done);
+            specsRun += 1;
+            var options = { sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
+                            mediaType: cameraConstants.MediaType.VIDEO };
+            driver
+                // skip ui unteractions
+                .then(function () { return getPicture(options, false, true); })
+                .waitForElementByXPath('//*[contains(@label,"Videos")]', MINUTE / 2)
+                .elementByAccessibilityId('Cancel')
+                .click()
+                .fail(gracefullyFail)
+                .done(done);
+        }, 7 * MINUTE);
+
+        // getPicture(), then dismiss
+        // wait for the error callback to be called
+        it('camera.ui.spec.2 Dismissing the camera', function (done) {
+            checkSession(done);
+            if (!isDevice) {
+                pending('Camera is not available on iOS simulator');
+            }
+            specsRun += 1;
+            var options = { sourceType: cameraConstants.PictureSourceType.CAMERA,
+                            saveToPhotoAlbum: false };
+            driver
+                .then(function () {
+                    return getPicture(options, true);
+                })
+                .then(function () {
+                    return checkPicture(false);
+                })
+                .fail(gracefullyFail)
+                .done(done);
+        }, 7 * MINUTE);
+
+        it('camera.ui.spec.3 Verifying target image size, sourceType=CAMERA', function (done) {
+            var options = {
+                quality: 50,
+                allowEdit: false,
+                sourceType: cameraConstants.PictureSourceType.CAMERA,
+                saveToPhotoAlbum: false,
+                targetWidth: 210,
+                targetHeight: 210
+            };
+
+            runSpec(options, done, pending).done(done);
+        }, 7 * MINUTE);
+
+        it('camera.ui.spec.4 Verifying target image size, sourceType=SAVEDPHOTOALBUM', function (done) {
+            var options = {
+                quality: 50,
+                allowEdit: false,
+                sourceType: cameraConstants.PictureSourceType.SAVEDPHOTOALBUM,
+                saveToPhotoAlbum: false,
+                targetWidth: 210,
+                targetHeight: 210
+            };
+
+            runSpec(options, done, pending).done(done);
+        }, 7 * MINUTE);
+
+        it('camera.ui.spec.5 Verifying target image size, sourceType=PHOTOLIBRARY', function (done) {
+            var options = {
+                quality: 50,
+                allowEdit: false,
+                sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
+                saveToPhotoAlbum: false,
+                targetWidth: 210,
+                targetHeight: 210
+            };
+
+            runSpec(options, done, pending).done(done);
+        }, 7 * MINUTE);
+
+        it('camera.ui.spec.6 Verifying target image size, sourceType=CAMERA, destinationType=FILE_URL', function (done) {
+            // remove this line if you don't mind the tests leaving a photo saved on device
+            pending('Cannot prevent iOS from saving the picture to photo library');
+
+            var options = {
+                quality: 50,
+                allowEdit: false,
+                sourceType: cameraConstants.PictureSourceType.CAMERA,
+                destinationType: cameraConstants.DestinationType.FILE_URL,
+                saveToPhotoAlbum: false,
+                targetWidth: 210,
+                targetHeight: 210
+            };
+
+            runSpec(options, done, pending).done(done);
+        }, 7 * MINUTE);
+
+        it('camera.ui.spec.7 Verifying target image size, sourceType=SAVEDPHOTOALBUM, destinationType=FILE_URL', function (done) {
+            var options = {
+                quality: 50,
+                allowEdit: false,
+                sourceType: cameraConstants.PictureSourceType.SAVEDPHOTOALBUM,
+                destinationType: cameraConstants.DestinationType.FILE_URL,
+                saveToPhotoAlbum: false,
+                targetWidth: 210,
+                targetHeight: 210
+            };
+
+            runSpec(options, done, pending).done(done);
+        }, 7 * MINUTE);
+
+        it('camera.ui.spec.8 Verifying target image size, sourceType=PHOTOLIBRARY, destinationType=FILE_URL', function (done) {
+            var options = {
+                quality: 50,
+                allowEdit: false,
+                sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
+                destinationType: cameraConstants.DestinationType.FILE_URL,
+                saveToPhotoAlbum: false,
+                targetWidth: 210,
+                targetHeight: 210
+            };
+
+            runSpec(options, done, pending).done(done);
+        }, 7 * MINUTE);
+
+        it('camera.ui.spec.9 Verifying target image size, sourceType=CAMERA, destinationType=FILE_URL, quality=100', function (done) {
+            // remove this line if you don't mind the tests leaving a photo saved on device
+            pending('Cannot prevent iOS from saving the picture to photo library');
+
+            var options = {
+                quality: 100,
+                allowEdit: false,
+                sourceType: cameraConstants.PictureSourceType.CAMERA,
+                destinationType: cameraConstants.DestinationType.FILE_URL,
+                saveToPhotoAlbum: false,
+                targetWidth: 305,
+                targetHeight: 305
+            };
+            runSpec(options, done, pending).done(done);
+        }, 7 * MINUTE);
+
+        it('camera.ui.spec.10 Verifying target image size, sourceType=SAVEDPHOTOALBUM, destinationType=FILE_URL, quality=100', function (done) {
+            var options = {
+                quality: 100,
+                allowEdit: false,
+                sourceType: cameraConstants.PictureSourceType.SAVEDPHOTOALBUM,
+                destinationType: cameraConstants.DestinationType.FILE_URL,
+                saveToPhotoAlbum: false,
+                targetWidth: 305,
+                targetHeight: 305
+            };
+
+            runSpec(options, done, pending).done(done);
+        }, 7 * MINUTE);
+
+        it('camera.ui.spec.11 Verifying target image size, sourceType=PHOTOLIBRARY, destinationType=FILE_URL, quality=100', function (done) {
+            var options = {
+                quality: 100,
+                allowEdit: false,
+                sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
+                destinationType: cameraConstants.DestinationType.FILE_URL,
+                saveToPhotoAlbum: false,
+                targetWidth: 305,
+                targetHeight: 305
+            };
+
+            runSpec(options, done, pending).done(done);
+        }, 7 * MINUTE);
+
+        // combine various options for getPicture()
+        generateOptions().forEach(function (spec) {
+            it('camera.ui.spec.12.' + spec.id + ' Combining options. ' + spec.description, function (done) {
+                // remove this check if you don't mind the tests leaving a photo saved on device
+                if (spec.options.sourceType === cameraConstants.PictureSourceType.CAMERA &&
+                    spec.options.destinationType === cameraConstants.DestinationType.NATIVE_URI) {
+                    pending('Skipping: cannot prevent iOS from saving the picture to photo library and cannot delete it. ' +
+                        'For more info, see iOS quirks here: https://github.com/apache/cordova-plugin-camera#ios-quirks-1');
+                }
+
+                runSpec(spec.options, done, pending).done(done);
+            }, 7 * MINUTE);
+        });
+
+    });
+
+    it('camera.ui.util Destroy the session', function (done) {
+        checkSession(done);
+        driver
+            .quit()
+            .done(done);
+    }, 5 * MINUTE);
+});


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org