diff --git a/appium-tests/android/android.spec.js b/appium-tests/android/android.spec.js index 37485ca..a011941 100644 --- a/appium-tests/android/android.spec.js +++ b/appium-tests/android/android.spec.js @@ -1,4 +1,5 @@ /*jshint node: true, jasmine: true */ +/* global navigator, Q */ /* * * Licensed to the Apache Software Foundation (ASF) under one @@ -27,18 +28,18 @@ 'use strict'; -var wdHelper = require('../helpers/wdHelper'); +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 screenshotHelper = require('../helpers/screenshotHelper'); -var STARTING_MESSAGE = 'Ready for action!'; -var RETRY_COUNT = 3; // how many times to retry taking a picture before failing 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; @@ -46,33 +47,29 @@ describe('Camera tests Android.', function () { var webviewContext = DEFAULT_WEBVIEW_CONTEXT; // this indicates that the device library has the test picture: var isTestPictureSaved = false; - // this indicates that there was a critical error and we should try to recover: - var errorFlag = false; - // this indicates that we couldn't restore Appium session and should fail fast: - var stopFlag = 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; - function win() { - expect(true).toBe(true); + function getNextPromiseId() { + promiseCount += 1; + return getCurrentPromiseId(); } - function fail(error) { - screenshotHelper.saveScreenshot(driver); - if (error && error.message) { - console.log('An error occured: ' + error.message); - expect(true).toFailWithMessage(error.message); - throw error.message; - } - if (error) { - console.log('Failed expectation: ' + error); - expect(true).toFailWithMessage(error); - throw error; - } - // no message provided :( - expect(true).toBe(false); - throw 'An error without description occured'; + function getCurrentPromiseId() { + return PROMISE_PREFIX + promiseCount; + } + + function saveScreenshotAndFail(error) { + fail(error); + return screenshotHelper + .saveScreenshot(driver) + .quit() + .then(function () { + return getDriver(); + }); } // generates test specs by combining all the specified options @@ -95,152 +92,110 @@ describe('Camera tests Android.', function () { return cameraHelper.generateSpecs(sourceTypes, destinationTypes, encodingTypes, allowEditOptions); } - function getPicture(options, skipUiInteractions, retry) { + // 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 = {}; } - if (typeof retry === 'undefined') { - retry = 1; - } - var command = "navigator.camera.getPicture(function (result) { document.getElementById('info').innerHTML = result.slice(0, 100); }, " + - "function (err) { document.getElementById('info').innerHTML = 'ERROR: ' + err; }," + JSON.stringify(options) + ");"; return driver .context(webviewContext) - .execute(command) - .sleep(7000) + .execute(cameraHelper.getPicture, [options, promiseId]) .context('NATIVE_APP') - .sleep(5000) .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 touchTile = new wd.TouchAction(), - swipeRight = new wd.TouchAction(); - touchTile.press({x: Math.round(screenWidth / 4), y: Math.round(screenHeight / 5)}).release(); - swipeRight.press({x: 10, y: Math.round(screenHeight * 0.8)}) + var tapTile = new wd.TouchAction(); + var swipeRight = new wd.TouchAction(); + tapTile.tap({x: Math.round(screenWidth / 4), y: Math.round(screenHeight / 5)}); + swipeRight.press({x: 10, y: 100}) .wait(300) - .moveTo({x: Math.round(screenWidth / 2), y: Math.round(screenHeight / 2)}) + .moveTo({x: Math.round(screenWidth / 2), y: 100}) .release(); return driver - .performTouchAction(swipeRight) - .sleep(3000) - .elementByXPath('//*[@text="Gallery"]') + .elementByXPath('//android.widget.TextView[@text="Gallery"]') + .fail(function () { + return driver + .performTouchAction(swipeRight) + .elementByXPath('//android.widget.TextView[@text="Gallery"]'); + }) .then(function (element) { - return element.click().sleep(5000); + return element + .click() + // we need to sleep here to give a sidebar some time to close + // if we don't sleep here, sometimes we would click on a sidebar + // in the next step + .sleep(3000); }, function () { - // if the gallery is already opened, we'd just go on: + // the gallery is already opened, just go on: return driver; }) - .performTouchAction(touchTile); + .performTouchAction(tapTile); } + // taking a picture from camera return driver - .elementByXPath('//android.widget.ImageView[contains(@resource-id,\'shutter\')]') + .waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'shutter\')]', MINUTE) .click() - .sleep(3000) - .elementByXPath('//android.widget.ImageView[contains(@resource-id,\'done\')]') - .click() - .sleep(10000); + .waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'done\')]', MINUTE) + .click(); }) .then(function () { if (skipUiInteractions) { return; } - if (options.hasOwnProperty('allowEdit') && options.allowEdit === true) { + if (options.allowEdit) { return driver - .elementByXPath('//*[contains(@resource-id,\'save\')]') + .waitForElementByXPath('//*[contains(@resource-id,\'save\')]', MINUTE) .click(); } }) - .then(function () { - if (!skipUiInteractions) { - return driver.sleep(10000); - } - }) - .fail(function (error) { - if (retry < RETRY_COUNT) { - console.log('Failed to get a picture. Let\'s try it again... '); - return getPicture(options, skipUiInteractions, ++retry); - } else { - console.log('Tried ' + RETRY_COUNT + ' times but couldn\'t get the picture. Failing...'); - fail(error); - } - }); - } - - function enterTest() { - return driver - // trying to determine where we are - .context(webviewContext) - .fail(function (error) { - fail(error); - }) - .elementById('info') - .then(function () { - return driver; //we're already on the test screen - }, function () { - return driver - .elementById('middle') - .then(function () { - return driver - // we're on autotests page, we should go to start page - .execute('window.location = "../index.html"') - .sleep(5000) - .fail(function () { - errorFlag = true; - throw 'Couldn\'t find start page.'; - }); - }, function () { - return; // no-op - }) - // unknown starting page: no 'info' div - // adding it manually - .execute('var info = document.createElement("div"); ' + - 'info.id = "info"; ' + - 'document.body.appendChild(info);'); - }) - .sleep(5000); + .fail(fail); } + // checks if the picture was successfully taken + // if shouldLoad is falsy, ensures that the error callback was called function checkPicture(shouldLoad) { return driver .context(webviewContext) - .elementById('info') - .getAttribute('innerHTML') - .then(function (html) { - if (html.indexOf(STARTING_MESSAGE) >= 0) { - expect(true).toFailWithMessage('No callback was fired'); - } else if (shouldLoad) { - expect(html.length).toBeGreaterThan(0); - if (html.indexOf('ERROR') >= 0) { - fail(html); + .setAsyncScriptTimeout(MINUTE) + .executeAsync(cameraHelper.checkPicture, [getCurrentPromiseId()]) + .then(function (result) { + if (shouldLoad) { + expect(result.length).toBeGreaterThan(0); + if (result.indexOf('ERROR') >= 0) { + return fail(result); } } else { - if (html.indexOf('ERROR') === -1) { - fail('Unexpected success callback with result: ' + html); + if (result.indexOf('ERROR') === -1) { + return fail('Unexpected success callback with result: ' + result); } - expect(html.indexOf('ERROR')).toBe(0); + expect(result.indexOf('ERROR')).toBe(0); } }); } function runCombinedSpec(spec) { - return enterTest() + return driver .then(function () { return getPicture(spec.options); }) .then(function () { return checkPicture(true); }) - .then(win, fail); + .fail(saveScreenshotAndFail); } + // deletes the latest image from the gallery function deleteImage() { var holdTile = new wd.TouchAction(); - holdTile.press({x: Math.round(screenWidth / 3), y: Math.round(screenHeight / 5)}).wait(1000).release(); + holdTile.press({x: Math.round(screenWidth / 4), y: Math.round(screenHeight / 5)}).wait(1000).release(); return driver .performTouchAction(holdTile) .elementByXPath('//android.widget.TextView[@text="Delete"]') @@ -251,141 +206,48 @@ describe('Camera tests Android.', function () { .click(); }, function () { // couldn't find Delete menu item. Possibly there is no image. - return; + return driver; }); } function getDriver() { driver = wdHelper.getDriver('Android'); - return driver; + 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); + }); } - function checkStopFlag() { - if (stopFlag) { - fail('Something went wrong: the stopFlag is on. Please see the log for more details.'); - } - return stopFlag; - } - - beforeEach(function () { - jasmine.addMatchers({ - toFailWithMessage : function () { - return { - compare: function (actual, msg) { - console.log('Failing with message: ' + msg); - var result = { - pass: false, - message: msg - }; - // status 6 means that we've lost the session - // status 7 means that Appium couldn't find an element - // both these statuses mean that the test has failed but - // we should try to recreate the session for the following tests - if (msg.indexOf('Error response status: 6') >= 0 || - msg.indexOf('Error response status: 7') >= 0) { - errorFlag = true; - } - return result; - } - }; - } - }); - }); - it('camera.ui.util configuring driver and starting a session', function (done) { - stopFlag = true; // just in case of timeout - getDriver().then(function () { - stopFlag = false; - }, function (error) { - fail(error); - }) - .finally(done); + getDriver() + .fail(fail) + .done(done); }, 5 * MINUTE); - it('camera.ui.util determine webview context name', function (done) { - var i = 0; - return driver - .contexts(function (err, contexts) { - if (err) { - console.log(err); - } - for (i = 0; i < contexts.length; i++) { - if (contexts[i].indexOf('mobilespec') >= 0) { - webviewContext = contexts[i]; - } - } - done(); - }); - }, MINUTE); - it('camera.ui.util determine screen dimensions', function (done) { - return enterTest() - .execute('document.getElementById(\'info\').innerHTML = window.innerWidth;') - .sleep(5000) - .elementById('info') - .getAttribute('innerHTML') - .then(function (html) { - if (html !== STARTING_MESSAGE) { - screenWidth = Number(html); - } + return driver + .context(webviewContext) + .execute(function () { + return { + 'width': window.innerWidth, + 'height': window.innerHeight + }; + }, []) + .then(function (size) { + screenWidth = Number(size.width); + screenHeight = Number(size.height); }) - .execute('document.getElementById(\'info\').innerHTML = \'' + STARTING_MESSAGE + '\';') - .execute('document.getElementById(\'info\').innerHTML = window.innerHeight;') - .sleep(5000) - .elementById('info') - .getAttribute('innerHTML') - .then(function (html) { - if (html !== STARTING_MESSAGE) { - screenHeight = Number(html); - } - done(); - }); + .done(done); }, MINUTE); describe('Specs.', function () { - beforeEach(function (done) { - // prepare the app for the test - if (!stopFlag) { - return driver - .context(webviewContext) - .then(function () { - return driver; // no-op - }, function (error) { - expect(true).toFailWithMessage(error); - }) - .execute('document.getElementById("info").innerHTML = "' + STARTING_MESSAGE + '";') - .finally(done); - } - done(); - }, 3 * MINUTE); - - afterEach(function (done) { - if (!errorFlag || stopFlag) { - // either there's no error or we've failed irrecoverably - // nothing to worry about! - done(); - return; - } - // recreate the session if there was a critical error in a previous spec - stopFlag = true; // we're going to set this to false if we're able to restore the session - return driver - .quit() - .then(function () { - return getDriver() - .then(function () { - errorFlag = false; - stopFlag = false; - }, function (error) { - fail(error); - stopFlag = true; - }); - }, function (error) { - fail(error); - stopFlag = true; - }) - .finally(done); - }, 3 * MINUTE); - // getPicture() with saveToPhotoLibrary = true it('camera.ui.spec.1 Saving the picture to photo library', function (done) { var options = { @@ -394,8 +256,7 @@ describe('Camera tests Android.', function () { sourceType: cameraConstants.PictureSourceType.CAMERA, saveToPhotoAlbum: true }; - enterTest() - .context(webviewContext) + driver .then(function () { return getPicture(options); }) @@ -403,40 +264,26 @@ describe('Camera tests Android.', function () { isTestPictureSaved = true; return checkPicture(true); }) - .then(win, fail) - .finally(done); + .fail(saveScreenshotAndFail) + .done(done); }, 3 * MINUTE); // getPicture() with mediaType: VIDEO, sourceType: PHOTOLIBRARY it('camera.ui.spec.2 Selecting only videos', function (done) { - if (checkStopFlag()) { - done(); - return; - } var options = { sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY, mediaType: cameraConstants.MediaType.VIDEO }; - enterTest() + driver .then(function () { return getPicture(options, true); }) - .sleep(5000) - .context(webviewContext) - .elementById('info') - .getAttribute('innerHTML') - .then(function (html) { - if (html.indexOf('ERROR') >= 0) { - throw html; - } - }) .context('NATIVE_APP') - .sleep(5000) .then(function () { // try to find "Gallery" menu item // if there's none, the gallery should be already opened return driver - .elementByXPath('//*[@text="Gallery"]') + .elementByXPath('//android.widget.TextView[@text="Gallery"]') .then(function (element) { - return element.click().sleep(2000); + return element.click(); }, function () { return driver; }); @@ -450,143 +297,115 @@ describe('Camera tests Android.', function () { throw 'Couldn\'t find "Choose video" element.'; }); }) - .then(win, fail) - .deviceKeyEvent(4) - .sleep(2000) - .deviceKeyEvent(4) - .sleep(2000) - .elementById('action_bar_title') - .then(function () { - // success means we're still in native app + .deviceKeyEvent(BACK_BUTTON) + .elementByXPath('//android.widget.TextView[@text="Gallery"]') + .deviceKeyEvent(BACK_BUTTON) + .finally(function () { return driver - .deviceKeyEvent(4) - .sleep(2000); - }, function () { - // error means we're already in webview - return driver; + .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; + }); }) - .finally(done); + .fail(saveScreenshotAndFail) + .done(done); }, 3 * MINUTE); // getPicture(), then dismiss - // wait for the error callback to bee called + // wait for the error callback to be called it('camera.ui.spec.3 Dismissing the camera', function (done) { - if (checkStopFlag()) { - done(); - return; - } var options = { quality: 50, allowEdit: true, sourceType: cameraConstants.PictureSourceType.CAMERA, destinationType: cameraConstants.DestinationType.FILE_URI }; - enterTest() - .context(webviewContext) + driver .then(function () { return getPicture(options, true); }) - .sleep(5000) .context("NATIVE_APP") - .elementByXPath('//android.widget.ImageView[contains(@resource-id,\'cancel\')]') + .waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'cancel\')]', MINUTE / 2) .click() - .context(webviewContext) .then(function () { - return driver - .elementByXPath('//*[contains(text(),"Camera cancelled")]') - .then(function () { - return checkPicture(false); - }, function () { - throw 'Couldn\'t find "Camera cancelled" message.'; - }); + return checkPicture(false); }) - .then(win, fail) - .finally(done); + .fail(saveScreenshotAndFail) + .done(done); }, 3 * MINUTE); // getPicture(), then take picture but dismiss the edit - // wait for the error cllback to be called + // wait for the error callback to be called it('camera.ui.spec.4 Dismissing the edit', function (done) { - if (checkStopFlag()) { - done(); - return; - } var options = { quality: 50, allowEdit: true, sourceType: cameraConstants.PictureSourceType.CAMERA, destinationType: cameraConstants.DestinationType.FILE_URI }; - enterTest() - .context(webviewContext) + driver .then(function () { return getPicture(options, true); }) - .sleep(5000) .context('NATIVE_APP') - .elementByXPath('//android.widget.ImageView[contains(@resource-id,\'shutter\')]') + .waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'shutter\')]', MINUTE / 2) .click() - .elementByXPath('//android.widget.ImageView[contains(@resource-id,\'done\')]') + .waitForElementByXPath('//android.widget.ImageView[contains(@resource-id,\'done\')]', MINUTE / 2) .click() - .elementByXPath('//*[contains(@resource-id,\'discard\')]') + .waitForElementByXPath('//*[contains(@resource-id,\'discard\')]', MINUTE / 2) .click() - .sleep(5000) - .context(webviewContext) .then(function () { - return driver - .elementByXPath('//*[contains(text(),"Camera cancelled")]') - .then(function () { - return checkPicture(false); - }, function () { - throw 'Couldn\'t find "Camera cancelled" message.'; - }); + return checkPicture(false); }) - .then(win, fail) - .finally(done); + .fail(saveScreenshotAndFail) + .done(done); }, 3 * MINUTE); // combine various options for getPicture() generateSpecs().forEach(function (spec) { it('camera.ui.spec.5.' + spec.id + ' Combining options', function (done) { - if (checkStopFlag()) { - done(); - return; - } - runCombinedSpec(spec).then(done); + runCombinedSpec(spec) + .done(done); }, 3 * MINUTE); }); it('camera.ui.util Delete test image from device library', function (done) { - if (checkStopFlag()) { + if (!isTestPictureSaved) { + // couldn't save test picture earlier, so nothing to delete here done(); return; } - if (isTestPictureSaved) { - // delete exactly one last picture - // this should be the picture we've taken in the first spec - return driver - .context('NATIVE_APP') - .deviceKeyEvent(3) - .sleep(5000) - .elementByName('Apps') - .click() - .elementByXPath('//android.widget.TextView[@text="Gallery"]') - .click() - .elementByXPath('//android.widget.TextView[contains(@text,"Pictures")]') - .then(function (element) { - return element - .click() - .sleep(3000) - .then(deleteImage) - .then(function () { done(); }, function () { done(); }); - }, function () { - done(); - }); - } - // couldn't save test picture earlier, so nothing to delete here - done(); + // delete exactly one latest picture + // this should be the picture we've taken in the first spec + return 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) { - return driver.quit(done); + driver + .quit() + .done(done); }, MINUTE); }); diff --git a/appium-tests/helpers/cameraHelper.js b/appium-tests/helpers/cameraHelper.js index 9342a30..8a746f3 100644 --- a/appium-tests/helpers/cameraHelper.js +++ b/appium-tests/helpers/cameraHelper.js @@ -1,4 +1,5 @@ /*jshint node: true */ +/* global Q */ /* * * Licensed to the Apache Software Foundation (ASF) under one @@ -84,3 +85,21 @@ module.exports.generateSpecs = function (sourceTypes, destinationTypes, encoding } return specs; }; + +module.exports.getPicture = function (opts, pid) { + navigator._appiumPromises[pid] = Q.defer(); + navigator.camera.getPicture(function (result) { + navigator._appiumPromises[pid].resolve(result); + }, function (err) { + navigator._appiumPromises[pid].reject(err); + }, opts); +}; + +module.exports.checkPicture = function (pid, cb) { + navigator._appiumPromises[pid].promise + .then(function (result) { + cb(result); + }, function (err) { + cb('ERROR: ' + err); + }); +}; diff --git a/appium-tests/helpers/screenshotHelper.js b/appium-tests/helpers/screenshotHelper.js deleted file mode 100644 index e3cf136..0000000 --- a/appium-tests/helpers/screenshotHelper.js +++ /dev/null @@ -1,58 +0,0 @@ -/* jshint node: 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. - * -*/ - -'use strict'; - -var path = require('path'); -var screenshotPath = global.SCREENSHOT_PATH || path.join(__dirname, '../../../appium_screenshots/'); - -function generateScreenshotName() { - var date = new Date(); - - var month = date.getMonth() + 1; - var day = date.getDate(); - var hour = date.getHours(); - var min = date.getMinutes(); - var sec = date.getSeconds(); - - month = (month < 10 ? "0" : "") + month; - day = (day < 10 ? "0" : "") + day; - hour = (hour < 10 ? "0" : "") + hour; - min = (min < 10 ? "0" : "") + min; - sec = (sec < 10 ? "0" : "") + sec; - - return date.getFullYear() + '-' + month + '-' + day + '_' + hour + '.' + min + '.' + sec + '.png'; -} - -module.exports.saveScreenshot = function (driver) { - var oldContext; - return driver - .currentContext() - .then(function (cc) { - oldContext = cc; - }) - .context('NATIVE_APP') - .saveScreenshot(path.join(screenshotPath, generateScreenshotName())) - .then(function () { - return driver.context(oldContext); - }); -}; diff --git a/appium-tests/helpers/wdHelper.js b/appium-tests/helpers/wdHelper.js deleted file mode 100644 index f14c933..0000000 --- a/appium-tests/helpers/wdHelper.js +++ /dev/null @@ -1,68 +0,0 @@ -/* jshint node: 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. - * -*/ - -'use strict'; - -var wd = global.WD || require('wd'); -var driver; - -module.exports.getDriver = function (platform, callback) { - var serverConfig = { - host: 'localhost', - port: 4723 - }, - driverConfig = { - browserName: '', - 'appium-version': '1.5', - platformName: platform, - platformVersion: global.PLATFORM_VERSION || '', - deviceName: global.DEVICE_NAME || '', - app: global.PACKAGE_PATH, - autoAcceptAlerts: true, - }; - - if (process.env.CHROMEDRIVER_EXECUTABLE) { - driverConfig.chromedriverExecutable = process.env.CHROMEDRIVER_EXECUTABLE; - } - driver = wd.promiseChainRemote(serverConfig); - module.exports.configureLogging(driver); - - return driver.init(driverConfig).setImplicitWaitTimeout(10000) - .sleep(20000) // wait for the app to load - .then(callback); -}; - -module.exports.getWD = function () { - return wd; -}; - -module.exports.configureLogging = function (driver) { - driver.on('status', function (info) { - console.log(info); - }); - driver.on('command', function (meth, path, data) { - console.log(' > ' + meth, path, data || ''); - }); - driver.on('http', function (meth, path, data) { - console.log(' > ' + meth, path, data || ''); - }); -}; diff --git a/appium-tests/ios/ios.spec.js b/appium-tests/ios/ios.spec.js index 4b82fb2..27644ef 100644 --- a/appium-tests/ios/ios.spec.js +++ b/appium-tests/ios/ios.spec.js @@ -1,4 +1,5 @@ /*jshint node: true, jasmine: true */ +/* global navigator, Q */ /* * * Licensed to the Apache Software Foundation (ASF) under one @@ -27,41 +28,40 @@ 'use strict'; -var wdHelper = require('../helpers/wdHelper'); +var wdHelper = global.WD_HELPER; +var screenshotHelper = global.SCREENSHOT_HELPER; var wd = wdHelper.getWD(); var isDevice = global.DEVICE; var cameraConstants = require('../../www/CameraConstants'); var cameraHelper = require('../helpers/cameraHelper'); -var screenshotHelper = require('../helpers/screenshotHelper'); var MINUTE = 60 * 1000; var DEFAULT_WEBVIEW_CONTEXT = 'WEBVIEW_1'; +var PROMISE_PREFIX = 'appium_camera_promise_'; describe('Camera tests iOS.', function () { - var driver; var webviewContext = DEFAULT_WEBVIEW_CONTEXT; - var startingMessage = 'Ready for action!'; + // promise count to use in promise ID + var promiseCount = 0; - function win() { - expect(true).toBe(true); + function getNextPromiseId() { + promiseCount += 1; + return getCurrentPromiseId(); } - function fail(error) { - screenshotHelper.saveScreenshot(driver); - if (error && error.message) { - console.log('An error occured: ' + error.message); - expect(true).toFailWithMessage(error.message); - throw error.message; - } - if (error) { - console.log('Failed expectation: ' + error); - expect(true).toFailWithMessage(error); - throw error; - } - // no message provided :( - expect(true).toBe(false); - throw 'An error without description occured'; + function getCurrentPromiseId() { + return PROMISE_PREFIX + promiseCount; + } + + function saveScreenshotAndFail(error) { + fail(error); + return screenshotHelper + .saveScreenshot(driver) + .quit() + .then(function () { + return getDriver(); + }); } // generates test specs by combining all the specified options @@ -84,17 +84,34 @@ describe('Camera tests iOS.', function () { return cameraHelper.generateSpecs(sourceTypes, destinationTypes, encodingTypes, allowEditOptions); } + function usePicture() { + return driver + .elementByXPath('//*[@label="Use"]') + .click() + .fail(function () { + return driver + // For some reason "Choose" element is not clickable by standard Appium methods + // So getting its position and tapping there using TouchAction + .elementByXPath('//UIAButton[@label="Choose"]') + .getLocation() + .then(function (loc) { + var tapChoose = new wd.TouchAction(); + tapChoose.tap(loc); + return driver + .performTouchAction(tapChoose); + }); + }); + } + function getPicture(options, cancelCamera, skipUiInteractions) { + var promiseId = getNextPromiseId(); if (!options) { options = {}; } - var command = "navigator.camera.getPicture(function (result) { document.getElementById('info').innerHTML = 'Success: ' + result.slice(0, 100); }, " + - "function (err) { document.getElementById('info').innerHTML = 'ERROR: ' + err; }," + JSON.stringify(options) + ");"; + return driver - .sleep(2000) .context(webviewContext) - .execute(command) - .sleep(5000) + .execute(cameraHelper.getPicture, [options, promiseId]) .context('NATIVE_APP') .then(function () { if (skipUiInteractions) { @@ -102,187 +119,146 @@ describe('Camera tests iOS.', function () { } if (options.hasOwnProperty('sourceType') && options.sourceType === cameraConstants.PictureSourceType.PHOTOLIBRARY) { return driver - .elementByName('Camera Roll') + .waitForElementByXPath('//*[@label="Camera Roll"]', MINUTE / 2) .click() .elementByXPath('//UIACollectionCell') .click() .then(function () { - if (options.hasOwnProperty('allowEdit') && options.allowEdit === true) { - return driver - .elementByName('Use') - .click(); + if (!options.allowEdit) { + return driver; } - return driver; + return usePicture(); }); } if (options.hasOwnProperty('sourceType') && options.sourceType === cameraConstants.PictureSourceType.SAVEDPHOTOALBUM) { return driver - .elementByXPath('//UIACollectionCell') + .waitForElementByXPath('//UIACollectionCell', MINUTE / 2) .click() .then(function () { - if (options.hasOwnProperty('allowEdit') && options.allowEdit === true) { - return driver - .elementByName('Use') - .click(); + if (!options.allowEdit) { + return driver; } - return driver; + return usePicture(); }); } if (cancelCamera) { return driver - .elementByName('Cancel') + .waitForElementByXPath('//*[@label="Cancel"]', MINUTE / 2) .click(); } return driver - .elementByName('PhotoCapture') + .waitForElementByXPath('//*[@label="Take Picture"]', MINUTE / 2) .click() - .elementByName('Use Photo') + .elementByXPath('//*[@label="Use Photo"]') .click(); }) - .sleep(3000); - } - - function enterTest() { - return driver - .contexts(function (err, contexts) { - if (err) { - fail(err); - } else { - // if WEBVIEW context is available, use it - // if not, use NATIVE_APP - webviewContext = contexts[contexts.length - 1]; - } - }) - .then(function () { - return driver - .context(webviewContext); - }) - .fail(fail) - .elementById('info') - .fail(function () { - // unknown starting page: no 'info' div - // adding it manually - return driver - .execute('var info = document.createElement("div"); ' + - 'info.id = "info"' + - 'document.body.appendChild(info);') - .fail(fail); - }) - .execute('document.getElementById("info").innerHTML = "' + startingMessage + '";') .fail(fail); } + // checks if the picture was successfully taken + // if shouldLoad is falsy, ensures that the error callback was called function checkPicture(shouldLoad) { return driver - .contexts(function (err, contexts) { - // if WEBVIEW context is available, use it - // if not, use NATIVE_APP - webviewContext = contexts[contexts.length - 1]; - }) .context(webviewContext) - .elementById('info') - .getAttribute('innerHTML') - .then(function (html) { - if (html.indexOf(startingMessage) >= 0) { - expect(true).toFailWithMessage('No callback was fired'); - } else if (shouldLoad) { - expect(html.length).toBeGreaterThan(0); - if (html.indexOf('ERROR') >= 0) { - expect(true).toFailWithMessage(html); + .setAsyncScriptTimeout(MINUTE) + .executeAsync(cameraHelper.checkPicture, [getCurrentPromiseId()]) + .then(function (result) { + if (shouldLoad) { + expect(result.length).toBeGreaterThan(0); + if (result.indexOf('ERROR') >= 0) { + return fail(result); } } else { - if (html.indexOf('ERROR') === -1) { - expect(true).toFailWithMessage('Unexpected success callback with result: ' + html); + if (result.indexOf('ERROR') === -1) { + return fail('Unexpected success callback with result: ' + result); } - expect(html.indexOf('ERROR')).toBe(0); + expect(result.indexOf('ERROR')).toBe(0); } - }) - .context('NATIVE_APP'); + }); } function runCombinedSpec(spec) { - return enterTest() + return driver .then(function () { return getPicture(spec.options); }) .then(function () { return checkPicture(true); }) - .then(win, fail); + .fail(saveScreenshotAndFail); } - beforeEach(function () { - jasmine.addMatchers({ - toFailWithMessage : function () { - return { - compare: function (actual, msg) { - console.log('Failing with message: ' + msg); - var result = { - pass: false, - message: msg - }; - return result; - } - }; - } - }); - }); + function getDriver() { + 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); + }); + } - it('camera.ui.util Configuring driver and starting a session', function (done) { - driver = wdHelper.getDriver('iOS', done); - }, 3 * MINUTE); + it('camera.ui.util configure driver and start a session', function (done) { + getDriver() + .fail(fail) + .finally(done); + }, 5 * MINUTE); describe('Specs.', function () { // getPicture() with mediaType: VIDEO, sourceType: PHOTOLIBRARY it('camera.ui.spec.1 Selecting only videos', function (done) { var options = { sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY, mediaType: cameraConstants.MediaType.VIDEO }; - enterTest() - .then(function () { return getPicture(options, false, true); }) // skip ui unteractions - .sleep(5000) - .elementByName('Videos') - .then(win, fail) - .elementByName('Cancel') + driver + // skip ui unteractions + .then(function () { return getPicture(options, false, true); }) + .waitForElementByXPath('//*[contains(@label,"Videos")]', MINUTE / 2) + .elementByXPath('//*[@label="Cancel"]') .click() - .finally(done); + .fail(saveScreenshotAndFail) + .done(done); }, 3 * MINUTE); // getPicture(), then dismiss - // wait for the error callback to bee called + // wait for the error callback to be called it('camera.ui.spec.2 Dismissing the camera', function (done) { - // camera is not available on iOS simulator + // camera is not available on the iOS simulator if (!isDevice) { pending(); } var options = { sourceType: cameraConstants.PictureSourceType.CAMERA }; - enterTest() + driver .then(function () { return getPicture(options, true); }) .then(function () { return checkPicture(false); }) - .elementByXPath('//UIAStaticText[contains(@label,"no image selected")]') - .then(function () { - return checkPicture(false); - }, fail) - .finally(done); + .fail(saveScreenshotAndFail) + .done(done); }, 3 * MINUTE); // combine various options for getPicture() generateSpecs().forEach(function (spec) { it('camera.ui.spec.3.' + spec.id + ' Combining options', function (done) { // camera is not available on iOS simulator - if (!isDevice) { + if (!isDevice && spec.options.sourceType === cameraConstants.PictureSourceType.CAMERA) { pending(); } - runCombinedSpec(spec).then(done); + runCombinedSpec(spec).done(done); }, 3 * MINUTE); }); }); it('camera.ui.util.4 Destroy the session', function (done) { - driver.quit(done); + driver + .quit() + .done(done); }, MINUTE); });