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);
- });
|