diff --git a/content/developer/reference/backend/testing.rst b/content/developer/reference/backend/testing.rst
index 5c61e88e3..ba1dd2065 100644
--- a/content/developer/reference/backend/testing.rst
+++ b/content/developer/reference/backend/testing.rst
@@ -1,4 +1,3 @@
-
.. _reference/testing:
@@ -639,28 +638,76 @@ Here are some example of steps:
Here are some possible arguments for your personalized steps:
-- **trigger**: selector/element/jQuery you want to trigger
-- **extra-trigger**: optional selector/element/jQuery that needs to be present before the next
- step begins. This is especially useful when the tour needs to wait for a wizard to open, a
- line added to a list view...
-- **run**: optional action to run, defaults either to `click` or `text Test` if you are triggering
- an input. A multitude of actions are possible. Here are some of them: `click`, `dbclick`,
- `tripleclick`, `text Example`, `drag_and_drop selector1 selector2`...
-- **edition**: optional,
+- **trigger**: Selector/element to ``run`` an action on. The tour will
+ wait until the element exists and is visible before ``run``-ing the
+ action *on it*.
+- **extra_trigger**: Optional secondary condition for the step to
+ ``run``. Will be waited for like the **trigger** element but the
+ action will not run on the extra trigger.
+
+ Useful to have a precondition, or two different and unrelated
+ conditions.
+- **run**: Action to perform on the *trigger* element.
+
+ By default, tries to set the **trigger**'s content to ``Text`` if
+ it's an ``input``, otherwise ``click`` it.
+
+ The action can also be:
+
+ - A function, synchronous, executed with the trigger's ``Tip`` as
+ context (``this``) and the action helpers as parameter.
+ - The name of one of the action helpers, which will be run on the
+ trigger element:
+
+ .. rst-class:: o-definition-list
+
+ ``click``
+ Clicks the element, performing all the relevant intermediate
+ events.
+ :samp:`text {content}`
+ Clicks (focuses) the element then sets ``content`` as the
+ element's value (if an input), option (if a select), or
+ content.
+ ``dblclick``, ``tripleclick``
+ Same as ``click`` with multiple repetitions.
+ ``clicknoleave``
+ By default, ``click`` (and variants) will trigger "exit"
+ events on the trigger element (mouseout, mouseleave). This
+ helper suppresses those (note: further clicks on other
+ elements will not trigger those events implicitly).
+ ``text_blur``
+ Similar to ``text`` but follows the edition with ``focusout``
+ and ``blur`` events.
+ :samp:`drag_and_drop {target}`
+ Simulates the dragging of the **trigger** element over to the
+ ``target``.
+- **edition**: Optional,
- If you don't specify an edition, the step will be active in both community and enterprise.
- Sometimes, a step will be different in enterprise or in community. You can then write two
steps, one for the enterprise edition and one for the community one.
- Generally, you want to specify an edition for steps that use the main menu as the main
menus are different in community and enterprise.
-- **position**: optional
-- **id**: optional
-- **auto**: optional
-- **in_modal**: optional
+- **position**: Optional, ``"top"``, ``"right"``, ``"bottom"``, or
+ ``"left"``. Where to position the tooltip relative to the **target**
+ when running interactive tours.
+- **content**: Optional but recommended, the content of the tooltip in
+ interactive tours, also logged to the console so very useful to
+ trace and debug automated tours.
+- **auto**: Whether the tour manager should wait for the user to
+ perform the action if the tour is interactive, defaults to
+ ``false``.
+- **in_modal**: If set the **trigger** element will be searched only
+ in the top modal window, defaults to ``false``.
+- **timeout**: How long to wait until the step can ``run``, in
+ milliseconds, 10000 (10 seconds).
-.. tip::
- Your browser's developer tools are your best tool to find the element your tour needs to use as a
- trigger/extra-trigger.
+.. important::
+
+ The last step(s) of a tour should always return the client to a
+ "stable" state (e.g. no ongoing editions) and ensure all
+ side-effects (network requests) have finished running to avoid race
+ conditions or errors during teardown.
.. seealso::
- `jQuery documentation about find `_
@@ -681,31 +728,75 @@ To start a tour from a python test, make the class inherit from
Debugging tips
--------------
-Running the tour in debug mode
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Observing tours in a browser
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-First, activate the :doc:`developer mode ` with
-`?debug=tests`. Then, open your debug menu and click on **Start Tour**. You can now launch your tour
-from there with the button `Test`.
+There are two ways with different tradeoffs:
-You can also add this step in your tour to stop it right where you want it to:
+``watch=True``
+''''''''''''''
+When running a tour locally via the test suite, the ``watch=True``
+parameter can be added to the ``browser_js`` or ``start_tour``
+call::
+
+ self.start_tour("/web", code, watch=True)
+
+This will automatically open a Chrome window with the tour being
+run inside it.
+
+**Advantages**
+ - always works if the tour has Python setup / surrounding code, or multiple steps
+ - runs entirely automatically (just select the test which launches the tour)
+ - transactional (*should* always be runnable multiple times)
+**Drawbacks**
+ - only works locally
+ - only works if the test / tour can run correctly locally
+
+Run via browser
+'''''''''''''''
+Tours can also be launched via the browser UI, either by calling
.. code-block:: javascript
- {
- trigger: "body",
- run: () => {debugger}
- }
+ odoo.startTour(tour_name);
-.. caution::
- Be aware that when running the tour, any data added to the setup of your python test won't be
- present in the tour unless you launched the test calling the tour with a breakpoint.
+in the javascript console, or by enabling :ref:`tests mode
+` by setting ``?debug=tests`` in
+the URL, then selecting **Start Tour** in the debug menu and picking a
+tour:
+.. image:: testing/tours.png
+ :align: center
+
+**Advantages**
+ - easier to run
+ - can be used on production or test sites, not just local instances
+ - allows running in "Onboarding" mode (manual steps)
+**Drawbacks**
+ - harder to use with test tours involving Python setup
+ - may not work multiple times depending on tour side-effects
+
+.. tip::
+
+ It's possible to use this method to observe or interact with tours
+ which require Python setup:
+
+ - add a *python* breakpoint before the relevant tour is started
+ (``start_tour`` or ``browser_js`` call)
+ - when the breakpoint is hit, open the instance in your browser
+ - run the tour
+
+ At this point the Python setup will be visible to the browser, and
+ the tour will be able to run.
+
+ You may want to comment the ``start_tour`` or ``browser_js`` call
+ if you also want the test to continue afterwards, depending on the
+ tour's side-effects.
Screenshots and screencasts during browser_js tests
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-When running tests that use HttpCase.browser_js from the command line, the Chrome
+When running tests that use ``HttpCase.browser_js`` from the command line, the Chrome
browser is used in headless mode. By default, if a test fails, a PNG screenshot is
taken at the moment of the failure and written in
@@ -716,17 +807,47 @@ taken at the moment of the failure and written in
Two new command line arguments were added since Odoo 13.0 to control this behavior:
:option:`--screenshots ` and :option:`--screencasts `
+Introspecting / debugging steps
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Watch `browser_js` tests in Chrome DevTools
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+When trying to fix / debug a tour, the screenshots (on failure) are
+not necessarily sufficient. In that case it can be useful to see
+what's happening at some or each step.
-Set `watch` to `True` when starting a tour to automatically open a new Chrome tab and watch the execution of the tour in Chrome DevTools.
+While this is pretty easy when in an "onboarding" (as they're mostly
+driven explicitly by the user) it's more complicated when running
+"test" tours, or when running tours through the test suite. In that
+case there are two main tricks:
-.. example::
- .. code-block:: python
+- Have a step with a ``run() { debugger; }`` action.
- self.browser_js("/web", code, watch=True)
+ This can be added to an existing step, or can be a new dedicated
+ step. Once the step's **trigger** is matched, the execution will
+ stop all javascript execution.
+ **Advantages**
+ - very simple
+ - the tour restarts as soon as you resume execution
+ **Drawbacks**
+ - page interaction is limited as all javascript is blocked
+ - debugging the inside of the tour manager is not very useful
+- Add a step with a trigger which never succeeds and a very long
+ ``timeout``.
+
+ The browser will wait for the **trigger** until the ``timeout``
+ before it fails the tour, this allows inspecting and interacting
+ with the page until the developer is ready to resume, by manually
+ enabling the **trigger** (a nonsense class is useful there, as it
+ can be triggered by adding the class to any visible element of the
+ page).
+
+ **Advantages**
+ - allows interacting with the page
+ - easy to apply to a step which times out (just add a long
+ ``timeout`` then look around)
+ - no useless (for this situation) debugger UI
+ **Drawbacks**
+ - more manual, especially when resuming
Performance Testing
===================
diff --git a/content/developer/reference/backend/testing/tours.png b/content/developer/reference/backend/testing/tours.png
new file mode 100644
index 000000000..d032b8570
Binary files /dev/null and b/content/developer/reference/backend/testing/tours.png differ