diff options
| author | Christophe Monniez <moc@odoo.com> | 2018-07-04 16:32:05 +0200 |
|---|---|---|
| committer | Christophe Monniez <moc@odoo.com> | 2018-08-29 16:57:27 +0200 |
| commit | 3f0ebca1924afb538c6f7960a42aba725654c643 (patch) | |
| tree | 1bcdb20706658bdde4487c38abad1d4b5204c5bc | |
| parent | 19cc218d03c9e2eb32ce354db96668b4c5280854 (diff) | |
[IMP] web: New test to test Odoo menus
This commit adds a new js test in web that enters each app and click on
each view switcher, each menu and each search filter.
This test is tagged '-standard' and 'click_all' as it could last a long
time, that way it's not executed by default.
It could also be started from the browser console with
'clickEverywhere();'.
| -rw-r--r-- | addons/web/static/src/js/tools/test_menus.js | 278 | ||||
| -rw-r--r-- | addons/web/static/src/js/tools/test_menus_loader.js | 12 | ||||
| -rw-r--r-- | addons/web/static/src/js/widgets/debug_manager.js | 4 | ||||
| -rw-r--r-- | addons/web/static/src/xml/base.xml | 1 | ||||
| -rw-r--r-- | addons/web/tests/__init__.py | 1 | ||||
| -rw-r--r-- | addons/web/tests/test_click_everywhere.py | 17 | ||||
| -rw-r--r-- | addons/web/views/webclient_templates.xml | 1 |
7 files changed, 314 insertions, 0 deletions
diff --git a/addons/web/static/src/js/tools/test_menus.js b/addons/web/static/src/js/tools/test_menus.js new file mode 100644 index 00000000000..6a8c9d2118b --- /dev/null +++ b/addons/web/static/src/js/tools/test_menus.js @@ -0,0 +1,278 @@ +(function (exports) { + /** + * The purpose of this test is to click on every installed App and then + * open each view. On each view, click on each filter. + */ + "use strict"; + var clientActionCount = 0; + var viewUpdateCount = 0; + var testedApps; + var testedMenus; + + function createWebClientHooks() { + var AbstractController = odoo.__DEBUG__.services['web.AbstractController']; + var Discuss = odoo.__DEBUG__.services['mail.Discuss']; + var WebClient = odoo.__DEBUG__.services["web.WebClient"]; + + WebClient.include({ + current_action_updated : function (action, controller) { + this._super(action, controller); + clientActionCount++; + }, + }); + + AbstractController.include({ + start: function(){ + this.$el.attr('data-view-type', this.viewType); + return this._super(); + }, + update: function(params, options) { + return this._super(params, options).then(function (){ + viewUpdateCount++; + }); + }, + }); + + if (Discuss) { + Discuss.include({ + _fetchAndRenderThread: function() { + return this._super().then(function (){ + viewUpdateCount++; + }); + }, + }); + } + } + + // Main function that starts orchestration of tests + function clickEverywhere(){ + console.log("Starting ClickEverywhere test"); + var startTime = performance.now() + createWebClientHooks(); + testedApps = []; + testedMenus = []; + var isEnterprise = odoo.session_info.server_version_info[5] === 'e'; + // finding applications menus + var $listOfAppMenuItems; + if (isEnterprise) { + console.log("Odoo flavor: Enterprise") + var $homeMenu = $("nav.o_main_navbar > a.o_menu_toggle.fa-th"); + $homeMenu.click() + $listOfAppMenuItems = $(".o_app, .o_menuitem") + } else { + console.log("Odoo flavor: Community") + $listOfAppMenuItems = $('#oe_main_menu_navbar a.oe_menu_leaf, #oe_main_menu_navbar a.oe_menu_toggler'); + } + console.log('Found ', $listOfAppMenuItems.length, 'apps to test'); + + var testDef = $.when(); + testDef = chainDeferred($listOfAppMenuItems, testDef, testApp); + return testDef.then(function() { + console.log("Successfully tested ", testedApps.length, " apps"); + console.log("Successfully tested ", testedMenus.length - testedApps.length, " menus"); + console.log("ok"); + }).always(function() { + console.log("Test took ", (performance.now() - startTime)/1000, " seconds"); + }).fail(function () { + console.error("Error !") + }); + } + + + /** + * Test an "App" menu item by orchestrating the following actions: + * 1 - clicking on its menuItem + * 2 - clicking on each view + * 3 - clicking on each menu + * 3.1 - clicking on each view + * @param {DomElement} element: the App menu item + * @returns {Deferred} + */ + function testApp(element){ + console.log("Testing app menu:", element.dataset.menuXmlid); + if (testedApps.indexOf(element.dataset.menuXmlid) >= 0) return $.Deferred().resolve(); // Another infinite loop protection + testedApps.push(element.dataset.menuXmlid); + return testMenuItem(element).then(function () { + var $subMenuItems; + var isEnterprise = odoo.session_info.server_version_info[5] === 'e'; + if (isEnterprise) { + $subMenuItems = $('.o_menu_entry_lvl_1, .o_menu_entry_lvl_2, .o_menu_entry_lvl_3, .o_menu_entry_lvl_4'); + } else { + $subMenuItems = $('div.o_sub_menu_content .oe_menu_leaf'); + } + var testMenuDef = $.when(); + testMenuDef = chainDeferred($subMenuItems, testMenuDef, testMenuItem); + return testMenuDef; + }).then(function(){ + // no effect in community + var $homeMenu = $("nav.o_main_navbar > a.o_menu_toggle.fa-th"); + $homeMenu.click(); + }); + } + + + /** + * Test a menu item by: + * 1 - clikcing on the menuItem + * 2 - Orchestrate the view switch + * + * @param {DomElement} element: the menu item + * @returns {Deferred} + */ + function testMenuItem(element){ + if (testedMenus.indexOf(element.dataset.menuXmlid) >= 0) return $.Deferred().resolve(); // Avoid infinite loop + console.log("Testing menu", element.innerText.trim(), " ", element.dataset.menuXmlid); + testedMenus.push(element.dataset.menuXmlid); + var startActionCount = clientActionCount; + element.click(); + var isModal = false; + return waitForCondition(function() { + // sometimes, the app is just a modal that needs to be closed + var $modal = $('.modal[role="dialog"][open="open"]'); + if ($modal.length > 0) { + $modal.modal('hide'); + isModal = true; + return true; + }; + return startActionCount != clientActionCount; + }).then(function() { + if (!isModal) { + return testFilters(); + } + }).then(function () { + if (!isModal) { + return testViews(); + } + }).fail(function() { + console.error("Error while testing", element); + }); + }; + + + /** + * Orchestrate the test of views + * This function finds the buttons that permit to switch views and orchestrate + * the click on each of them + */ + function testViews() { + var $switches = $("nav.o_cp_switch_buttons > button:not(.active):visible"); + var testSwitchDef = $.when(); + // chainDeferred($switches, testSwitchDef, testViewSwitch # FIXME + _.each($switches, function(switchButton) { + testSwitchDef = testSwitchDef.then(function () { + // get the view view-type data attribute + return testViewSwitch(switchButton.dataset.viewType); + }); + }); + return testSwitchDef; + } + + /** + * Test a view button + * @param {string} viewType: a string for the view type to test (list, kanban ...) + * @returns {Deferred} a deferred that wait for the view to be loaded + */ + function testViewSwitch(viewType){ + console.log("Testing view switch: ", viewType); + // timeout to avoid click debounce + setTimeout(function() { + var $element = $("nav.o_cp_switch_buttons > button[data-view-type=" + viewType + "]"); + console.log('Clicking on: ', $element[0].dataset.viewType, ' view switcher'); + $element.click(); + },250); + var waitViewSwitch = waitForCondition(function(){ + return $('.o_content > .o_view_controller').data('view-type') === viewType; + }); + return waitViewSwitch.then(function() { + return testFilters(); + }); + } + + /** + * Test filters + * Click on each filter in the control pannel + */ + function testFilters() { + var filterDef = $.when(); + // var $filters = $('div.o_control_panel div.btn-group.o_dropdown > ul.o_filters_menu > li:not(.o_add_custom_filter)'); + var $filters = $('.o_filters_menu > .o_menu_item') + console.log("Testing " + $filters.length + " filters"); + var filter_ids = _.compact(_.map($filters, function(f) { return f.dataset.id})); + _.each(filter_ids, function(filter_id){ + filterDef = filterDef.then(function(){ + var currentViewCount = viewUpdateCount; + var $filter = $('.o_menu_item[data-id="' + filter_id + '"] a'); + // with some customized search views, the filter cannot be found + if ($filter[0] === undefined) { + console.warn('Filter with ID ', filter_id , 'cannot be found'); + return $.Deferred().resolve(); + }; + console.log('Clicking on filter "', $filter.text().trim(), '"'); + $filter[0].click(); + setTimeout(function() { + var $filterOption = $('.o_menu_item .o_item_option[data-item_id="' + filter_id + '"]:not(.selected) a'); + // In case the filter is a date filter, we need to click on the first filter option (like 'today','This week' ...) + if ($filterOption.length > 0) { + console.log('Clicking on filter option "', $filterOption[0], '"'); + $filterOption[0].click(); + console.log('And now on filter again'); + $filter = $('.o_menu_item[data-id="' + filter_id + '"] a'); + $filter[0].click(); // To avoid that the next view fold the options + } + }, 250); + return waitForCondition(function() { + return currentViewCount !== viewUpdateCount; + }); + }); + }); + return filterDef; + } + + // utility functions + /** + * Wait a certain amount of time for a condition to occur + * @param stopCondition: a function that returns a boolean + * @returns {Deferred} that is rejected if the timeout is exceeded + */ + function waitForCondition(stopCondition) { + var def = $.Deferred(); + var interval = 250; + var timeLimit = 15000; + function checkCondition() { + if (stopCondition()) { + def.resolve(); + } else { + timeLimit -= interval; + if (timeLimit > 0) { + // recursive call until the resolve or the timeout + setTimeout(checkCondition, interval); + } else { + console.error("Timeout exceeded", stopCondition); + def.reject(); + } + } + } + setTimeout(checkCondition, interval); + return def; + }; + + + /** + * chain deferred actions + * @param $elements: a list of jquery elements to be passed as arg to the function + * @param deferred: the deferred on which other deferreds will be chained + * @param f: the function to be deferred + * @returns : the chained deferred + */ + function chainDeferred($elements, deferred, f) { + _.each($elements, function($el) { + deferred = deferred.then(function () { + return f($el); + }); + }); + return deferred; + } + + + exports.clickEverywhere = clickEverywhere; +})(window); diff --git a/addons/web/static/src/js/tools/test_menus_loader.js b/addons/web/static/src/js/tools/test_menus_loader.js new file mode 100644 index 00000000000..b126d3bb4d8 --- /dev/null +++ b/addons/web/static/src/js/tools/test_menus_loader.js @@ -0,0 +1,12 @@ +odoo.define('web.clickEverywhere', function (require) { + "use strict"; + var ajax = require('web.ajax'); + function startClickEverywhere() { + ajax.loadJS('web/static/src/js/tools/test_menus.js').then( + function() { + clickEverywhere(); + } + ); + } + return startClickEverywhere; +}); diff --git a/addons/web/static/src/js/widgets/debug_manager.js b/addons/web/static/src/js/widgets/debug_manager.js index b96c38ab998..5b944724c11 100644 --- a/addons/web/static/src/js/widgets/debug_manager.js +++ b/addons/web/static/src/js/widgets/debug_manager.js @@ -3,6 +3,7 @@ odoo.define('web.DebugManager', function (require) { var ActionManager = require('web.ActionManager'); var dialogs = require('web.view_dialogs'); +var startClickEverywhere = require('web.clickEverywhere'); var config = require('web.config'); var core = require('web.core'); var Dialog = require('web.Dialog'); @@ -211,6 +212,9 @@ var DebugManager = Widget.extend({ url: '/web/tests/mobile?mod=*' }); }, + perform_click_everywhere_test: function () { + startClickEverywhere(); + }, split_assets: function() { window.location = $.param.querystring(window.location.href, 'debug=assets'); }, diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index e1c74db4645..e1ba3c29509 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -237,6 +237,7 @@ <t t-name="WebClient.DebugManager.Global"> <a role="menuitem" href="#" data-action="perform_js_tests" class="dropdown-item">Run JS Tests</a> <a role="menuitem" href="#" data-action="perform_js_mobile_tests" class="dropdown-item">Run JS Mobile Tests</a> + <a role="menuitem" href="#" data-action="perform_click_everywhere_test" class="dropdown-item">Run Click Everywhere Test</a> <a role="menuitem" href="#" data-action="select_view" class="dropdown-item">Open View</a> <t t-if="manager._events"> <div class="dropdown-divider" role="separator"/> diff --git a/addons/web/tests/__init__.py b/addons/web/tests/__init__.py index 582c61fcaef..ffa03b739d9 100644 --- a/addons/web/tests/__init__.py +++ b/addons/web/tests/__init__.py @@ -4,3 +4,4 @@ from . import test_js from . import test_menu from . import test_serving_base +from . import test_click_everywhere diff --git a/addons/web/tests/test_click_everywhere.py b/addons/web/tests/test_click_everywhere.py new file mode 100644 index 00000000000..1e79104c30f --- /dev/null +++ b/addons/web/tests/test_click_everywhere.py @@ -0,0 +1,17 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import odoo.tests + + +@odoo.tests.tagged('click_all', 'post_install', '-at_install', '-standard') +class TestMenusAdmin(odoo.tests.HttpCase): + + def test_01_click_everywhere_as_admin(self): + self.browser_js("/web", "odoo.__DEBUG__.services['web.clickEverywhere']();", "'web.clickEverywhere' in odoo.__DEBUG__.services", login="admin", timeout=1800) + + +@odoo.tests.tagged('click_all', 'post_install', '-at_install', '-standard') +class TestMenusDemo(odoo.tests.HttpCase): + + def test_01_click_everywhere_as_demo(self): + self.browser_js("/web", "odoo.__DEBUG__.services['web.clickEverywhere']();", "'web.clickEverywhere' in odoo.__DEBUG__.services", login="demo", timeout=1800) diff --git a/addons/web/views/webclient_templates.xml b/addons/web/views/webclient_templates.xml index 49f056a2339..c8b1b9399c1 100644 --- a/addons/web/views/webclient_templates.xml +++ b/addons/web/views/webclient_templates.xml @@ -220,6 +220,7 @@ <script type="text/javascript" src="/web/static/src/js/widgets/auto_complete.js"></script> <script type="text/javascript" src="/web/static/src/js/widgets/change_password.js"></script> <script type="text/javascript" src="/web/static/src/js/widgets/debug_manager.js"></script> + <script type="text/javascript" src="/web/static/src/js/tools/test_menus_loader.js"/> <script type="text/javascript" src="/web/static/src/js/widgets/data_export.js"></script> <script type="text/javascript" src="/web/static/src/js/widgets/date_picker.js"></script> <script type="text/javascript" src="/web/static/src/js/widgets/domain_selector_dialog.js"></script> |
