diff --git a/appium-tests/ios/ios.spec.js b/appium-tests/ios/ios.spec.js index cab45f6..bf3b33e 100644 --- a/appium-tests/ios/ios.spec.js +++ b/appium-tests/ios/ios.spec.js @@ -21,10 +21,10 @@ * */ -// these tests are meant to be executed by Cordova Medic Appium runner -// you can find it here: https://github.com/apache/cordova-medic/ +// 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-medic/medic/medic.js appium --platform android --plugins cordova-plugin-camera" +// just run "node cordova-paramedic/main.js --platform ios --plugin cordova-plugin-camera" 'use strict'; @@ -37,6 +37,7 @@ 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; @@ -45,6 +46,10 @@ describe('Camera tests iOS.', function () { 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; @@ -55,10 +60,9 @@ describe('Camera tests iOS.', function () { return PROMISE_PREFIX + promiseCount; } - function saveScreenshotAndFail(error) { + function gracefullyFail(error) { fail(error); - return screenshotHelper - .saveScreenshot(driver) + return driver .quit() .then(function () { return getDriver(); @@ -82,11 +86,43 @@ describe('Camera tests iOS.', function () { .elementByXPath('//*[@label="Use"]') .click() .fail(function () { - // For some reason "Choose" element is not clickable by standard Appium methods + if (isXCUI) { + return driver + .waitForElementByAccessibilityId('Choose', MINUTE / 3) + .click(); + } + // For some reason "Choose" element is not clickable by standard Appium methods on iOS <= 9 return wdHelper.tapElementByXPath('//UIAButton[@label="Choose"]', driver); }); } + 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) { @@ -96,17 +132,18 @@ describe('Camera tests iOS.', function () { return driver .context(webviewContext) .execute(cameraHelper.getPicture, [options, promiseId]) - .context('NATIVE_APP') + .context(CONTEXT_NATIVE_APP) .then(function () { if (skipUiInteractions) { return; } if (options.hasOwnProperty('sourceType') && options.sourceType === cameraConstants.PictureSourceType.PHOTOLIBRARY) { return driver - .waitForElementByXPath('//*[@label="Camera Roll"]', MINUTE / 2) - .click() - .elementByXPath('//UIACollectionCell') + .waitForElementByAccessibilityId('Camera Roll', MINUTE / 2) .click() + .then(function () { + return clickPhoto(); + }) .then(function () { if (!options.allowEdit) { return driver; @@ -115,9 +152,7 @@ describe('Camera tests iOS.', function () { }); } if (options.hasOwnProperty('sourceType') && options.sourceType === cameraConstants.PictureSourceType.SAVEDPHOTOALBUM) { - return driver - .waitForElementByXPath('//UIACollectionCell', MINUTE / 2) - .click() + return clickPhoto() .then(function () { if (!options.allowEdit) { return driver; @@ -127,15 +162,13 @@ describe('Camera tests iOS.', function () { } if (cancelCamera) { return driver - .waitForElementByXPath('//*[@label="Cancel"]', MINUTE / 2) - .elementByXPath('//*[@label="Cancel"]') - .elementByXPath('//*[@label="Cancel"]') + .waitForElementByAccessibilityId('Cancel', MINUTE / 2) .click(); } return driver - .waitForElementByXPath('//*[@label="Take Picture"]', MINUTE / 2) + .waitForElementByAccessibilityId('Take Picture', MINUTE / 2) .click() - .waitForElementByXPath('//*[@label="Use Photo"]', MINUTE / 2) + .waitForElementByAccessibilityId('Use Photo', MINUTE / 2) .click(); }) .fail(fail); @@ -164,7 +197,12 @@ describe('Camera tests iOS.', function () { // takes a picture with the specified options // and then verifies it - function runSpec(options) { + 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); @@ -172,10 +210,11 @@ describe('Camera tests iOS.', function () { .then(function () { return checkPicture(true, options); }) - .fail(saveScreenshotAndFail); + .fail(gracefullyFail); } function getDriver() { + failedToStart = true; driver = wdHelper.getDriver('iOS'); return wdHelper.getWebviewContext(driver) .then(function(context) { @@ -187,6 +226,42 @@ describe('Camera tests iOS.', function () { }) .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; }); } @@ -199,27 +274,46 @@ describe('Camera tests iOS.', function () { it('camera.ui.util configure driver and start a session', function (done) { getDriver() - .then(function () { - failedToStart = false; - }, fail) + .fail(fail) .done(done); - }, 10 * MINUTE); + }, 15 * MINUTE); describe('Specs.', function () { + afterEach(function (done) { + if (specsRun >= 15) { + 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(); + }) + .done(done); + } else { + done(); + } + }, 15 * 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) - .elementByXPath('//*[@label="Cancel"]') + .elementByAccessibilityId('Cancel') .click() - .fail(saveScreenshotAndFail) + .fail(gracefullyFail) .done(done); - }, 3 * MINUTE); + }, 7 * MINUTE); // getPicture(), then dismiss // wait for the error callback to be called @@ -228,6 +322,7 @@ describe('Camera tests iOS.', function () { if (!isDevice) { pending('Camera is not available on iOS simulator'); } + specsRun += 1; var options = { sourceType: cameraConstants.PictureSourceType.CAMERA, saveToPhotoAlbum: false }; driver @@ -237,15 +332,11 @@ describe('Camera tests iOS.', function () { .then(function () { return checkPicture(false); }) - .fail(saveScreenshotAndFail) + .fail(gracefullyFail) .done(done); - }, 3 * MINUTE); + }, 7 * MINUTE); it('camera.ui.spec.3 Verifying target image size, sourceType=CAMERA', function (done) { - checkSession(done); - if (!isDevice) { - pending('Camera is not available on iOS simulator'); - } var options = { quality: 50, allowEdit: false, @@ -255,11 +346,10 @@ describe('Camera tests iOS.', function () { targetHeight: 210 }; - runSpec(options).done(done); - }, 3 * MINUTE); + runSpec(options, done, pending).done(done); + }, 7 * MINUTE); it('camera.ui.spec.4 Verifying target image size, sourceType=SAVEDPHOTOALBUM', function (done) { - checkSession(done); var options = { quality: 50, allowEdit: false, @@ -269,11 +359,10 @@ describe('Camera tests iOS.', function () { targetHeight: 210 }; - runSpec(options).done(done); - }, 3 * MINUTE); + runSpec(options, done, pending).done(done); + }, 7 * MINUTE); it('camera.ui.spec.5 Verifying target image size, sourceType=PHOTOLIBRARY', function (done) { - checkSession(done); var options = { quality: 50, allowEdit: false, @@ -283,17 +372,13 @@ describe('Camera tests iOS.', function () { targetHeight: 210 }; - runSpec(options).done(done); - }, 3 * MINUTE); + 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'); - checkSession(done); - if (!isDevice) { - pending('Camera is not available on iOS simulator'); - } var options = { quality: 50, allowEdit: false, @@ -304,11 +389,10 @@ describe('Camera tests iOS.', function () { targetHeight: 210 }; - runSpec(options).done(done); - }, 3 * MINUTE); + runSpec(options, done, pending).done(done); + }, 7 * MINUTE); it('camera.ui.spec.7 Verifying target image size, sourceType=SAVEDPHOTOALBUM, destinationType=FILE_URL', function (done) { - checkSession(done); var options = { quality: 50, allowEdit: false, @@ -319,11 +403,10 @@ describe('Camera tests iOS.', function () { targetHeight: 210 }; - runSpec(options).done(done); - }, 3 * MINUTE); + runSpec(options, done, pending).done(done); + }, 7 * MINUTE); it('camera.ui.spec.8 Verifying target image size, sourceType=PHOTOLIBRARY, destinationType=FILE_URL', function (done) { - checkSession(done); var options = { quality: 50, allowEdit: false, @@ -334,17 +417,13 @@ describe('Camera tests iOS.', function () { targetHeight: 210 }; - runSpec(options).done(done); - }, 3 * MINUTE); + 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'); - checkSession(done); - if (!isDevice) { - pending('Camera is not available on iOS simulator'); - } var options = { quality: 100, allowEdit: false, @@ -354,11 +433,10 @@ describe('Camera tests iOS.', function () { targetWidth: 305, targetHeight: 305 }; - runSpec(options).done(done); - }, 3 * MINUTE); + 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) { - checkSession(done); var options = { quality: 100, allowEdit: false, @@ -369,11 +447,10 @@ describe('Camera tests iOS.', function () { targetHeight: 305 }; - runSpec(options).done(done); - }, 3 * MINUTE); + 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) { - checkSession(done); var options = { quality: 100, allowEdit: false, @@ -384,17 +461,12 @@ describe('Camera tests iOS.', function () { targetHeight: 305 }; - runSpec(options).done(done); - }, 3 * MINUTE); + 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) { - checkSession(done); - if (!isDevice && spec.options.sourceType === cameraConstants.PictureSourceType.CAMERA) { - pending('Camera is not available on iOS simulator'); - } - // 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) { @@ -402,8 +474,8 @@ describe('Camera tests iOS.', function () { 'For more info, see iOS quirks here: https://github.com/apache/cordova-plugin-camera#ios-quirks-1'); } - runSpec(spec.options).done(done); - }, 3 * MINUTE); + runSpec(spec.options, done, pending).done(done); + }, 7 * MINUTE); }); });