diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/web/static/tests/helpers/qunit_config.js | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/web/static/tests/helpers/qunit_config.js')
| -rw-r--r-- | addons/web/static/tests/helpers/qunit_config.js | 249 |
1 files changed, 249 insertions, 0 deletions
diff --git a/addons/web/static/tests/helpers/qunit_config.js b/addons/web/static/tests/helpers/qunit_config.js new file mode 100644 index 00000000..575c8022 --- /dev/null +++ b/addons/web/static/tests/helpers/qunit_config.js @@ -0,0 +1,249 @@ +(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; + } + }); + } +}); + +})(); |
