[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:
Christophe Monniez
2018-08-29 16:57:27 +02:00
parent 19cc218d03
commit 3f0ebca192
7 changed files with 314 additions and 0 deletions
@@ -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');
},
+1
View File
@@ -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"/>
+1
View File
@@ -4,3 +4,4 @@
from . import test_js
from . import test_menu
from . import test_serving_base
from . import test_click_everywhere
+17
View File
@@ -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)
+1
View File
@@ -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>