import { expect, test } from "@odoo/hoot"; import { click, queryAllTexts, waitFor } from "@odoo/hoot-dom"; import { Deferred, animationFrame, runAllTimers } from "@odoo/hoot-mock"; import { MockServer, contains, createKanbanRecord, defineActions, defineModels, editKanbanRecord, editKanbanRecordQuickCreateInput, fields, getService, models, mountWithCleanup, onRpc, patchWithCleanup, stepAllNetworkCalls, switchView, toggleMenuItem, toggleSearchBarMenu, webModels, } from "@web/../tests/web_test_helpers"; import { browser } from "@web/core/browser/browser"; import { router } from "@web/core/browser/router"; import { redirect } from "@web/core/utils/urls"; import { useSetupAction } from "@web/search/action_hook"; import { listView } from "@web/views/list/list_view"; import { FormViewDialog } from "@web/views/view_dialogs/form_view_dialog"; import { clearUncommittedChanges } from "@web/webclient/actions/action_service"; import { WebClient } from "@web/webclient/webclient"; import { clickSave, editFavoriteName, editSearch, getPagerLimit, getPagerValue, makeMockEnv, makeServerError, pagerNext, removeFacet, saveFavorite, serverState, toggleSaveFavorite, validateSearch, } from "../../web_test_helpers"; const { ResCompany, ResPartner, ResUsers } = webModels; function clickListNew() { return contains(".o_control_panel_main_buttons .o_list_button_add").click(); } class Partner extends models.Model { _rec_name = "display_name"; display_name = fields.Char(); foo = fields.Char(); m2o = fields.Many2one({ relation: "partner" }); o2m = fields.One2many({ relation: "partner" }); _records = [ { id: 1, display_name: "First record", foo: "yop", m2o: 3, o2m: [2, 3] }, { id: 2, display_name: "Second record", foo: "blip", m2o: 3, o2m: [1, 4, 5] }, { id: 3, display_name: "Third record", foo: "gnap", m2o: 1, o2m: [] }, { id: 4, display_name: "Fourth record", foo: "plop", m2o: 1, o2m: [] }, { id: 5, display_name: "Fifth record", foo: "zoup", m2o: 1, o2m: [] }, ]; _views = { "form,false": `
`, "form,74": `
`, "kanban,1": ` `, "list,false": ``, "pivot,false": ``, "search,false": ``, "search,4": ` `, }; } class Pony extends models.Model { name = fields.Char(); _records = [ { id: 4, name: "Twilight Sparkle" }, { id: 6, name: "Applejack" }, { id: 9, name: "Fluttershy" }, ]; _views = { "list,false": ``, "form,false": `
`, "search,false": ``, }; } defineModels([Partner, Pony, ResCompany, ResPartner, ResUsers]); defineActions([ { id: 1, xml_id: "action_1", name: "Partners Action 1", res_model: "partner", type: "ir.actions.act_window", views: [[1, "kanban"]], }, { id: 2, xml_id: "action_2", name: "Partner", res_id: 2, res_model: "partner", type: "ir.actions.act_window", views: [[74, "form"]], }, { id: 3, xml_id: "action_3", name: "Partners", res_model: "partner", mobile_view_mode: "kanban", type: "ir.actions.act_window", views: [ [false, "list"], [1, "kanban"], [false, "form"], ], }, { id: 4, xml_id: "action_4", name: "Partners Action 4", res_model: "partner", type: "ir.actions.act_window", views: [ [1, "kanban"], [false, "list"], [false, "form"], ], }, { id: 5, xml_id: "action_5", name: "Create a Partner", res_model: "partner", target: "new", type: "ir.actions.act_window", views: [[false, "form"]], }, { id: 8, xml_id: "action_8", name: "Favorite Ponies", res_model: "pony", type: "ir.actions.act_window", views: [ [false, "list"], [false, "form"], ], }, { id: 9, xml_id: "action_9", name: "Ponies", res_model: "pony", type: "ir.actions.act_window", views: [[false, "list"]], }, ]); test("can execute act_window actions from db ID", async () => { stepAllNetworkCalls(); await mountWithCleanup(WebClient); await getService("action").doAction(1); expect(".o_control_panel").toHaveCount(1, { message: "should have rendered a control panel" }); expect(".o_kanban_view").toHaveCount(1, { message: "should have rendered a kanban view" }); expect.verifySteps([ "/web/webclient/translations", "/web/webclient/load_menus", "/web/action/load", "get_views", "web_search_read", ]); }); test("click on a list row when there is no form in the action", async () => { stepAllNetworkCalls(); await mountWithCleanup(WebClient); await getService("action").doAction(9); expect.verifySteps([ "/web/webclient/translations", "/web/webclient/load_menus", "/web/action/load", "get_views", "web_search_read", "has_group", ]); await contains(".o_data_row:eq(0) .o_data_cell").click(); expect.verifySteps([]); }); test("click on open form view button when there is no form in the action", async () => { Pony._views[ "list,false" ] = ``; stepAllNetworkCalls(); await mountWithCleanup(WebClient); await getService("action").doAction(9); expect.verifySteps([ "/web/webclient/translations", "/web/webclient/load_menus", "/web/action/load", "get_views", "web_search_read", "has_group", ]); await contains(".o_data_row:eq(0) .o_list_record_open_form_view").click(); expect(".o_form_view").toHaveCount(1, { message: "should display the form view" }); expect.verifySteps(["get_views", "web_read"]); }); test("click on new record button in list when there is no form in the action", async () => { stepAllNetworkCalls(); await mountWithCleanup(WebClient); await getService("action").doAction(9); expect.verifySteps([ "/web/webclient/translations", "/web/webclient/load_menus", "/web/action/load", "get_views", "web_search_read", "has_group", ]); await contains(".o_list_button_add").click(); expect(".o_form_view").toHaveCount(1, { message: "should display the form view" }); expect.verifySteps(["get_views", "onchange"]); }); test.tags("desktop"); test("sidebar is present in list view", async () => { expect.assertions(4); Partner._toolbar = { print: [{ name: "Print that record" }], }; onRpc("get_views", ({ kwargs }) => { expect(kwargs.options.toolbar).toBe(true, { message: "should ask for toolbar information", }); }); await mountWithCleanup(WebClient); await getService("action").doAction(3); expect(".o_cp_action_menus .o_dropdown_title").toHaveCount(0); // no action menu await contains("input.form-check-input").click(); expect('.o_cp_action_menus button.dropdown-toggle:contains("Print")').toBeVisible(); expect('.o_cp_action_menus button.dropdown-toggle:contains("Action")').toBeVisible(); }); test.tags("desktop"); test("can switch between views", async () => { stepAllNetworkCalls(); await mountWithCleanup(WebClient); await getService("action").doAction(3); expect(".o_list_view").toHaveCount(1, { message: "should display the list view" }); // switch to kanban view await switchView("kanban"); expect(".o_list_view").toHaveCount(0, { message: "should no longer display the list view" }); expect(".o_kanban_view").toHaveCount(1, { message: "should display the kanban view" }); // switch back to list view await switchView("list"); expect(".o_list_view").toHaveCount(1, { message: "should display the list view" }); expect(".o_kanban_view").toHaveCount(0, { message: "should no longer display the kanban view", }); // open a record in form view await contains(".o_list_view .o_data_cell").click(); expect(".o_list_view").toHaveCount(0, { message: "should no longer display the list view" }); expect(".o_form_view").toHaveCount(1, { message: "should display the form view" }); expect(".o_field_widget[name=foo] input").toHaveValue("yop", { message: "should have opened the correct record", }); // go back to list view using the breadcrumbs await contains(".o_control_panel .breadcrumb a").click(); expect(".o_list_view").toHaveCount(1, { message: "should display the list view" }); expect(".o_form_view").toHaveCount(0, { message: "should no longer display the form view" }); expect.verifySteps([ "/web/webclient/translations", "/web/webclient/load_menus", "/web/action/load", "get_views", "web_search_read", "has_group", "web_search_read", "web_search_read", "web_read", "web_search_read", ]); }); test.tags("desktop"); test("switching into a view with mode=edit lands in edit mode", async () => { Partner._views["kanban,1"] = ` `; defineActions([ { id: 1, xml_id: "action_1", name: "Partners Action 1 patched", res_model: "partner", type: "ir.actions.act_window", views: [ [false, "kanban"], [false, "form"], ], }, ]); stepAllNetworkCalls(); await mountWithCleanup(WebClient); await getService("action").doAction(1); expect(".o_kanban_view").toHaveCount(1, { message: "should display the kanban view" }); // quick create record and click Edit await createKanbanRecord(); await editKanbanRecordQuickCreateInput("display_name", "New name"); await editKanbanRecord(); expect(".o_form_view .o_form_editable").toHaveCount(1, { message: "should display the form view in edit mode", }); expect.verifySteps([ "/web/webclient/translations", "/web/webclient/load_menus", "/web/action/load", "get_views", "web_read_group", "web_search_read", "web_search_read", "onchange", "name_create", "web_read", "web_read", ]); }); test.tags("desktop"); test("orderedBy in context is not propagated when executing another action", async () => { expect.assertions(6); Partner._views["form,false"] = `
`; Partner._filters = [ { id: 1, context: "{}", domain: "[]", sort: "[]", is_default: true, name: "My filter", }, ]; let searchReadCount = 1; onRpc("web_search_read", ({ model, sort, kwargs }) => { if (searchReadCount === 1) { expect(model).toBe("partner"); expect(sort).toBe(undefined); } if (searchReadCount === 2) { expect(model).toBe("partner"); expect(kwargs.order).toBe("foo ASC"); } if (searchReadCount === 3) { expect(model).toBe("pony"); expect(sort).toBe(undefined); } searchReadCount += 1; }); await mountWithCleanup(WebClient); await getService("action").doAction(3); // Sort records await contains(".o_list_view th.o_column_sortable").click(); // Get to the form view of the model, on the first record await contains(".o_data_cell").click(); // Execute another action by clicking on the button within the form await contains('button[name="8"]').click(); }); test.tags("desktop"); test("breadcrumbs are updated when switching between views", async () => { await mountWithCleanup(WebClient); await getService("action").doAction(3); expect(".o_control_panel .breadcrumb-item").toHaveCount(0); expect(".o_control_panel .o_breadcrumb .active").toHaveText("Partners"); // switch to kanban view await switchView("kanban"); expect(".o_control_panel .breadcrumb-item").toHaveCount(0); expect(".o_control_panel .o_breadcrumb .active").toHaveText("Partners"); // open a record in form view await contains(".o_kanban_view .o_kanban_record").click(); expect(queryAllTexts(".breadcrumb-item, .o_breadcrumb .active")).toEqual([ "Partners", "First record", ]); // go back to kanban view using the breadcrumbs await contains(".o_control_panel .breadcrumb a").click(); expect(queryAllTexts(".breadcrumb-item, .o_breadcrumb .active")).toEqual(["Partners"]); // switch back to list view await switchView("list"); expect(queryAllTexts(".breadcrumb-item, .o_breadcrumb .active")).toEqual(["Partners"]); // open a record in form view await contains(".o_list_view .o_data_cell").click(); expect(queryAllTexts(".breadcrumb-item, .o_breadcrumb .active")).toEqual([ "Partners", "First record", ]); // go back to list view using the breadcrumbs await contains(".o_control_panel .breadcrumb a").click(); expect(".o_list_view").toHaveCount(1, { message: "should be back on list view" }); expect(queryAllTexts(".breadcrumb-item, .o_breadcrumb .active")).toEqual(["Partners"]); }); test.tags("desktop"); test("switch buttons are updated when switching between views", async () => { await mountWithCleanup(WebClient); await getService("action").doAction(3); expect(".o_control_panel button.o_switch_view").toHaveCount(2, { message: "should have two switch buttons (list and kanban)", }); expect(".o_control_panel button.o_switch_view.active").toHaveCount(1, { message: "should have only one active button", }); expect(".o_control_panel .o_switch_view:first").toHaveClass("o_list", { message: "list switch button should be the first one", }); expect(".o_control_panel .o_switch_view.o_list").toHaveClass("active", { message: "list should be the active view", }); // switch to kanban view await switchView("kanban"); expect(".o_control_panel .o_switch_view").toHaveCount(2, { message: "should still have two switch buttons (list and kanban)", }); expect(".o_control_panel .o_switch_view.active").toHaveCount(1, { message: "should still have only one active button", }); expect(".o_control_panel .o_switch_view:first").toHaveClass("o_list", { message: "list switch button should still be the first one", }); expect(".o_control_panel .o_switch_view.o_kanban").toHaveClass("active", { message: "kanban should now be the active view", }); // switch back to list view await switchView("list"); expect(".o_control_panel .o_switch_view").toHaveCount(2, { message: "should still have two switch buttons (list and kanban)", }); expect(".o_control_panel .o_switch_view.o_list").toHaveClass("active", { message: "list should now be the active view", }); // open a record in form view await contains(".o_list_view .o_data_cell").click(); expect(".o_control_panel .o_switch_view").toHaveCount(0, { message: "should not have any switch buttons", }); // go back to list view using the breadcrumbs await contains(".o_control_panel .breadcrumb a").click(); expect(".o_control_panel .o_switch_view").toHaveCount(2, { message: "should have two switch buttons (list and kanban)", }); expect(".o_control_panel .o_switch_view.o_list").toHaveClass("active", { message: "list should be the active view", }); }); test.tags("desktop"); test("pager is updated when switching between views", async () => { Partner._views["list,false"] = ``; await mountWithCleanup(WebClient); await getService("action").doAction(4); expect(".o_control_panel .o_pager_value").toHaveText("1-5", { message: "value should be correct for kanban", }); expect(".o_control_panel .o_pager_limit").toHaveText("5", { message: "limit should be correct for kanban", }); // switch to list view await switchView("list"); expect(".o_control_panel .o_pager_value").toHaveText("1-3", { message: "value should be correct for list", }); expect(".o_control_panel .o_pager_limit").toHaveText("5", { message: "limit should be correct for list", }); // open a record in form view await contains(".o_list_view .o_data_cell").click(); expect(".o_control_panel .o_pager_value").toHaveText("1", { message: "value should be correct for form", }); expect(".o_control_panel .o_pager_limit").toHaveText("3", { message: "limit should be correct for form", }); // go back to list view using the breadcrumbs await contains(".o_control_panel .breadcrumb a").click(); expect(".o_control_panel .o_pager_value").toHaveText("1-3", { message: "value should be correct for list", }); expect(".o_control_panel .o_pager_limit").toHaveText("5", { message: "limit should be correct for list", }); // switch back to kanban view await switchView("kanban"); expect(".o_control_panel .o_pager_value").toHaveText("1-5", { message: "value should be correct for kanban", }); expect(".o_control_panel .o_pager_limit").toHaveText("5", { message: "limit should be correct for kanban", }); }); test.tags("desktop"); test("Props are updated and kept when switching/restoring views", async () => { Partner._views["form,false"] = /* xml */ `
`; onRpc("get_formview_action", ({ args, model }) => { return { res_id: args[0][0], res_model: model, type: "ir.actions.act_window", views: [[false, "form"]], }; }); await mountWithCleanup(WebClient); await getService("action").doAction(3); // 5 records initially expect(".o_data_row").toHaveCount(5); await contains(".o_data_row:first-of-type .o_data_cell").click(); // Open 1 / 5 expect(".o_field_char input").toHaveValue("First record"); expect(getPagerValue()).toEqual([1]); expect(getPagerLimit()).toBe(5); await contains(".o_field_many2one .o_external_button", { visible: false }).click(); // Click on M2O -> 1 / 1 expect(".o_field_char input").toHaveValue("Third record"); expect(getPagerValue()).toEqual([1]); expect(getPagerLimit()).toBe(1); await contains(".o_back_button").click(); // Back to 1 / 5 expect(".o_field_char input").toHaveValue("First record"); expect(getPagerValue()).toEqual([1]); expect(getPagerLimit()).toBe(5); await pagerNext(); // Next page -> 2 / 5 expect(".o_field_char input").toHaveValue("Second record"); expect(getPagerValue()).toEqual([2]); expect(getPagerLimit()).toBe(5); await contains(".o_field_many2one .o_external_button", { visible: false }).click(); // Click on M2O -> still 1 / 1 expect(".o_field_char input").toHaveValue("Third record"); expect(getPagerValue()).toEqual([1]); expect(getPagerLimit()).toBe(1); await contains(".o_back_button").click(); // Back to 2 / 5 expect(".o_field_char input").toHaveValue("Second record"); expect(getPagerValue()).toEqual([2]); expect(getPagerLimit()).toBe(5); }); test.tags("desktop"); test("domain is kept when switching between views", async () => { defineActions([ { id: 3, name: "Partners", res_model: "partner", search_view_id: [4, "a custom search view"], type: "ir.actions.act_window", views: [ [false, "list"], [1, "kanban"], [false, "form"], ], }, ]); await mountWithCleanup(WebClient); await getService("action").doAction(3); expect(".o_data_row").toHaveCount(5); // activate a domain await toggleSearchBarMenu(); await toggleMenuItem("M2O"); expect(".o_data_row").toHaveCount(3); // switch to kanban await switchView("kanban"); expect(".o_kanban_record:not(.o_kanban_ghost)").toHaveCount(3); // remove the domain await contains(".o_searchview .o_facet_remove").click(); expect(".o_kanban_record:not(.o_kanban_ghost)").toHaveCount(5); // switch back to list await switchView("list"); expect(".o_data_row").toHaveCount(5); }); test.tags("desktop"); test("A new form view can be reloaded after a failed one", async () => { expect.errors(1); await mountWithCleanup(WebClient); await getService("action").doAction(3); expect(".o_list_view").toHaveCount(1, { message: "The list view should be displayed" }); await runAllTimers(); // wait for the update of the router expect(router.current).toEqual({ action: 3, actionStack: [ { action: 3, displayName: "Partners", view_type: "list", }, ], }); // Click on the first record await contains(".o_list_view .o_data_row .o_data_cell").click(); expect(".o_form_view").toHaveCount(1, { message: "The form view should be displayed" }); expect(".o_last_breadcrumb_item").toHaveText("First record"); await runAllTimers(); // wait for the update of the router expect(browser.location.pathname).toBe("/odoo/action-3/1"); // Delete the current record await contains(".o_cp_action_menus .fa-cog").click(); await contains(".o_menu_item:contains(Delete)").click(); expect(".modal").toHaveCount(1, { message: "a confirm modal should be displayed" }); await contains(".modal-footer button.btn-primary").click(); // The form view is automatically switched to the next record expect(".o_last_breadcrumb_item").toHaveText("Second record"); await runAllTimers(); // wait for the update of the router expect(browser.location.pathname).toBe("/odoo/action-3/2"); // Go back to the previous (now deleted) record browser.history.back(); await runAllTimers(); expect(browser.location.pathname).toBe("/odoo/action-3/1"); // As the previous one is deleted, we go back to the list await runAllTimers(); // wait for the update of the router expect(".o_list_view").toHaveCount(1, { message: "should still display the list view" }); // Click on the first record await contains(".o_list_view .o_data_row .o_data_cell").click(); expect(".o_form_view").toHaveCount(1, { message: "The form view should still load after a previous failed update | reload", }); expect(".o_last_breadcrumb_item").toHaveText("Second record"); expect.verifyErrors([ "It seems the records with IDs 1 cannot be found. They might have been deleted.", ]); }); test.tags("desktop"); test("there is no flickering when switching between views", async () => { let def; onRpc(() => def); await mountWithCleanup(WebClient); await getService("action").doAction(3); // switch to kanban view def = new Deferred(); await switchView("kanban"); expect(".o_list_view").toHaveCount(1, { message: "should still display the list view" }); expect(".o_kanban_view").toHaveCount(0, { message: "shouldn't display the kanban view yet" }); def.resolve(); await animationFrame(); expect(".o_list_view").toHaveCount(0, { message: "shouldn't display the list view anymore" }); expect(".o_kanban_view").toHaveCount(1, { message: "should now display the kanban view" }); // switch back to list view def = new Deferred(); await switchView("list"); expect(".o_kanban_view").toHaveCount(1, { message: "should still display the kanban view" }); expect(".o_list_view").toHaveCount(0, { message: "shouldn't display the list view yet" }); def.resolve(); await animationFrame(); expect(".o_kanban_view").toHaveCount(0, { message: "shouldn't display the kanban view anymore", }); expect(".o_list_view").toHaveCount(1, { message: "should now display the list view" }); // open a record in form view def = new Deferred(); await contains(".o_list_view .o_data_cell").click(); expect(".o_list_view").toHaveCount(1, { message: "should still display the list view" }); expect(".o_form_view").toHaveCount(0, { message: "shouldn't display the form view yet" }); expect(queryAllTexts(".breadcrumb-item, .o_breadcrumb .active")).toEqual(["Partners"]); def.resolve(); await animationFrame(); expect(".o_list_view").toHaveCount(0, { message: "should no longer display the list view" }); expect(".o_form_view").toHaveCount(1, { message: "should display the form view" }); expect(queryAllTexts(".breadcrumb-item, .o_breadcrumb .active")).toEqual([ "Partners", "First record", ]); // go back to list view using the breadcrumbs def = new Deferred(); await contains(".o_control_panel .breadcrumb a").click(); expect(".o_form_view").toHaveCount(1, { message: "should still display the form view" }); expect(".o_list_view").toHaveCount(0, { message: "shouldn't display the list view yet" }); expect(queryAllTexts(".breadcrumb-item, .o_breadcrumb .active")).toEqual([ "Partners", "First record", ]); def.resolve(); await animationFrame(); expect(".o_form_view").toHaveCount(0, { message: "should no longer display the form view" }); expect(".o_list_view").toHaveCount(1, { message: "should display the list view" }); expect(queryAllTexts(".breadcrumb-item, .o_breadcrumb .active")).toEqual(["Partners"]); }); test.tags("desktop"); test("breadcrumbs are updated when display_name changes", async () => { await mountWithCleanup(WebClient); await getService("action").doAction(3); // open a record in form view await contains(".o_list_view .o_data_cell").click(); expect(queryAllTexts(".breadcrumb-item, .o_breadcrumb .active")).toEqual([ "Partners", "First record", ]); // change the display_name await contains(".o_field_widget[name=display_name] input").edit("New name"); await clickSave(); expect(queryAllTexts(".breadcrumb-item, .o_breadcrumb .active")).toEqual([ "Partners", "New name", ]); }); test.tags("desktop"); test('reverse breadcrumb works on accesskey "b"', async () => { await mountWithCleanup(WebClient); await getService("action").doAction(3); // open a record in form view await contains(".o_list_view .o_data_cell").click(); await contains(".o_form_view button:contains(Execute action)").click(); expect(queryAllTexts(".breadcrumb-item, .o_breadcrumb .active")).toEqual([ "Partners", "First record", "Partners Action 4", ]); expect(".breadcrumb-item.o_back_button").toHaveAttribute("data-hotkey", "b", { message: "previous breadcrumb should have accessKey 'b'", }); await contains(".breadcrumb-item.o_back_button").click(); expect(queryAllTexts(".breadcrumb-item, .o_breadcrumb .active")).toEqual([ "Partners", "First record", ]); expect(".breadcrumb-item.o_back_button").toHaveAttribute("data-hotkey", "b", { message: "previous breadcrumb should have accessKey 'b'", }); }); test.tags("desktop"); test("reload previous controller when discarding a new record", async () => { stepAllNetworkCalls(); await mountWithCleanup(WebClient); await getService("action").doAction(3); // create a new record await clickListNew(); expect(".o_form_view .o_form_editable").toHaveCount(1, { message: "should have opened the form view in edit mode", }); // discard await contains(".o_control_panel .o_form_button_cancel").click(); expect(".o_list_view").toHaveCount(1, { message: "should have switched back to the list view", }); expect.verifySteps([ "/web/webclient/translations", "/web/webclient/load_menus", "/web/action/load", "get_views", "web_search_read", "has_group", "onchange", "web_search_read", ]); }); test.tags("desktop"); test("execute_action of type object are handled", async () => { expect.assertions(4); serverState.userContext = { some_key: 2 }; onRpc("partner", "object", async function ({ args, kwargs }) { expect(kwargs).toEqual( { context: { lang: "en", uid: 7, tz: "taht", allowed_company_ids: [1], some_key: 2, }, }, { message: "should call route with correct arguments" } ); return this.env["partner"].write(args[0], { foo: "value changed" }); }); stepAllNetworkCalls(); await mountWithCleanup(WebClient); await getService("action").doAction(3); // open a record in form view await contains(".o_list_view .o_data_cell").click(); expect(".o_field_widget[name=foo] input").toHaveValue("yop", { message: "check initial value of 'yop' field", }); // click on 'Call method' button (should call an Object method) await contains(".o_form_view button:contains(Call method)").click(); expect(".o_field_widget[name=foo] input").toHaveValue("value changed", { message: "'yop' has been changed by the server, and should be updated in the UI", }); expect.verifySteps([ "/web/webclient/translations", "/web/webclient/load_menus", "/web/action/load", "get_views", "web_search_read", "has_group", "web_read", "object", "web_read", ]); }); test.tags("desktop"); test("execute_action of type object: disable buttons (2)", async () => { Pony._views["form,44"] = `