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:35 UTC

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

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