CB-10639 Appium tests: Added some timeouts,

Taking a screenshot on failure,
Retry taking a picture up to 3 times,
Try to restart the Appium session if it's lost
This commit is contained in:
Alexander Sorokin 2016-02-22 18:37:41 +03:00
parent f792aaacc3
commit c1948fc0d4
5 changed files with 176 additions and 82 deletions

View File

@ -1,36 +1,43 @@
/*jslint node: true, plusplus: true */ /*jshint node: true, jasmine: true */
/*global beforeEach, afterEach */
/*global describe, it, xit, expect, jasmine */
'use strict';
// these tests are meant to be executed by Cordova Medic Appium runner // these tests are meant to be executed by Cordova Medic Appium runner
// you can find it here: https://github.com/apache/cordova-medic/ // you can find it here: https://github.com/apache/cordova-medic/
// it is not necessary to do a full CI setup to run these tests // 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-medic/medic/medic.js appium --platform android --plugins cordova-plugin-camera"
'use strict';
var wdHelper = require('../helpers/wdHelper'); var wdHelper = require('../helpers/wdHelper');
var wd = wdHelper.getWD(); var wd = wdHelper.getWD();
var cameraConstants = require('../../www/CameraConstants'); var cameraConstants = require('../../www/CameraConstants');
var cameraHelper = require('../helpers/cameraHelper'); 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 DEFAULT_SCREEN_WIDTH = 360;
var DEFAULT_SCREEN_HEIGHT = 567;
var DEFAULT_WEBVIEW_CONTEXT = 'WEBVIEW';
describe('Camera tests Android.', function () { describe('Camera tests Android.', function () {
var driver, var driver;
startingMessage = 'Ready for action!', // the name of webview context, it will be changed to match needed context if there are named ones:
// the name of webview context, it will be changed to match needed context if there are named ones: var webviewContext = DEFAULT_WEBVIEW_CONTEXT;
webviewContext = 'WEBVIEW', // this indicates that the device library has the test picture:
// this indicates if device library has test picture: var isTestPictureSaved = false;
isTestPictureSaved = false, // this indicates that there was a critical error and tests cannot continue:
// this indecates that there was critical error and tests cannot continue: var stopFlag = false;
stopFlag = false, // we need to know the screen width and height to properly click on an image in the gallery
// we need to know the screen width and height to properly click on the first image in the gallery var screenWidth = DEFAULT_SCREEN_WIDTH;
screenWidth = 360, var screenHeight = DEFAULT_SCREEN_HEIGHT;
screenHeight = 567;
function win() { function win() {
expect(true).toBe(true); expect(true).toBe(true);
} }
function fail(error) { function fail(error) {
screenshotHelper.saveScreenshot(driver);
if (error && error.message) { if (error && error.message) {
console.log('An error occured: ' + error.message); console.log('An error occured: ' + error.message);
expect(true).toFailWithMessage(error.message); expect(true).toFailWithMessage(error.message);
@ -42,11 +49,12 @@ describe('Camera tests Android.', function () {
throw error; throw error;
} }
// no message provided :( // no message provided :(
console.log('An error without description occured');
expect(true).toBe(false); expect(true).toBe(false);
throw 'An error without description occured'; throw 'An error without description occured';
} }
// generates test specs by combining all the specified options
// you can add more options to test more scenarios
function generateSpecs() { function generateSpecs() {
var sourceTypes = [ var sourceTypes = [
cameraConstants.PictureSourceType.CAMERA, cameraConstants.PictureSourceType.CAMERA,
@ -65,17 +73,22 @@ describe('Camera tests Android.', function () {
return cameraHelper.generateSpecs(sourceTypes, destinationTypes, encodingTypes, allowEditOptions); return cameraHelper.generateSpecs(sourceTypes, destinationTypes, encodingTypes, allowEditOptions);
} }
function getPicture(options, skipUiInteractions) { function getPicture(options, skipUiInteractions, retry) {
if (!options) { if (!options) {
options = {}; options = {};
} }
if (typeof retry === 'undefined') {
retry = 1;
}
var command = "navigator.camera.getPicture(function (result) { document.getElementById('info').innerHTML = result.slice(0, 100); }, " + 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) + ");"; "function (err) { document.getElementById('info').innerHTML = 'ERROR: ' + err; }," + JSON.stringify(options) + ");";
return driver return driver
.context(webviewContext) .context(webviewContext)
.execute(command) .execute(command)
.sleep(5000) .sleep(7000)
.context('NATIVE_APP') .context('NATIVE_APP')
.sleep(5000)
.then(function () { .then(function () {
if (skipUiInteractions) { if (skipUiInteractions) {
return; return;
@ -105,8 +118,10 @@ describe('Camera tests Android.', function () {
return driver return driver
.elementByXPath('//android.widget.ImageView[contains(@resource-id,\'shutter\')]') .elementByXPath('//android.widget.ImageView[contains(@resource-id,\'shutter\')]')
.click() .click()
.sleep(3000)
.elementByXPath('//android.widget.ImageView[contains(@resource-id,\'done\')]') .elementByXPath('//android.widget.ImageView[contains(@resource-id,\'done\')]')
.click(); .click()
.sleep(10000);
}) })
.then(function () { .then(function () {
if (skipUiInteractions) { if (skipUiInteractions) {
@ -122,25 +137,24 @@ describe('Camera tests Android.', function () {
if (!skipUiInteractions) { if (!skipUiInteractions) {
return driver.sleep(10000); 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() { function enterTest() {
if (stopFlag) {
return driver
.context(webviewContext)
.then(function () {
throw 'stopFlag is on!';
});
}
return driver return driver
// trying to determine where we are // trying to determine where we are
.context(webviewContext) .context(webviewContext)
.then(function (result) {
console.log(result);
})
.fail(function (error) { .fail(function (error) {
expect(true).toFailWithMessage(error); fail(error);
}) })
.elementById('info') .elementById('info')
.then(function () { .then(function () {
@ -175,16 +189,16 @@ describe('Camera tests Android.', function () {
.elementById('info') .elementById('info')
.getAttribute('innerHTML') .getAttribute('innerHTML')
.then(function (html) { .then(function (html) {
if (html.indexOf(startingMessage) >= 0) { if (html.indexOf(STARTING_MESSAGE) >= 0) {
expect(true).toFailWithMessage('No callback was fired'); expect(true).toFailWithMessage('No callback was fired');
} else if (shouldLoad) { } else if (shouldLoad) {
expect(html.length).toBeGreaterThan(0); expect(html.length).toBeGreaterThan(0);
if (html.indexOf('ERROR') >= 0) { if (html.indexOf('ERROR') >= 0) {
expect(true).toFailWithMessage(html); fail(html);
} }
} else { } else {
if (html.indexOf('ERROR') === -1) { if (html.indexOf('ERROR') === -1) {
expect(true).toFailWithMessage('Unexpected success callback with result: ' + html); fail('Unexpected success callback with result: ' + html);
} }
expect(html.indexOf('ERROR')).toBe(0); expect(html.indexOf('ERROR')).toBe(0);
} }
@ -219,6 +233,18 @@ describe('Camera tests Android.', function () {
}); });
} }
function getDriver() {
driver = wdHelper.getDriver('Android');
return driver;
}
function checkStopFlag() {
if (stopFlag) {
fail('Something went wrong: the stopFlag is on. Please see the log for more details.');
}
return stopFlag;
}
beforeEach(function () { beforeEach(function () {
jasmine.addMatchers({ jasmine.addMatchers({
toFailWithMessage : function () { toFailWithMessage : function () {
@ -229,7 +255,12 @@ describe('Camera tests Android.', function () {
pass: false, pass: false,
message: msg message: msg
}; };
if (msg.indexOf('Error response status: 6') >= 0) { // 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) {
stopFlag = true; stopFlag = true;
} }
return result; return result;
@ -240,12 +271,8 @@ describe('Camera tests Android.', function () {
}); });
it('camera.ui.util configuring driver and starting a session', function (done) { it('camera.ui.util configuring driver and starting a session', function (done) {
driver = wdHelper.getDriver('Android', function () { getDriver().then(done);
return driver }, 5 * MINUTE);
.sleep(10000)
.finally(done);
});
}, 320000);
it('camera.ui.util determine webview context name', function (done) { it('camera.ui.util determine webview context name', function (done) {
var i = 0; var i = 0;
@ -261,7 +288,7 @@ describe('Camera tests Android.', function () {
} }
done(); done();
}); });
}, 30000); }, MINUTE);
it('camera.ui.util determine screen dimensions', function (done) { it('camera.ui.util determine screen dimensions', function (done) {
return enterTest() return enterTest()
@ -270,22 +297,22 @@ describe('Camera tests Android.', function () {
.elementById('info') .elementById('info')
.getAttribute('innerHTML') .getAttribute('innerHTML')
.then(function (html) { .then(function (html) {
if (html !== startingMessage) { if (html !== STARTING_MESSAGE) {
screenWidth = Number(html); screenWidth = Number(html);
} }
}) })
.execute('document.getElementById(\'info\').innerHTML = \'' + startingMessage + '\';') .execute('document.getElementById(\'info\').innerHTML = \'' + STARTING_MESSAGE + '\';')
.execute('document.getElementById(\'info\').innerHTML = window.innerHeight;') .execute('document.getElementById(\'info\').innerHTML = window.innerHeight;')
.sleep(5000) .sleep(5000)
.elementById('info') .elementById('info')
.getAttribute('innerHTML') .getAttribute('innerHTML')
.then(function (html) { .then(function (html) {
if (html !== startingMessage) { if (html !== STARTING_MESSAGE) {
screenHeight = Number(html); screenHeight = Number(html);
} }
done(); done();
}); });
}, 60000); }, MINUTE);
describe('Specs.', function () { describe('Specs.', function () {
beforeEach(function (done) { beforeEach(function (done) {
@ -295,13 +322,36 @@ describe('Camera tests Android.', function () {
.then(function () { .then(function () {
return driver; // no-op return driver; // no-op
}, function (error) { }, function (error) {
expect(true).toFailWithMessage(error); if (error.message.indexOf('Error response status: 6') >= 0) {
// the session has expired but we can fix this!
console.log('The session has expired. Trying to start a new one...');
return getDriver();
} else {
expect(true).toFailWithMessage(error);
}
}) })
.execute('document.getElementById("info").innerHTML = "' + startingMessage + '";') .execute('document.getElementById("info").innerHTML = "' + STARTING_MESSAGE + '";')
.finally(done); .finally(done);
} }
done(); done();
}, 600000); }, 3 * MINUTE);
afterEach(function (done) {
if (!stopFlag) {
done();
return;
}
// recreate the session if there was a critical error in the spec
return driver
.quit()
.then(function () {
return getDriver()
.then(function () {
stopFlag = false;
done();
});
});
}, 3 * MINUTE);
// getPicture() with saveToPhotoLibrary = true // getPicture() with saveToPhotoLibrary = true
it('camera.ui.spec.1 Saving the picture to photo library', function (done) { it('camera.ui.spec.1 Saving the picture to photo library', function (done) {
@ -322,12 +372,11 @@ describe('Camera tests Android.', function () {
}) })
.then(win, fail) .then(win, fail)
.finally(done); .finally(done);
}, 300000); }, 3 * MINUTE);
// getPicture() with mediaType: VIDEO, sourceType: PHOTOLIBRARY // getPicture() with mediaType: VIDEO, sourceType: PHOTOLIBRARY
it('camera.ui.spec.2 Selecting only videos', function (done) { it('camera.ui.spec.2 Selecting only videos', function (done) {
if (stopFlag) { if (checkStopFlag()) {
expect(true).toFailWithMessage('Couldn\'t start tests execution.');
done(); done();
return; return;
} }
@ -347,6 +396,7 @@ describe('Camera tests Android.', function () {
} }
}) })
.context('NATIVE_APP') .context('NATIVE_APP')
.sleep(5000)
.then(function () { .then(function () {
// try to find "Gallery" menu item // try to find "Gallery" menu item
// if there's none, the gallery should be already opened // if there's none, the gallery should be already opened
@ -383,13 +433,12 @@ describe('Camera tests Android.', function () {
return driver; return driver;
}) })
.finally(done); .finally(done);
}, 300000); }, 3 * MINUTE);
// getPicture(), then dismiss // getPicture(), then dismiss
// wait for the error callback to bee called // wait for the error callback to bee called
it('camera.ui.spec.3 Dismissing the camera', function (done) { it('camera.ui.spec.3 Dismissing the camera', function (done) {
if (stopFlag) { if (checkStopFlag()) {
expect(true).toFailWithMessage('Couldn\'t start tests execution.');
done(); done();
return; return;
} }
@ -418,13 +467,12 @@ describe('Camera tests Android.', function () {
}) })
.then(win, fail) .then(win, fail)
.finally(done); .finally(done);
}, 300000); }, 3 * MINUTE);
// getPicture(), then take picture but dismiss the edit // getPicture(), then take picture but dismiss the edit
// wait for the error cllback to be called // wait for the error cllback to be called
it('camera.ui.spec.4 Dismissing the edit', function (done) { it('camera.ui.spec.4 Dismissing the edit', function (done) {
if (stopFlag) { if (checkStopFlag()) {
expect(true).toFailWithMessage('Couldn\'t start tests execution.');
done(); done();
return; return;
} }
@ -458,24 +506,22 @@ describe('Camera tests Android.', function () {
}) })
.then(win, fail) .then(win, fail)
.finally(done); .finally(done);
}, 300000); }, 3 * MINUTE);
// combine various options for getPicture() // combine various options for getPicture()
generateSpecs().forEach(function (spec) { generateSpecs().forEach(function (spec) {
it('camera.ui.spec.5.' + spec.id + ' Combining options', function (done) { it('camera.ui.spec.5.' + spec.id + ' Combining options', function (done) {
if (stopFlag) { if (checkStopFlag()) {
expect(true).toFailWithMessage('Couldn\'t start tests execution.');
done(); done();
return; return;
} }
runCombinedSpec(spec).then(done); runCombinedSpec(spec).then(done);
}, 3 * 60 * 1000); }, 3 * MINUTE);
}); });
it('camera.ui.util Delete test image from device library', function (done) { it('camera.ui.util Delete test image from device library', function (done) {
if (stopFlag) { if (checkStopFlag()) {
expect(true).toFailWithMessage('Couldn\'t start tests executeion.');
done(); done();
return; return;
} }
@ -503,7 +549,7 @@ describe('Camera tests Android.', function () {
} }
// couldn't save test picture earlier, so nothing to delete here // couldn't save test picture earlier, so nothing to delete here
done(); done();
}, 300000); }, 3 * MINUTE);
}); });

View File

@ -1,4 +1,4 @@
/*jslint node: true, plusplus: true */ /*jshint node: true */
'use strict'; 'use strict';
var cameraConstants = require('../../www/CameraConstants'); var cameraConstants = require('../../www/CameraConstants');

View File

@ -0,0 +1,37 @@
/* jshint node: true */
'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(screenshotPath + generateScreenshotName())
.then(function () {
return driver.context(oldContext);
});
};

View File

@ -1,4 +1,4 @@
/*jslint node: true, plusplus: true */ /* jshint node: true */
'use strict'; 'use strict';
var wd = global.WD || require('wd'); var wd = global.WD || require('wd');
@ -24,11 +24,10 @@ module.exports.getDriver = function (platform, callback) {
} }
driver = wd.promiseChainRemote(serverConfig); driver = wd.promiseChainRemote(serverConfig);
module.exports.configureLogging(driver); module.exports.configureLogging(driver);
driver.init(driverConfig).setImplicitWaitTimeout(10000)
return driver.init(driverConfig).setImplicitWaitTimeout(10000)
.sleep(20000) // wait for the app to load .sleep(20000) // wait for the app to load
.then(callback); .then(callback);
return driver;
}; };
module.exports.getWD = function () { module.exports.getWD = function () {

View File

@ -1,6 +1,10 @@
/*jslint node: true, plusplus: true */ /*jshint node: true, jasmine: true */
/*global beforeEach, afterEach */
/*global describe, it, xit, expect, jasmine, pending */ // these tests are meant to be executed by Cordova Medic Appium runner
// you can find it here: https://github.com/apache/cordova-medic/
// it is not necessary to do a full CI setup to run these tests
// just run "node cordova-medic/medic/medic.js appium --platform android --plugins cordova-plugin-camera"
'use strict'; 'use strict';
var wdHelper = require('../helpers/wdHelper'); var wdHelper = require('../helpers/wdHelper');
@ -8,32 +12,40 @@ var wd = wdHelper.getWD();
var isDevice = global.DEVICE; var isDevice = global.DEVICE;
var cameraConstants = require('../../www/CameraConstants'); var cameraConstants = require('../../www/CameraConstants');
var cameraHelper = require('../helpers/cameraHelper'); var cameraHelper = require('../helpers/cameraHelper');
var screenshotHelper = require('../helpers/screenshotHelper');
var MINUTE = 60 * 1000;
var DEFAULT_WEBVIEW_CONTEXT = 'WEBVIEW_1';
describe('Camera tests iOS.', function () { describe('Camera tests iOS.', function () {
var driver,
webviewContext = 'WEBVIEW_1', var driver;
startingMessage = 'Ready for action!'; var webviewContext = DEFAULT_WEBVIEW_CONTEXT;
var startingMessage = 'Ready for action!';
function win() { function win() {
expect(true).toBe(true); expect(true).toBe(true);
} }
function fail(error) { function fail(error) {
screenshotHelper.saveScreenshot(driver);
if (error && error.message) { if (error && error.message) {
console.log('An error occured: ' + error.message); console.log('An error occured: ' + error.message);
expect(true).toFailWithMessage(error.message); expect(true).toFailWithMessage(error.message);
return; throw error.message;
} }
if (error) { if (error) {
console.log('Failed expectation: ' + error); console.log('Failed expectation: ' + error);
expect(true).toFailWithMessage(error); expect(true).toFailWithMessage(error);
return; throw error;
} }
// no message provided :( // no message provided :(
console.log('An error without description occured');
expect(true).toBe(false); expect(true).toBe(false);
throw 'An error without description occured';
} }
// generates test specs by combining all the specified options
// you can add more options to test more scenarios
function generateSpecs() { function generateSpecs() {
var sourceTypes = [ var sourceTypes = [
cameraConstants.PictureSourceType.CAMERA, cameraConstants.PictureSourceType.CAMERA,
@ -198,7 +210,7 @@ describe('Camera tests iOS.', function () {
it('camera.ui.util Configuring driver and starting a session', function (done) { it('camera.ui.util Configuring driver and starting a session', function (done) {
driver = wdHelper.getDriver('iOS', done); driver = wdHelper.getDriver('iOS', done);
}, 240000); }, 3 * MINUTE);
describe('Specs.', function () { describe('Specs.', function () {
// getPicture() with mediaType: VIDEO, sourceType: PHOTOLIBRARY // getPicture() with mediaType: VIDEO, sourceType: PHOTOLIBRARY
@ -213,7 +225,7 @@ describe('Camera tests iOS.', function () {
.elementByName('Cancel') .elementByName('Cancel')
.click() .click()
.finally(done); .finally(done);
}, 300000); }, 3 * MINUTE);
// getPicture(), then dismiss // getPicture(), then dismiss
// wait for the error callback to bee called // wait for the error callback to bee called
@ -235,7 +247,7 @@ describe('Camera tests iOS.', function () {
return checkPicture(false); return checkPicture(false);
}, fail) }, fail)
.finally(done); .finally(done);
}, 300000); }, 3 * MINUTE);
// combine various options for getPicture() // combine various options for getPicture()
generateSpecs().forEach(function (spec) { generateSpecs().forEach(function (spec) {
@ -245,7 +257,7 @@ describe('Camera tests iOS.', function () {
pending(); pending();
} }
runCombinedSpec(spec).then(done); runCombinedSpec(spec).then(done);
}, 3 * 60 * 1000); }, 3 * MINUTE);
}); });
}); });