CB-11183 Appium tests: Added image verification

This commit is contained in:
Alexander Sorokin 2016-05-18 11:48:29 +03:00
parent d7ca7edf88
commit 278b527702
3 changed files with 472 additions and 81 deletions

View File

@ -72,9 +72,9 @@ describe('Camera tests Android.', function () {
});
}
// generates test specs by combining all the specified options
// combinines specified options in all possible variations
// you can add more options to test more scenarios
function generateSpecs() {
function generateOptions() {
var sourceTypes = [
cameraConstants.PictureSourceType.CAMERA,
cameraConstants.PictureSourceType.PHOTOLIBRARY
@ -156,30 +156,27 @@ describe('Camera tests Android.', function () {
}
})
.fail(function (failure) {
console.log(failure);
throw failure;
});
}
// checks if the picture was successfully taken
// if shouldLoad is falsy, ensures that the error callback was called
function checkPicture(shouldLoad) {
function checkPicture(shouldLoad, options) {
if (!options) {
options = {};
}
return driver
.context(webviewContext)
.setAsyncScriptTimeout(MINUTE)
.executeAsync(cameraHelper.checkPicture, [getCurrentPromiseId()])
.executeAsync(cameraHelper.checkPicture, [getCurrentPromiseId(), options])
.then(function (result) {
if (shouldLoad) {
if (result.length === 0) {
throw 'The result is an empty string.';
}
if (result.indexOf('ERROR') >= 0) {
throw result;
}
} else {
if (result.indexOf('ERROR') === -1) {
throw 'Unexpected success callback with result: ' + result;
if (result !== 'OK') {
fail(result);
}
} else if (result.indexOf('ERROR') === -1) {
throw 'Unexpected success callback with result: ' + result;
}
});
}
@ -241,17 +238,19 @@ describe('Camera tests Android.', function () {
.fail(saveScreenshotAndFail);
}
function runCombinedSpec(s) {
var spec = function () {
// 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(s.options);
return getPicture(options);
})
.then(function () {
return checkPicture(true);
return checkPicture(true, options);
});
};
return tryRunSpec(spec);
}
it('camera.ui.util configuring driver and starting a session', function (done) {
@ -279,24 +278,17 @@ describe('Camera tests Android.', function () {
describe('Specs.', function () {
// getPicture() with saveToPhotoLibrary = true
it('camera.ui.spec.1 Saving a picture to the photo library', function (done) {
var spec = function() {
var options = {
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.CAMERA,
saveToPhotoAlbum: true
};
return driver
.then(function () {
return getPicture(options);
})
.then(function () {
isTestPictureSaved = true;
return checkPicture(true);
});
};
var spec = generateSpec({
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.CAMERA,
saveToPhotoAlbum: true
});
return tryRunSpec(spec)
tryRunSpec(spec)
.then(function () {
isTestPictureSaved = true;
})
.done(done);
}, 10 * MINUTE);
@ -346,18 +338,19 @@ describe('Camera tests Android.', function () {
});
});
};
return tryRunSpec(spec)
.done(done);
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 spec = function () {
var options = { quality: 50,
allowEdit: true,
sourceType: cameraConstants.PictureSourceType.CAMERA,
destinationType: cameraConstants.DestinationType.FILE_URI };
var options = {
quality: 50,
allowEdit: true,
sourceType: cameraConstants.PictureSourceType.CAMERA,
destinationType: cameraConstants.DestinationType.FILE_URI
};
return driver
.then(function () {
return getPicture(options, true);
@ -370,18 +363,19 @@ describe('Camera tests Android.', function () {
});
};
return tryRunSpec(spec)
.done(done);
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 spec = function () {
var options = { quality: 50,
allowEdit: true,
sourceType: cameraConstants.PictureSourceType.CAMERA,
destinationType: cameraConstants.DestinationType.FILE_URI };
var options = {
quality: 50,
allowEdit: true,
sourceType: cameraConstants.PictureSourceType.CAMERA,
destinationType: cameraConstants.DestinationType.FILE_URI
};
return driver
.then(function () {
return getPicture(options, true);
@ -398,15 +392,96 @@ describe('Camera tests Android.', function () {
});
};
return tryRunSpec(spec)
.done(done);
tryRunSpec(spec).done(done);
}, 10 * MINUTE);
it('camera.ui.spec.5 Verifying target image size, sourceType=CAMERA', function (done) {
var spec = generateSpec({
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.CAMERA,
saveToPhotoAlbum: false,
targetWidth: 210,
targetHeight: 210
});
tryRunSpec(spec).done(done);
}, 10 * MINUTE);
it('camera.ui.spec.6 Verifying target image size, sourceType=PHOTOLIBRARY', function (done) {
var spec = generateSpec({
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
saveToPhotoAlbum: false,
targetWidth: 210,
targetHeight: 210
});
tryRunSpec(spec).done(done);
}, 10 * MINUTE);
it('camera.ui.spec.7 Verifying target image size, sourceType=CAMERA, DestinationType=NATIVE_URI', function (done) {
var spec = generateSpec({
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.CAMERA,
destinationType: cameraConstants.DestinationType.NATIVE_URI,
saveToPhotoAlbum: false,
targetWidth: 210,
targetHeight: 210
});
tryRunSpec(spec).done(done);
}, 10 * MINUTE);
it('camera.ui.spec.8 Verifying target image size, sourceType=PHOTOLIBRARY, DestinationType=NATIVE_URI', function (done) {
var spec = generateSpec({
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
destinationType: cameraConstants.DestinationType.NATIVE_URI,
saveToPhotoAlbum: false,
targetWidth: 210,
targetHeight: 210
});
tryRunSpec(spec).done(done);
}, 10 * MINUTE);
it('camera.ui.spec.9 Verifying target image size, sourceType=CAMERA, DestinationType=NATIVE_URI, quality=100', function (done) {
var spec = generateSpec({
quality: 100,
allowEdit: true,
sourceType: cameraConstants.PictureSourceType.CAMERA,
destinationType: cameraConstants.DestinationType.NATIVE_URI,
saveToPhotoAlbum: false,
targetWidth: 305,
targetHeight: 305
});
tryRunSpec(spec).done(done);
}, 10 * MINUTE);
it('camera.ui.spec.10 Verifying target image size, sourceType=PHOTOLIBRARY, DestinationType=NATIVE_URI, quality=100', function (done) {
var spec = generateSpec({
quality: 100,
allowEdit: true,
sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
destinationType: cameraConstants.DestinationType.NATIVE_URI,
saveToPhotoAlbum: false,
targetWidth: 305,
targetHeight: 305
});
tryRunSpec(spec).done(done);
}, 10 * MINUTE);
// combine various options for getPicture()
generateSpecs().forEach(function (spec) {
it('camera.ui.spec.5.' + spec.id + ' Combining options. ' + spec.description, function (done) {
runCombinedSpec(spec)
.done(done);
generateOptions().forEach(function (spec) {
it('camera.ui.spec.11.' + spec.id + ' Combining options. ' + spec.description, function (done) {
var s = generateSpec(spec.options);
tryRunSpec(s).done(done);
}, 10 * MINUTE);
});

View File

@ -1,5 +1,5 @@
/*jshint node: true */
/* global Q */
/* global Q, resolveLocalFileSystemURI, Camera, cordova */
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
@ -94,7 +94,13 @@ module.exports.generateSpecs = function (sourceTypes, destinationTypes, encoding
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);
@ -103,11 +109,185 @@ module.exports.getPicture = function (opts, pid) {
}, opts);
};
module.exports.checkPicture = function (pid, cb) {
// verifies taken picture when the promise is resolved,
// calls a callback with 'OK' if everything is good,
// calls a callback with 'ERROR: <error message>' 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, 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 skipFileTypeCheck = isAndroid &&
options.quality === 100 &&
!options.targetWidth && !options.targetHeight &&
!options.correctOrientation;
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.resolveLocalFileSystemURI) {
errorCallback('Cannot read file. Please install cordova-plugin-file to fix this.');
return;
}
resolveLocalFileSystemURI(result, function (entry) {
if (skipFileTypeCheck) {
displayFile(entry);
} else {
verifyFile(entry);
}
});
} 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.createElement('img');
document.body.appendChild(imgEl);
var timedOut = false;
var loadTimeout = setTimeout(function () {
timedOut = true;
document.body.removeChild(imgEl);
errorCallback('The image did not load: ' + image.substring(0, 150));
}, 10000);
var done = function (status) {
if (!timedOut) {
clearTimeout(loadTimeout);
document.body.removeChild(imgEl);
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) {
cb(result);
}, function (err) {
cb('ERROR: ' + err);
verifyResult(result);
})
.fail(function (e) {
errorCallback(e);
});
};

View File

@ -66,7 +66,7 @@ describe('Camera tests iOS.', function () {
// generates test specs by combining all the specified options
// you can add more options to test more scenarios
function generateSpecs() {
function generateOptions() {
var sourceTypes = cameraConstants.PictureSourceType;
var destinationTypes = cameraConstants.DestinationType;
var encodingTypes = cameraConstants.EncodingType;
@ -149,33 +149,34 @@ describe('Camera tests iOS.', function () {
// checks if the picture was successfully taken
// if shouldLoad is falsy, ensures that the error callback was called
function checkPicture(shouldLoad) {
function checkPicture(shouldLoad, options) {
if (!options) {
options = {};
}
return driver
.context(webviewContext)
.setAsyncScriptTimeout(MINUTE)
.executeAsync(cameraHelper.checkPicture, [getCurrentPromiseId()])
.executeAsync(cameraHelper.checkPicture, [getCurrentPromiseId(), options])
.then(function (result) {
if (shouldLoad) {
expect(result.length).toBeGreaterThan(0);
if (result.indexOf('ERROR') >= 0) {
return fail(result);
if (result !== 'OK') {
fail(result);
}
} else {
if (result.indexOf('ERROR') === -1) {
return fail('Unexpected success callback with result: ' + result);
}
expect(result.indexOf('ERROR')).toBe(0);
} else if (result.indexOf('ERROR') === -1) {
throw 'Unexpected success callback with result: ' + result;
}
});
}
function runCombinedSpec(spec) {
// takes a picture with the specified options
// and then verifies it
function runSpec(options) {
return driver
.then(function () {
return getPicture(spec.options);
return getPicture(options);
})
.then(function () {
return checkPicture(true);
return checkPicture(true, options);
})
.fail(saveScreenshotAndFail);
}
@ -219,9 +220,8 @@ describe('Camera tests iOS.', function () {
// getPicture(), then dismiss
// wait for the error callback to be called
it('camera.ui.spec.2 Dismissing the camera', function (done) {
// camera is not available on the iOS simulator
if (!isDevice) {
pending();
pending('Camera is not available on iOS simulator');
}
var options = { sourceType: cameraConstants.PictureSourceType.CAMERA };
driver
@ -235,20 +235,156 @@ describe('Camera tests iOS.', function () {
.done(done);
}, 3 * MINUTE);
it('camera.ui.spec.3 Verifying target image size, sourceType=CAMERA', function (done) {
if (!isDevice) {
pending('Camera is not available on iOS simulator');
}
var options = {
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.CAMERA,
saveToPhotoAlbum: false,
targetWidth: 210,
targetHeight: 210
};
runSpec(options).done(done);
}, 3 * 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(done);
}, 3 * 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(done);
}, 3 * MINUTE);
it('camera.ui.spec.6 Verifying target image size, sourceType=CAMERA, destinationType=NATIVE_URI', function (done) {
if (!isDevice) {
pending('Camera is not available on iOS simulator');
}
var options = {
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.CAMERA,
destinationType: cameraConstants.DestinationType.NATIVE_URI,
saveToPhotoAlbum: false,
targetWidth: 210,
targetHeight: 210
};
runSpec(options).done(done);
}, 3 * MINUTE);
it('camera.ui.spec.7 Verifying target image size, sourceType=SAVEDPHOTOALBUM, destinationType=NATIVE_URI', function (done) {
var options = {
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.SAVEDPHOTOALBUM,
destinationType: cameraConstants.DestinationType.NATIVE_URI,
saveToPhotoAlbum: false,
targetWidth: 210,
targetHeight: 210
};
runSpec(options).done(done);
}, 3 * MINUTE);
it('camera.ui.spec.8 Verifying target image size, sourceType=PHOTOLIBRARY, destinationType=NATIVE_URI', function (done) {
var options = {
quality: 50,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
destinationType: cameraConstants.DestinationType.NATIVE_URI,
saveToPhotoAlbum: false,
targetWidth: 210,
targetHeight: 210
};
runSpec(options).done(done);
}, 3 * MINUTE);
it('camera.ui.spec.9 Verifying target image size, sourceType=CAMERA, destinationType=NATIVE_URI, quality=100', function (done) {
if (!isDevice) {
pending('Camera is not available on iOS simulator');
}
var options = {
quality: 100,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.CAMERA,
destinationType: cameraConstants.DestinationType.NATIVE_URI,
saveToPhotoAlbum: false,
targetWidth: 305,
targetHeight: 305
};
runSpec(options).done(done);
}, 3 * MINUTE);
it('camera.ui.spec.10 Verifying target image size, sourceType=SAVEDPHOTOALBUM, destinationType=NATIVE_URI, quality=100', function (done) {
var options = {
quality: 100,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.SAVEDPHOTOALBUM,
destinationType: cameraConstants.DestinationType.NATIVE_URI,
saveToPhotoAlbum: false,
targetWidth: 305,
targetHeight: 305
};
runSpec(options).done(done);
}, 3 * MINUTE);
it('camera.ui.spec.11 Verifying target image size, sourceType=PHOTOLIBRARY, destinationType=NATIVE_URI, quality=100', function (done) {
var options = {
quality: 100,
allowEdit: false,
sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
destinationType: cameraConstants.DestinationType.NATIVE_URI,
saveToPhotoAlbum: false,
targetWidth: 305,
targetHeight: 305
};
runSpec(options).done(done);
}, 3 * MINUTE);
// combine various options for getPicture()
generateSpecs().forEach(function (spec) {
it('camera.ui.spec.3.' + spec.id + ' Combining options. ' + spec.description, function (done) {
// camera is not available on iOS simulator
generateOptions().forEach(function (spec) {
it('camera.ui.spec.12.' + spec.id + ' Combining options. ' + spec.description, function (done) {
if (!isDevice && spec.options.sourceType === cameraConstants.PictureSourceType.CAMERA) {
pending();
pending('Camera is not available on iOS simulator');
}
runCombinedSpec(spec).done(done);
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(done);
}, 3 * MINUTE);
});
});
it('camera.ui.util.4 Destroy the session', function (done) {
it('camera.ui.util Destroy the session', function (done) {
driver
.quit()
.done(done);