mirror of
https://github.com/apache/cordova-plugin-camera.git
synced 2025-01-19 20:02:52 +08:00
c1948fc0d4
Taking a screenshot on failure, Retry taking a picture up to 3 times, Try to restart the Appium session if it's lost
560 lines
22 KiB
JavaScript
560 lines
22 KiB
JavaScript
/*jshint node: true, jasmine: true */
|
|
|
|
// 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';
|
|
|
|
var wdHelper = require('../helpers/wdHelper');
|
|
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 DEFAULT_SCREEN_WIDTH = 360;
|
|
var DEFAULT_SCREEN_HEIGHT = 567;
|
|
var DEFAULT_WEBVIEW_CONTEXT = 'WEBVIEW';
|
|
|
|
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;
|
|
// this indicates that there was a critical error and tests cannot continue:
|
|
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;
|
|
|
|
function win() {
|
|
expect(true).toBe(true);
|
|
}
|
|
|
|
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';
|
|
}
|
|
|
|
// generates test specs by combining all the specified options
|
|
// you can add more options to test more scenarios
|
|
function generateSpecs() {
|
|
var sourceTypes = [
|
|
cameraConstants.PictureSourceType.CAMERA,
|
|
cameraConstants.PictureSourceType.PHOTOLIBRARY
|
|
],
|
|
destinationTypes = cameraConstants.DestinationType,
|
|
encodingTypes = [
|
|
cameraConstants.EncodingType.JPEG,
|
|
cameraConstants.EncodingType.PNG
|
|
],
|
|
allowEditOptions = [
|
|
true,
|
|
false
|
|
];
|
|
|
|
return cameraHelper.generateSpecs(sourceTypes, destinationTypes, encodingTypes, allowEditOptions);
|
|
}
|
|
|
|
function getPicture(options, skipUiInteractions, retry) {
|
|
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)
|
|
.context('NATIVE_APP')
|
|
.sleep(5000)
|
|
.then(function () {
|
|
if (skipUiInteractions) {
|
|
return;
|
|
}
|
|
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 / 2)})
|
|
.wait(300)
|
|
.moveTo({x: Math.round(screenWidth / 2), y: Math.round(screenHeight / 2)})
|
|
.release();
|
|
return driver
|
|
.performTouchAction(swipeRight)
|
|
.sleep(3000)
|
|
.elementByXPath('//*[@text="Gallery"]')
|
|
.then(function (element) {
|
|
return element.click().sleep(5000);
|
|
}, function () {
|
|
// if the gallery is already opened, we'd just go on:
|
|
return driver;
|
|
})
|
|
.performTouchAction(touchTile);
|
|
}
|
|
return driver
|
|
.elementByXPath('//android.widget.ImageView[contains(@resource-id,\'shutter\')]')
|
|
.click()
|
|
.sleep(3000)
|
|
.elementByXPath('//android.widget.ImageView[contains(@resource-id,\'done\')]')
|
|
.click()
|
|
.sleep(10000);
|
|
})
|
|
.then(function () {
|
|
if (skipUiInteractions) {
|
|
return;
|
|
}
|
|
if (options.hasOwnProperty('allowEdit') && options.allowEdit === true) {
|
|
return driver
|
|
.elementByXPath('//*[contains(@resource-id,\'save\')]')
|
|
.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 () {
|
|
stopFlag = 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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
} else {
|
|
if (html.indexOf('ERROR') === -1) {
|
|
fail('Unexpected success callback with result: ' + html);
|
|
}
|
|
expect(html.indexOf('ERROR')).toBe(0);
|
|
}
|
|
});
|
|
}
|
|
|
|
function runCombinedSpec(spec) {
|
|
return enterTest()
|
|
.then(function () {
|
|
return getPicture(spec.options);
|
|
})
|
|
.then(function () {
|
|
return checkPicture(true);
|
|
})
|
|
.then(win, fail);
|
|
}
|
|
|
|
function deleteImage() {
|
|
var holdTile = new wd.TouchAction();
|
|
holdTile.press({x: Math.round(screenWidth / 3), y: Math.round(screenHeight / 5)}).wait(1000).release();
|
|
return driver
|
|
.performTouchAction(holdTile)
|
|
.elementByXPath('//android.widget.TextView[@text="Delete"]')
|
|
.then(function (element) {
|
|
return element
|
|
.click()
|
|
.elementByXPath('//android.widget.Button[@text="OK"]')
|
|
.click();
|
|
}, function () {
|
|
// couldn't find Delete menu item. Possibly there is no image.
|
|
return;
|
|
});
|
|
}
|
|
|
|
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 () {
|
|
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) {
|
|
stopFlag = true;
|
|
}
|
|
return result;
|
|
}
|
|
};
|
|
}
|
|
});
|
|
});
|
|
|
|
it('camera.ui.util configuring driver and starting a session', function (done) {
|
|
getDriver().then(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);
|
|
}
|
|
})
|
|
.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();
|
|
});
|
|
}, MINUTE);
|
|
|
|
describe('Specs.', function () {
|
|
beforeEach(function (done) {
|
|
if (!stopFlag) {
|
|
return driver
|
|
.context(webviewContext)
|
|
.then(function () {
|
|
return driver; // no-op
|
|
}, function (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 = "' + STARTING_MESSAGE + '";')
|
|
.finally(done);
|
|
}
|
|
done();
|
|
}, 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
|
|
it('camera.ui.spec.1 Saving the picture to photo library', function (done) {
|
|
var options = {
|
|
quality: 50,
|
|
allowEdit: false,
|
|
sourceType: cameraConstants.PictureSourceType.CAMERA,
|
|
saveToPhotoAlbum: true
|
|
};
|
|
enterTest()
|
|
.context(webviewContext)
|
|
.then(function () {
|
|
return getPicture(options);
|
|
})
|
|
.then(function () {
|
|
isTestPictureSaved = true;
|
|
return checkPicture(true);
|
|
})
|
|
.then(win, fail)
|
|
.finally(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()
|
|
.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"]')
|
|
.then(function (element) {
|
|
return element.click().sleep(2000);
|
|
}, function () {
|
|
return driver;
|
|
});
|
|
})
|
|
.then(function () {
|
|
// if the gallery is opened on the videos page,
|
|
// there should be a "Choose video" caption
|
|
return driver
|
|
.elementByXPath('//*[@text="Choose video"]')
|
|
.fail(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
|
|
return driver
|
|
.deviceKeyEvent(4)
|
|
.sleep(2000);
|
|
}, function () {
|
|
// error means we're already in webview
|
|
return driver;
|
|
})
|
|
.finally(done);
|
|
}, 3 * MINUTE);
|
|
|
|
// getPicture(), then dismiss
|
|
// wait for the error callback to bee 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)
|
|
.then(function () {
|
|
return getPicture(options, true);
|
|
})
|
|
.sleep(5000)
|
|
.context("NATIVE_APP")
|
|
.elementByXPath('//android.widget.ImageView[contains(@resource-id,\'cancel\')]')
|
|
.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.';
|
|
});
|
|
})
|
|
.then(win, fail)
|
|
.finally(done);
|
|
}, 3 * MINUTE);
|
|
|
|
// getPicture(), then take picture but dismiss the edit
|
|
// wait for the error cllback 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)
|
|
.then(function () {
|
|
return getPicture(options, true);
|
|
})
|
|
.sleep(5000)
|
|
.context('NATIVE_APP')
|
|
.elementByXPath('//android.widget.ImageView[contains(@resource-id,\'shutter\')]')
|
|
.click()
|
|
.elementByXPath('//android.widget.ImageView[contains(@resource-id,\'done\')]')
|
|
.click()
|
|
.elementByXPath('//*[contains(@resource-id,\'discard\')]')
|
|
.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.';
|
|
});
|
|
})
|
|
.then(win, fail)
|
|
.finally(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);
|
|
}, 3 * MINUTE);
|
|
});
|
|
|
|
|
|
it('camera.ui.util Delete test image from device library', function (done) {
|
|
if (checkStopFlag()) {
|
|
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();
|
|
}, 3 * MINUTE);
|
|
|
|
});
|
|
|
|
it('camera.ui.util Destroy the session', function (done) {
|
|
return driver.quit(done);
|
|
}, 10000);
|
|
});
|