diff --git a/appium-tests/android/android.spec.js b/appium-tests/android/android.spec.js deleted file mode 100644 index b75698708..000000000 --- a/appium-tests/android/android.spec.js +++ /dev/null @@ -1,751 +0,0 @@ -/* - * - * 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 -// 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 deleted file mode 100644 index 72f7a2700..000000000 --- a/appium-tests/helpers/cameraHelper.js +++ /dev/null @@ -1,311 +0,0 @@ -/* 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: ' 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 deleted file mode 100644 index d4eebdec7..000000000 --- a/appium-tests/ios/ios.spec.js +++ /dev/null @@ -1,512 +0,0 @@ -/* - * - * 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); -});