Files
odoo_source/addons/web/static/tests/helpers/qunit_config.js
T
Pierre Paridans 996c2eadf1 [FIX] web: qunit: always log name of failing qunit-fixture content test
Follow-up of odoo/odoo@598d036b59

closes odoo/odoo#64171

X-original-commit: b4ceaa06b2c3ca4b9a1e95dc815d18867c49fd80
Signed-off-by: Adrien Dieudonné (adr) <adr@odoo.com>
2021-01-06 16:58:56 +00:00

250 lines
8.5 KiB
JavaScript

(function() {
"use strict";
/**
* QUnit Config
*
* The Odoo javascript test framework is based on QUnit (http://qunitjs.com/).
* This file is necessary to setup Qunit and to prepare its interactions with
* Odoo. It has to be loaded before any tests are defined.
*
* Note that it is not an Odoo module, because we want this code to be executed
* as soon as possible, not whenever the Odoo module system feels like it.
*/
/**
* This configuration variable is not strictly necessary, but it ensures more
* safety for asynchronous tests. With it, each test has to explicitely tell
* QUnit how many assertion it expects, otherwise the test will fail.
*/
QUnit.config.requireExpects = true;
/**
* not important in normal mode, but in debug=assets, the files are loaded
* asynchroneously, which can lead to various issues with QUnit... Notice that
* this is done outside of odoo modules, otherwise the setting would not take
* effect on time.
*/
QUnit.config.autostart = false;
/**
* A test timeout of 1 min, before an async test is considered failed.
*/
QUnit.config.testTimeout = 1 * 60 * 1000;
/**
* Hide passed tests by default in the QUnit page
*/
QUnit.config.hidepassed = (window.location.href.match(/[?&]testId=/) === null);
var sortButtonAppended = false;
/**
* If we want to log several errors, we have to log all of them at once, as
* browser_js is closed as soon as an error is logged.
*/
const errorMessages = [];
/**
* List of elements tolerated in the body after a test. The property "keep"
* prevents the element from being removed (typically: qunit suite elements).
*/
const validElements = [
// always in the body:
{ tagName: 'DIV', attr: 'id', value: 'qunit', keep: true },
{ tagName: 'DIV', attr: 'id', value: 'qunit-fixture', keep: true },
// shouldn't be in the body after a test but are tolerated:
{ tagName: 'SCRIPT', attr: 'id', value: '' },
{ tagName: 'DIV', attr: 'className', value: 'o_notification_manager' },
{ tagName: 'DIV', attr: 'className', value: 'tooltip fade bs-tooltip-auto' },
{ tagName: 'DIV', attr: 'className', value: 'tooltip fade bs-tooltip-auto show' },
{ tagName: 'SPAN', attr: 'className', value: 'select2-hidden-accessible' },
// Due to a Document Kanban bug (already present in 12.0)
{ tagName: 'DIV', attr: 'className', value: 'ui-helper-hidden-accessible' },
{ tagName: 'UL', attr: 'className', value: 'ui-menu ui-widget ui-widget-content ui-autocomplete ui-front' },
];
/**
* Waits for the module system to end processing the JS modules, so that we can
* make the suite fail if some modules couldn't be loaded (e.g. because of a
* missing dependency).
*
* @returns {Promise<boolean>}
*/
async function checkModules() {
// do not mark the suite as successful already, as we still need to ensure
// that all modules have been correctly loaded
$('#qunit-banner').removeClass('qunit-pass');
const $modulesAlert = $('<div>')
.addClass('alert alert-info')
.text('Waiting for modules check...');
$modulesAlert.appendTo('#qunit');
// wait for the module system to end processing the JS modules
await odoo.__DEBUG__.didLogInfo;
const info = odoo.__DEBUG__.jsModules;
if (info.missing.length || info.failed.length) {
$('#qunit-banner').addClass('qunit-fail');
$modulesAlert.toggleClass('alert-info alert-danger');
const failingModules = info.missing.concat(info.failed);
const error = `Some modules couldn't be started: ${failingModules.join(', ')}.`;
$modulesAlert.text(error);
errorMessages.unshift(error);
return false;
} else {
$modulesAlert.toggleClass('alert-info alert-success');
$modulesAlert.text('All modules have been correctly loaded.');
$('#qunit-banner').addClass('qunit-pass');
return true;
}
}
/**
* This is the way the testing framework knows that tests passed or failed. It
* only look in the phantomJS console and check if there is a ok or an error.
*
* Someday, we should devise a safer strategy...
*/
QUnit.done(async function (result) {
const allModulesLoaded = await checkModules();
if (result.failed) {
errorMessages.push(`${result.failed} / ${result.total} tests failed.`);
}
if (!result.failed && allModulesLoaded) {
console.log('test successful');
} else {
console.error(errorMessages.join('\n'));
}
if (!sortButtonAppended) {
_addSortButton();
}
});
/**
* This logs various data in the console, which will be available in the log
* .txt file generated by the runbot.
*/
QUnit.log(function (result) {
if (!result.result) {
var info = '"QUnit test failed: "' + result.module + ' > ' + result.name + '"';
info += ' [message: "' + result.message + '"';
if (result.actual !== null) {
info += ', actual: "' + result.actual + '"';
}
if (result.expected !== null) {
info += ', expected: "' + result.expected + '"';
}
info += ']';
errorMessages.push(info);
}
});
/**
* This is done mostly for the .txt log file generated by the runbot.
*/
QUnit.moduleDone(function(result) {
if (!result.failed) {
console.log('"' + result.name + '"', "passed", result.total, "tests.");
} else {
console.log('"' + result.name + '"',
"failed", result.failed,
"tests out of", result.total, ".");
}
});
/**
* After each test, we check that there is no leftover in the DOM.
*
* Note: this event is not QUnit standard, we added it for this specific use case.
* As a payload, an object with keys 'moduleName' and 'testName' is provided. It
* is used to indicate the test that left elements in the DOM, when it happens.
*/
QUnit.on('OdooAfterTestHook', function (info) {
const toRemove = [];
// check for leftover elements in the body
for (const bodyChild of document.body.children) {
const tolerated = validElements.find((e) =>
e.tagName === bodyChild.tagName && bodyChild[e.attr] === e.value
);
if (!tolerated) {
console.error(`Test ${info.moduleName} > ${info.testName}`);
console.error('Body still contains undesirable elements:' +
'\nInvalid element:\n' + bodyChild.outerHTML);
QUnit.pushFailure(`Body still contains undesirable elements`);
}
if (!tolerated || !tolerated.keep) {
toRemove.push(bodyChild);
}
}
// check for leftovers in #qunit-fixture
const qunitFixture = document.getElementById('qunit-fixture');
if (qunitFixture.children.length) {
console.error(`Test ${info.moduleName} > ${info.testName}`);
console.error('#qunit-fixture still contains elements:' +
'\n#qunit-fixture HTML:\n' + qunitFixture.outerHTML);
QUnit.pushFailure(`#qunit-fixture still contains elements`);
toRemove.push(...qunitFixture.children);
}
// remove unwanted elements if not in debug
if (!document.body.classList.contains('debug')) {
for (const el of toRemove) {
el.remove();
}
}
});
/**
* Add a sort button on top of the QUnit result page, so we can see which tests
* take the most time.
*/
function _addSortButton() {
sortButtonAppended = true;
var $sort = $('<label> sort by time (desc)</label>').css({float: 'right'});
$('h2#qunit-userAgent').append($sort);
$sort.click(function() {
var $ol = $('ol#qunit-tests');
var $results = $ol.children('li').get();
$results.sort(function (a, b) {
var timeA = Number($(a).find('span.runtime').first().text().split(" ")[0]);
var timeB = Number($(b).find('span.runtime').first().text().split(" ")[0]);
if (timeA < timeB) {
return 1;
} else if (timeA > timeB) {
return -1;
} else {
return 0;
}
});
$.each($results, function(idx, $itm) { $ol.append($itm); });
});
}
/**
* We add here a 'fail fast' feature: we often want to stop the test suite after
* the first failed test. This is also useful for the runbot test suites.
*/
QUnit.config.urlConfig.push({
id: "failfast",
label: "Fail Fast",
tooltip: "Stop the test suite immediately after the first failed test."
});
QUnit.begin(function() {
if (QUnit.config.failfast) {
QUnit.testDone(function(details) {
if (details.failed > 0) {
QUnit.config.queue.length = 0;
}
});
}
});
})();