| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639 | /*jshint node: true, jasmine: true *//* * * 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 Medic Appium runner// you can find it here: https://github.com/apache/cordova-medic/// it is not necessary to do a full CI setup to run these tests// just run "node cordova-medic/medic/medic.js appium --platform android --plugins cordova-plugin-camera"'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_';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;    // a path to the image we add to the gallery before test run    var fillerImagePath;    function getNextPromiseId() {        promiseCount += 1;        return getCurrentPromiseId();    }    function getCurrentPromiseId() {        return PROMISE_PREFIX + promiseCount;    }    function saveScreenshotAndFail(error) {        fail(error);        return screenshotHelper            .saveScreenshot(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 = {};        }        return driver            .context(webviewContext)            .execute(cameraHelper.getPicture, [options, promiseId])            .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: 150})                        .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                        .waitForElementByXPath('//android.widget.TextView[@text="Gallery"]', 20000)                        .elementByXPath('//android.widget.TextView[@text="Gallery"]')                        .elementByXPath('//android.widget.TextView[@text="Gallery"]')                        .elementByXPath('//android.widget.TextView[@text="Gallery"]')                        .elementByXPath('//android.widget.TextView[@text="Gallery"]')                        .fail(function () {                            return driver                                .performTouchAction(swipeRight)                                .waitForElementByXPath('//android.widget.TextView[@text="Gallery"]', 20000)                                .elementByXPath('//android.widget.TextView[@text="Gallery"]')                                .elementByXPath('//android.widget.TextView[@text="Gallery"]')                                .elementByXPath('//android.widget.TextView[@text="Gallery"]')                                .elementByXPath('//android.widget.TextView[@text="Gallery"]');                        })                        .click()                        // always wait before performing touchAction                        .sleep(7000)                        .performTouchAction(tapTile);                }                // taking a picture from camera                return driver                    .waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'shutter\')]', MINUTE / 2)                    .elementByXPath('//android.widget.ImageView[contains(@resource-id,\'shutter\')]')                    .elementByXPath('//android.widget.ImageView[contains(@resource-id,\'shutter\')]')                    .click()                    .waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'done\')]', MINUTE / 2)                    .elementByXPath('//android.widget.ImageView[contains(@resource-id,\'done\')]')                    .elementByXPath('//android.widget.ImageView[contains(@resource-id,\'done\')]')                    .click();            })            .then(function () {                if (skipUiInteractions) {                    return;                }                if (options.allowEdit) {                    return driver                        .waitForElementByXPath('//*[contains(@resource-id,\'save\')]', 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])            .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)            .elementByXPath('//android.widget.TextView[@text="Delete"]')            .then(function (element) {                return element                    .click()                    .elementByXPath('//android.widget.Button[@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()            .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(saveScreenshotAndFail);    }    // 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) {        if (!appiumSessionStarted) {            fail('Failed to start a session');            done();        }    }    function checkCamera(pending) {        if (!cameraAvailable) {            pending('This test requires camera');        }    }    it('camera.ui.util configuring driver and starting a session', function (done) {        getDriver()            .then(function () {                appiumSessionStarted = true;            }, fail)            .done(done);    }, 10 * MINUTE);    it('camera.ui.util determine screen dimensions', function (done) {        checkSession(done);        driver            .context(webviewContext)            .execute(function () {                return {                    'width': screen.availWidth,                    'height': screen.availHeight                };            }, [])            .then(function (size) {                screenWidth = Number(size.width);                screenHeight = Number(size.height);            })            .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) {            checkSession(done);            checkCamera(pending);            var spec = generateSpec({                quality: 50,                allowEdit: false,                sourceType: cameraConstants.PictureSourceType.CAMERA,                saveToPhotoAlbum: true            });            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('NATIVE_APP')                    .then(function () {                        // try to find "Gallery" menu item                        // if there's none, the gallery should be already opened                        return driver                            .waitForElementByXPath('//android.widget.TextView[@text="Gallery"]', 20000)                            .elementByXPath('//android.widget.TextView[@text="Gallery"]')                            .elementByXPath('//android.widget.TextView[@text="Gallery"]')                            .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" caption                        return driver                            .elementByXPath('//*[@text="Choose video"]')                            .fail(function () {                                throw 'Couldn\'t find "Choose video" element.';                            });                    })                    .deviceKeyEvent(BACK_BUTTON)                    .elementByXPath('//android.widget.TextView[@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) {            checkSession(done);            checkCamera(pending);            var spec = function () {                var options = {                    quality: 50,                    allowEdit: true,                    sourceType: cameraConstants.PictureSourceType.CAMERA,                    destinationType: cameraConstants.DestinationType.FILE_URI                };                return driver                    .then(function () {                        return getPicture(options, true);                    })                    .context("NATIVE_APP")                    .waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'cancel\')]', MINUTE / 2)                    .elementByXPath('//android.widget.ImageView[contains(@resource-id,\'cancel\')]')                    .elementByXPath('//android.widget.ImageView[contains(@resource-id,\'cancel\')]')                    .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) {            checkSession(done);            checkCamera(pending);            var spec = function () {                var options = {                    quality: 50,                    allowEdit: true,                    sourceType: cameraConstants.PictureSourceType.CAMERA,                    destinationType: cameraConstants.DestinationType.FILE_URI                };                return driver                    .then(function () {                        return getPicture(options, true);                    })                    .context('NATIVE_APP')                    .waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'shutter\')]', MINUTE / 2)                    .elementByXPath('//android.widget.ImageView[contains(@resource-id,\'shutter\')]')                    .elementByXPath('//android.widget.ImageView[contains(@resource-id,\'shutter\')]')                    .click()                    .waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'done\')]', MINUTE / 2)                    .elementByXPath('//android.widget.ImageView[contains(@resource-id,\'done\')]')                    .elementByXPath('//android.widget.ImageView[contains(@resource-id,\'done\')]')                    .click()                    .waitForElementByXPath('//*[contains(@resource-id,\'discard\')]', MINUTE / 2)                    .elementByXPath('//*[contains(@resource-id,\'discard\')]')                    .elementByXPath('//*[contains(@resource-id,\'discard\')]')                    .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) {            checkSession(done);            checkCamera(pending);            var spec = generateSpec({                quality: 50,                allowEdit: false,                sourceType: cameraConstants.PictureSourceType.CAMERA,                saveToPhotoAlbum: false,                targetWidth: 210,                targetHeight: 210            });            tryRunSpec(spec).done(done);        }, 10 * MINUTE);        it('camera.ui.spec.6 Verifying target image size, sourceType=PHOTOLIBRARY', function (done) {            checkSession(done);            var spec = generateSpec({                quality: 50,                allowEdit: false,                sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,                saveToPhotoAlbum: false,                targetWidth: 210,                targetHeight: 210            });            tryRunSpec(spec).done(done);        }, 10 * MINUTE);        it('camera.ui.spec.7 Verifying target image size, sourceType=CAMERA, DestinationType=NATIVE_URI', function (done) {            checkSession(done);            checkCamera(pending);            var spec = generateSpec({                quality: 50,                allowEdit: false,                sourceType: cameraConstants.PictureSourceType.CAMERA,                destinationType: cameraConstants.DestinationType.NATIVE_URI,                saveToPhotoAlbum: false,                targetWidth: 210,                targetHeight: 210            });            tryRunSpec(spec).done(done);        }, 10 * MINUTE);        it('camera.ui.spec.8 Verifying target image size, sourceType=PHOTOLIBRARY, DestinationType=NATIVE_URI', function (done) {            checkSession(done);            var spec = generateSpec({                quality: 50,                allowEdit: false,                sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,                destinationType: cameraConstants.DestinationType.NATIVE_URI,                saveToPhotoAlbum: false,                targetWidth: 210,                targetHeight: 210            });            tryRunSpec(spec).done(done);        }, 10 * MINUTE);        it('camera.ui.spec.9 Verifying target image size, sourceType=CAMERA, DestinationType=NATIVE_URI, quality=100', function (done) {            checkSession(done);            checkCamera(pending);            var spec = generateSpec({                quality: 100,                allowEdit: true,                sourceType: cameraConstants.PictureSourceType.CAMERA,                destinationType: cameraConstants.DestinationType.NATIVE_URI,                saveToPhotoAlbum: false,                targetWidth: 305,                targetHeight: 305            });            tryRunSpec(spec).done(done);        }, 10 * MINUTE);        it('camera.ui.spec.10 Verifying target image size, sourceType=PHOTOLIBRARY, DestinationType=NATIVE_URI, quality=100', function (done) {            checkSession(done);            var spec = generateSpec({                quality: 100,                allowEdit: true,                sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,                destinationType: cameraConstants.DestinationType.NATIVE_URI,                saveToPhotoAlbum: false,                targetWidth: 305,                targetHeight: 305            });            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);                if (spec.options.sourceType == cameraConstants.PictureSourceType.CAMERA) {                    checkCamera(pending);                }                var s = generateSpec(spec.options);                tryRunSpec(s).done(done);            }, 10 * MINUTE);        });        it('camera.ui.util Delete filler picture from device library', function (done) {            driver                .context(webviewContext)                .deleteFillerImage(fillerImagePath)                .done(done);        }, MINUTE);        it('camera.ui.util Delete taken picture from device library', function (done) {            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('NATIVE_APP')                .deviceKeyEvent(BACK_BUTTON)                .sleep(1000)                .deviceKeyEvent(BACK_BUTTON)                .sleep(1000)                .deviceKeyEvent(BACK_BUTTON)                .elementById('Apps')                .click()                .elementByXPath('//android.widget.TextView[@text="Gallery"]')                .click()                .elementByXPath('//android.widget.TextView[contains(@text,"Pictures")]')                .click()                .then(deleteImage)                .deviceKeyEvent(BACK_BUTTON)                .sleep(1000)                .deviceKeyEvent(BACK_BUTTON)                .sleep(1000)                .deviceKeyEvent(BACK_BUTTON)                .fail(fail)                .finally(done);        }, 3 * MINUTE);    });    it('camera.ui.util Destroy the session', function (done) {        checkSession(done);        driver            .quit()            .done(done);    }, 5 * MINUTE);});
 |