Commit f596613d authored by Jérome Perrin's avatar Jérome Perrin

Error handling and css fonts in Selenium extensions

See merge request nexedi/erp5!1220
parents b683393f 3ef27120
Pipeline #10813 failed with stage
......@@ -2,6 +2,56 @@
* Selenium extensions for the ERP5 project
*/
/**
* Wrap a promise to make it usable by selenium commands.
*
* If the promise is rejected, the command will fail with the promise rejection value.
* If the promise is resovled, the resolved value is not used.
*
* The asynchronicity of do* method is as follow Selenium.prototype.doXXX
* returns a function and this function will be called again and again until:
* * function returns true, which means step is successfull
* * function returns false, which means step is not finished and function will be called again
* * an execption is raised, in that case the step is failed
* * global timeout is reached.
* we implement the state management with similar approach as what's discussed
* https://stackoverflow.com/questions/30564053/how-can-i-synchronously-determine-a-javascript-promises-state
*
* @param {Promise} promise the promise to await
* @returns {() => boolean}
*/
function wrapPromise(promise) {
/** @type {'pending' | 'resolved' | 'rejected'} */
var promiseState;
var rejectionValue;
return () => {
if (promiseState === 'pending') {
return false;
}
if (promiseState === 'resolved') {
return true;
}
if (promiseState === 'rejected') {
Assert.fail("" + rejectionValue);
return true
}
promise.then(
function () {
promiseState = 'resolved';
}).catch(
function (error) {
console.error(error);
promiseState = 'rejected';
rejectionValue = error;
}
);
promiseState = 'pending';
return false;
}
}
/**
* You can set file data to file input field without security error.
* <tr>
......@@ -9,30 +59,21 @@
* <td>field_my_file</td>
* <td>/data.jpg myfilename.jpg</td>
* </tr>
*
* @param {string} locator the selenium locator
* @param {string} url_and_filename the URL and filename, separated by space
* @returns {() => boolean}
*/
Selenium.prototype.doSetFile = function(locator, url_and_filename) {
var tmpArray = url_and_filename.split(' ', 2);
var url = tmpArray[0];
var fileName = tmpArray[1];
var rejectionValue,
promiseState;
// same technique as doVerifyImageMatchSnapshot below
var assertFileSet = () => {
if (promiseState === 'pending') {
return false;
}
if (promiseState === 'resolved') {
return true;
}
if (promiseState === 'rejected') {
Assert.fail(rejectionValue);
}
promiseState = 'pending';
if (!fileName) {
throw new Error('file name must not be empty.');
}
var fileField = this.page().findElement(locator);
if (!fileName) {
throw new Error('file name must not be empty.');
}
var fileField = this.page().findElement(locator);
return wrapPromise(
fetch(url)
.then(function(response) {
if (!response.ok) {
......@@ -48,19 +89,7 @@ Selenium.prototype.doSetFile = function(locator, url_and_filename) {
new DataTransfer();
dT.items.add(new File([blob], fileName));
fileField.files = dT.files;
})
.then(
function() {
promiseState = 'resolved';
},
function(error) {
console.error(error);
promiseState = 'rejected';
rejectionValue = 'Error setting file ' + error;
}
);
}
return assertFileSet;
}));
};
......@@ -157,10 +186,10 @@ function getReferenceImageURL(testPathName) {
* Helper function to generate a DOM elements
*
* @param {string} tagName name of the element
* @param {Node?} childList list of child elements
* @param {Map<string,any>?} attributeDict attributes
* @param {string?} textContent
* @return {Node}
* @param {Node[]} [childList] list of child elements
* @param {Object} [attributeDict] attributes
* @param {string} [textContent]
* @return {HTMLElement}
*/
function generateElement(tagName, childList, attributeDict, textContent) {
var element = document.createElement(tagName);
......@@ -174,6 +203,9 @@ function generateElement(tagName, childList, attributeDict, textContent) {
element.appendChild(child);
});
}
if (textContent) {
element.textContent = textContent;
}
return element;
}
......@@ -248,49 +280,41 @@ function generateUpdateForm(referenceImageURL, newImageData) {
/**
* verify that the rendering of the element `locator` matches the previously saved reference.
*
* Arguments:
* locator - an element locator
* misMatchTolerance - the percentage of mismatch allowed. If this is 0, the
* Note that this is implemented as do* method and not a assert* method because only do* methods are asynchronous.
*
* @param {string} locator - an element locator
* @param {string} misMatchTolerance - the percentage of mismatch allowed. If this is 0, the
* images must be exactly same. If more than 0, image will also be resized.
* @returns {() => boolean}
*/
Selenium.prototype.doVerifyImageMatchSnapshot = (
locator,
misMatchTolerance
) => {
// XXX this is a do* method and not a assert* method because only do* methods are
// asynchronous.
// The asynchronicity of do* method is as follow Selenium.prototype.doXXX
// returns a function and this function will be called again and again until:
// * function returns true, which means step is successfull
// * function returns false, which means step is not finished and function will be called again
// * an execption is raised, in that case the step is failed
// * global timeout is reached.
// we implement the state management with similar approach as what's discussed
// https://stackoverflow.com/questions/30564053/how-can-i-synchronously-determine-a-javascript-promises-state
var promiseState, rejectionValue, canvasPromise;
return function assertCanvasImage() {
if (promiseState === 'pending') {
return false;
}
if (promiseState === 'resolved') {
return true;
}
if (promiseState === 'rejected') {
Assert.fail(rejectionValue);
}
var misMatchToleranceFloat = parseFloat(misMatchTolerance);
if (isNaN(misMatchToleranceFloat)) {
misMatchToleranceFloat = 0;
}
misMatchTolerance = parseFloat(misMatchTolerance);
if (isNaN(misMatchTolerance)) {
misMatchTolerance = 0;
}
promiseState = 'pending';
element = selenium.browserbot.findElement(locator);
if (element.nodeName == 'CANVAS' /* instanceof HTMLCanvasElement XXX ? */) {
canvasPromise = Promise.resolve(element);
} else {
canvasPromise = html2canvas(element);
}
/** @type {Promise<HTMLCanvasElement>} */
var canvasPromise;
/** @type {HTMLElement} */
var element = selenium.browserbot.findElement(locator);
if (element.nodeName == 'CANVAS' /* instanceof HTMLCanvasElement XXX ? */) {
canvasPromise = Promise.resolve(element);
} else {
// create a canvas in the same document, so that if this document has loaded
// extra fonts they are also available.
// As suggested on https://github.com/niklasvh/html2canvas/issues/1772
var destinationCanvas = element.ownerDocument.createElement("canvas");
destinationCanvas.width = element.scrollWidth;
destinationCanvas.height = element.scrollHeight;
canvasPromise = html2canvas(element, { canvas: destinationCanvas });
}
return wrapPromise(
canvasPromise
.then(canvas => {
return canvas.toDataURL();
......@@ -310,12 +334,12 @@ Selenium.prototype.doVerifyImageMatchSnapshot = (
blob => {
return new Promise((resolve, reject) => {
var fr = new FileReader();
fr.onload = d => resolve(fr.result);
fr.onload = () => resolve(fr.result);
fr.onerror = reject;
fr.readAsDataURL(blob);
});
},
e => {
() => {
// fetching reference was not found, return empty image instead, it will be different
// (unless the tolerance is too high)
return document.createElement('canvas').toDataURL();
......@@ -328,16 +352,14 @@ Selenium.prototype.doVerifyImageMatchSnapshot = (
useCrossOrigin: false
})
.compareTo(expected);
if (misMatchTolerance > 0) {
if (misMatchToleranceFloat > 0) {
comparator = comparator.scaleToSameSize();
}
comparator.onComplete(resolve);
});
})
.then(diff => {
if (diff.rawMisMatchPercentage <= misMatchTolerance) {
promiseState = 'resolved';
} else {
if (diff.rawMisMatchPercentage > misMatchToleranceFloat) {
return generateUpdateForm(referenceImageURL, actual).then(
updateReferenceImageForm => {
htmlTestRunner.currentTest.currentRow.trElement
......@@ -364,19 +386,11 @@ Selenium.prototype.doVerifyImageMatchSnapshot = (
)
])
);
promiseState = 'rejected';
rejectionValue =
'Images are ' + diff.misMatchPercentage + '% different';
throw new Error('Images are ' + diff.misMatchPercentage + '% different');
}
);
}
});
})
.catch(error => {
console.error(error);
promiseState = 'rejected';
rejectionValue = 'Error computing image differences ' + error;
});
};
}));
};
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment