[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();'.
This commit is contained in:
@@ -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);
|
||||
@@ -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;
|
||||
});
|
||||
@@ -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');
|
||||
},
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -4,3 +4,4 @@
|
||||
from . import test_js
|
||||
from . import test_menu
|
||||
from . import test_serving_base
|
||||
from . import test_click_everywhere
|
||||
|
||||
@@ -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)
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user