Тестування Odoo¶
Є багато способів перевірити додаток. В Odoo ми маємо три види тестів
Модульні тести Python (див. Тестування коду Python): корисні для тестування бізнес-логіки моделі
Модульні тести JS (див. Тестування коду JS): корисні для ізольованого тестування коду javascript
Тури (див. Інтеграційне тестування): тури імітують реальну ситуацію. Вони гарантують, що частини python і javascript належним чином спілкуються одна з одною.
Тестування коду Python¶
Odoo provides support for testing modules using Python’s unittest library.
Щоб написати тести, просто визначте підпакет tests
у своєму модулі, він автоматично перевірятиметься на тестові модулі. Тестові модулі повинні мати назву, яка починатиметься з test_
, і їх слід імпортувати з tests/__init__.py
, напр.
your_module
├── ...
├── tests
| ├── __init__.py
| ├── test_bar.py
| └── test_foo.py
і __init__.py
містить:
from . import test_foo, test_bar
Попередження
тестові модулі, які не імпортовані з tests/__init__.py
, не запускатимуться
Програма тестування просто запустить будь-який тестовий приклад, як описано в офіційній документації unittest, але Odoo надає ряд утиліт і помічників, пов’язаних з тестуванням вмісту Odoo (головним чином модулів):
- class odoo.tests.TransactionCase(methodName='runTest')[source]¶
Test class in which all test methods are run in a single transaction, but each test method is run in a sub-transaction managed by a savepoint. The transaction’s cursor is always closed without committing.
The data setup common to all methods should be done in the class method
setUpClass
, so that it is done once for all test methods. This is useful for test cases containing fast tests but with significant database setup common to all cases (complex in-db test data).After being run, each test method cleans up the record cache and the registry cache. However, there is no cleanup of the registry models and fields. If a test modifies the registry (custom models and/or fields), it should prepare the necessary cleanup (
self.registry.reset_changes()
).- browse_ref(xid)[source]¶
Повертає об’єкт запису для наданого зовнішнього ідентифікатора
- Параметри
xid – повний зовнішній ідентифікатор у формі
module.identifier
- Raise
ValueError, якщо не знайдено
- Повертає
- ref(xid)[source]¶
Returns database ID for the provided external identifier, shortcut for
_xmlid_lookup
- Параметри
xid – повний зовнішній ідентифікатор у формі
module.identifier
- Raise
ValueError, якщо не знайдено
- Повертає
зареєстрований id
- class odoo.tests.SingleTransactionCase(methodName='runTest')[source]¶
TestCase, у якому всі тестові методи виконуються в одній транзакції, транзакція починається з першого тестового методу та відкочується в кінці останнього.
- browse_ref(xid)[source]¶
Повертає об’єкт запису для наданого зовнішнього ідентифікатора
- Параметри
xid – повний зовнішній ідентифікатор у формі
module.identifier
- Raise
ValueError, якщо не знайдено
- Повертає
- ref(xid)[source]¶
Returns database ID for the provided external identifier, shortcut for
_xmlid_lookup
- Параметри
xid – повний зовнішній ідентифікатор у формі
module.identifier
- Raise
ValueError, якщо не знайдено
- Повертає
зареєстрований id
- class odoo.tests.HttpCase(methodName='runTest')[source]¶
Транзакційний HTTP TestCase з url_open і безголовими помічниками Chrome.
- browser_js(url_path, code, ready='', login=None, timeout=60, cookies=None, error_checker=None, watch=False, cpu_throttling=None, **kw)[source]¶
Test JavaScript code running in the browser.
To signal success test do:
console.log('test successful')
To signal test failure raise an exception or callconsole.error
with a message. Test will stop when a failure occurs iferror_checker
is not defined or returnsTrue
for this message- Параметри
url_path (string) – URL path to load the browser page on
code (string) – JavaScript code to be executed
ready (string) – JavaScript object to wait for before proceeding with the test
login (string) – logged in user which will execute the test. e.g. „admin“, „demo“
timeout (int) – maximum time to wait for the test to complete (in seconds). Default is 60 seconds
cookies (dict) – dictionary of cookies to set before loading the page
error_checker – function to filter failures out. If provided, the function is called with the error log message, and if it returns
False
the log is ignored and the test continue If not provided, every error log triggers a failurewatch (bool) – open a new browser window to watch the test execution
cpu_throttling (int) – CPU throttling rate as a slowdown factor (1 is no throttle, 2 is 2x slowdown, etc)
- browse_ref(xid)[source]¶
Повертає об’єкт запису для наданого зовнішнього ідентифікатора
- Параметри
xid – повний зовнішній ідентифікатор у формі
module.identifier
- Raise
ValueError, якщо не знайдено
- Повертає
- ref(xid)[source]¶
Returns database ID for the provided external identifier, shortcut for
_xmlid_lookup
- Параметри
xid – повний зовнішній ідентифікатор у формі
module.identifier
- Raise
ValueError, якщо не знайдено
- Повертає
зареєстрований id
- odoo.tests.tagged(*tags)[source]¶
A decorator to tag BaseCase objects.
Tags are stored in a set that can be accessed from a „test_tags“ attribute.
A tag prefixed by „-“ will remove the tag e.g. to remove the „standard“ tag.
By default, all Test classes from odoo.tests.common have a test_tags attribute that defaults to „standard“ and „at_install“.
When using class inheritance, the tags ARE inherited.
За замовчуванням тести запускаються один раз відразу після встановлення відповідного модуля. Тестові приклади також можна налаштувати для запуску після встановлення всіх модулів, а не для запуску одразу після встановлення модуля:
# coding: utf-8
from odoo.tests import HttpCase, tagged
# This test should only be executed after all modules have been installed.
@tagged('-at_install', 'post_install')
class WebsiteVisitorTests(HttpCase):
def test_create_visitor_on_tracked_page(self):
Page = self.env['website.page']
The most common situation is to use
TransactionCase
and test a property of a model
in each method:
class TestModelA(TransactionCase):
def test_some_action(self):
record = self.env['model.a'].create({'field': 'value'})
record.some_action()
self.assertEqual(
record.field,
expected_field_value)
# other tests...
Примітка
Методи тестування мають починатися з test_
- class odoo.tests.Form(record, view=None)[source]¶
Реалізація представлення форми на стороні сервера (частково)
Реалізує більшу частину потоку маніпуляцій «представлення форми», щоб тести на стороні сервера могли більш правильно відображати поведінку, яка спостерігалася б під час маніпулювання інтерфейсом:
call the relevant onchanges on «creation»;
call the relevant onchanges on setting fields;
properly handle defaults & onchanges around x2many fields.
Saving the form returns the current record (which means the created record if in creation mode). It can also be accessed as
form.record
, but only when the form has no pending changes.Regular fields can just be assigned directly to the form. In the case of
Many2one
fields, one can assign a recordset:# empty recordset => creation mode f = Form(self.env['sale.order']) f.partner_id = a_partner so = f.save()
One can also use the form as a context manager to create or edit a record. The changes are automatically saved at the end of the scope:
with Form(self.env['sale.order']) as f1: f1.partner_id = a_partner # f1 is saved here # retrieve the created record so = f1.record # call Form on record => edition mode with Form(so) as f2: f2.payment_term_id = env.ref('account.account_payment_term_15days') # f2 is saved here
Для полів
Many2many
саме поле єM2MProxy
і може бути змінено додаванням або видаленням записів:with Form(user) as u: u.groups_id.add(env.ref('account.group_account_manager')) u.groups_id.remove(id=env.ref('base.group_portal').id)
Finally
One2many
are reified asO2MProxy
.Because the
One2many
only exists through its parent, it is manipulated more directly by creating «sub-forms» with thenew()
andedit()
methods. These would normally be used as context managers since they get saved in the parent record:with Form(so) as f3: f.partner_id = a_partner # add support with f3.order_line.new() as line: line.product_id = env.ref('product.product_product_2') # add a computer with f3.order_line.new() as line: line.product_id = env.ref('product.product_product_3') # we actually want 5 computers with f3.order_line.edit(1) as line: line.product_uom_qty = 5 # remove support f3.order_line.remove(index=0) # SO is saved here
- Параметри
record (odoo.models.Model) – empty or singleton recordset. An empty recordset will put the view in «creation» mode from default values, while a singleton will put it in «edit» mode and only load the view’s data.
view (int | str | odoo.model.Model) – id, xmlid або фактичний об’єкт перегляду для використання для змін і обмежень представлення. Якщо жодного не надано, просто завантажується типове представлення для моделі.
Нове в версії 12.0.
- save()[source]¶
Save the form (if necessary) and return the current record:
does not save
readonly
fields;does not save unmodified fields (during edition) — any assignment or onchange return marks the field as modified, even if set to its current value.
When nothing must be saved, it simply returns the current record.
- Викликає
AssertionError – якщо у формі є незаповнені обов’язкові поля
- property record¶
Return the record being edited by the form. This attribute is readonly and can only be accessed when the form has no pending changes.
- class odoo.tests.M2MProxy(form, field_name)[source]¶
Proxy object for editing the value of a many2many field.
Поводиться як
Sequence
наборів записів, які можна індексувати або нарізати, щоб отримати фактичні основні набори записів.- add(record)[source]¶
Додає
record
до поля, запис уже має існувати.Додавання буде завершено лише після збереження батьківського запису.
- class odoo.tests.O2MProxy(form, field_name)[source]¶
Proxy object for editing the value of a one2many field.
- new()[source]¶
Повертає
Форму
для нового записуOne2many
, правильно ініціалізованого.Форма створюється з представлення списку, якщо його можна редагувати, або з представлення форми поля в іншому випадку.
- Викликає
AssertionError – якщо поле не редагується
- edit(index)[source]¶
Повертає
Форму
для редагування вже існуючого записуOne2many
.Форма створюється з представлення списку, якщо його можна редагувати, або з представлення форми поля в іншому випадку.
- Викликає
AssertionError – якщо поле не редагується
- remove(index)[source]¶
Видаляє запис в
index
з батьківської форми.- Викликає
AssertionError – якщо поле не редагується
Запуск тестів¶
Тести запускаються автоматично під час встановлення або оновлення модулів, якщо --test-enable
було ввімкнено під час запуску сервера Odoo.
Вибір тесту¶
В Odoo тести Python можна позначати тегами, щоб полегшити вибір тестів під час виконання тестів.
Subclasses of odoo.tests.BaseCase
(usually through
TransactionCase
or
HttpCase
) are automatically tagged with
standard
and at_install
by default.
Виклик¶
--test-tags
можна використовувати для вибору/фільтрування тестів для запуску в командному рядку. Це передбачає --test-enable
, тому немає необхідності вказувати --test-enable
, коли використовуючи --test-tags
.
Цей параметр за замовчуванням має значення +standard
, що означає, що тести з тегом standard
(явно чи неявно) виконуватимуться за замовчуванням під час запуску Odoo за допомогою --test-enable
.
When writing tests, the tagged()
decorator can be
used on test classes to add or remove tags.
Аргументами декоратора є назви тегів у вигляді рядків.
Небезпека
tagged()
is a class decorator, it has no
effect on functions or methods
Щоб видалити теги замість того, щоб додавати або вибирати їх, наприклад. якщо ви не хочете, щоб ваш тест виконувався за замовчуванням, ви можете видалити тег standard
:
from odoo.tests import TransactionCase, tagged
@tagged('-standard', 'nice')
class NiceTest(TransactionCase):
...
Цей тест не буде вибрано за замовчуванням, щоб запустити відповідний тег потрібно буде вибрати явно:
$ odoo-bin --test-tags nice
Зауважте, що виконуватимуться лише тести з тегом nice
. Щоб запустити як ``both``, так і ``standard`` тести, надайте кілька значень :option:`–test-tags <odoo-bin –test-tags>`: у командному рядку значення є *additive (ви вибираєте всі тести з any із зазначених тегів)
$ odoo-bin --test-tags nice,standard
Параметр config switch також приймає префікси +
і -
. Префікс +
мається на увазі, тому він абсолютно необов’язковий. Префікс -
(мінус) призначений для скасування вибору тестів, позначених префіксованими тегами, навіть якщо вони вибрані іншими вказаними тегами, наприклад. якщо є standard
тести, які також позначені як slow
, ви можете запустити всі стандартні тести except повільних:
$ odoo-bin --test-tags 'standard,-slow'
When you write a test that does not inherit from the
BaseCase
, this test will not have the default tags,
you have to add them explicitly to have the test included in the default test
suite. This is a common issue when using a simple unittest.TestCase
as
they’re not going to get run:
import unittest
from odoo.tests import tagged
@tagged('standard', 'at_install')
class SmallTest(unittest.TestCase):
...
Окрім тегів, ви також можете вказати конкретні модулі, класи або функції для тестування. Повний синтаксис формату, який приймає --test-tags
є:
[-][tag][/module][:class][.method]
Отже, якщо ви хочете протестувати модуль stock_account
, ви можете використовувати:
$ odoo-bin --test-tags /stock_account
Якщо ви хочете протестувати певну функцію з унікальною назвою, його можна вказати безпосередньо:
$ odoo-bin --test-tags .test_supplier_invoice_forwarded_by_internal_user_without_supplier
Це еквівалентно
$ odoo-bin --test-tags /account:TestAccountIncomingSupplierInvoice.test_supplier_invoice_forwarded_by_internal_user_without_supplier
якщо назва тесту однозначна. Кілька модулів, класів і функцій можна вказати одночасно, розділивши їх символом ,
, як у звичайних тегах.
Приклади¶
Важливо
Тести будуть виконуватися тільки в установлених модулях. Якщо ви починаєте з чистої бази даних, вам потрібно принаймні один раз встановити модулі з перемикачем -i
. Після цього він більше не потрібен, якщо тільки вам не потрібно оновити модуль, у такому випадку можна використовувати -u
. Для простоти ці перемикачі не вказані в наведених нижче прикладах.
Запустіть лише тести з модуля продажу:
$ odoo-bin --test-tags /sale
Запустіть тести з модуля продажу, але не ті, які позначені як повільні:
$ odoo-bin --test-tags '/sale,-slow'
Виконуйте лише ті тести, які є на складі або позначені як повільні:
$ odoo-bin --test-tags '-standard, slow, /stock'
Примітка
-standard
неявний (не обов’язковий) і представлений для ясності
Тестування коду JS¶
Тестування складної системи є важливим запобіжним заходом для запобігання регресії та гарантії, що деякі базові функції все ще працюють. Оскільки Odoo має нетривіальну кодову базу в Javascript, її необхідно перевірити. У цьому розділі ми обговоримо практику тестування JS-коду окремо: ці тести залишаються в браузері й не повинні досягати сервера.
Набір тестів Qunit¶
Фреймворк Odoo використовує фреймворк для тестування бібліотеки QUnit як засіб виконання тестів. QUnit визначає поняття tests і modules (набору пов’язаних тестів) і надає нам веб-інтерфейс для виконання тестів.
Наприклад, ось як може виглядати тест pyUtils:
QUnit.module('py_utils');
QUnit.test('simple arithmetic', function (assert) {
assert.expect(2);
var result = pyUtils.py_eval("1 + 2");
assert.strictEqual(result, 3, "should properly evaluate sum");
result = pyUtils.py_eval("42 % 5");
assert.strictEqual(result, 2, "should properly evaluate modulo operator");
});
Основний спосіб запустити набір тестів - мати запущений сервер Odoo, а потім перейти у веб-браузері до /web/tests
. Потім набір тестів буде виконано механізмом Javascript веб-браузера.

Веб-інтерфейс користувача має багато корисних функцій: він може запускати лише деякі підмодулі або фільтрувати тести, які відповідають рядку. Він може показати всі твердження, невдалі чи пройдені, повторно запустити певні тести, …
Попередження
Під час роботи набору тестів переконайтеся, що:
вікно браузера сфокусовано,
він не збільшений/зменшений. Він повинен мати рівно 100% масштабу.
Якщо це не так, деякі тести будуть невдалими без належного пояснення.
Тестування інфраструктури¶
Ось короткий огляд найважливіших частин інфраструктури тестування:
є набір ресурсів під назвою web.qunit_suite. Цей набір містить основний код (загальні активи + бекенд активів), деякі бібліотеки, засіб виконання тестів QUnit і тестові пакети, перелічені нижче.
пакет під назвою web.tests_assets включає більшість активів і утиліт, необхідних для набору тестів: користувальницькі підтвердження QUnit, помічники тестування, активи з відкладеним завантаженням тощо.
інший комплект активів, web.qunit_suite_tests, містить усі тестові сценарії. Зазвичай це місце, де тестові файли додаються до набору.
у web є controller, зіставлений з маршрутом /web/tests. Цей контролер просто рендерить шаблон web.qunit_suite.
щоб виконати тести, можна просто вказати браузер на маршрут /web/tests. У цьому випадку браузер завантажить усі ресурси, а QUnit візьме на себе роботу.
є певний код у qunit_config.js, який записує в консоль певну інформацію, коли тест проходить чи не проходить.
ми хочемо, щоб runbot також запускав ці тести, тому існує тест (у test_js.py), який просто породжує браузер і спрямовує його на URL-адресу web/tests. Зауважте, що метод browser_js створює безголовий екземпляр Chrome.
Модульність і тестування¶
Завдяки тому, як Odoo розроблено, будь-який аддон може змінювати поведінку інших частин системи. Наприклад, аддон voip може змінити віджет FieldPhone для використання додаткових функцій. Це не дуже добре з точки зору системи тестування, оскільки це означає, що тест у веб-доповненні буде невдалим щоразу, коли встановлено доповнення VoIP (зверніть увагу, що Runbot запускає тести з усіма встановленими додатками).
У той же час наша система тестування хороша, оскільки вона може виявити, коли інший модуль порушує певну основну функціональність. Повного вирішення цієї проблеми немає. Наразі ми вирішуємо це в кожному окремому випадку.
Зазвичай не варто змінювати якусь іншу поведінку. Для нашого прикладу VoIP, безперечно, чистіше додати новий віджет FieldVOIPPhone і змінити декілька представлень, які його потребують. Таким чином, віджет FieldPhone не зазнає впливу, і обидва можна перевірити.
Додавання нового тесту¶
Припустімо, що ми підтримуємо аддон my_addon і хочемо додати тест для деякого коду javascript (наприклад, якусь службову функцію myFunction, розташовану в my_addon.utils). Процес додавання нового тестового прикладу такий:
створити новий файл my_addon/static/tests/utils_tests.js. Цей файл містить базовий код для додавання модуля QUnit my_addon > utils.
odoo.define('my_addon.utils_tests', function (require) { "use strict"; var utils = require('my_addon.utils'); QUnit.module('my_addon', {}, function () { QUnit.module('utils'); }); });
У my_addon/assets.xml додайте файл до основних тестових ресурсів:
<?xml version="1.0" encoding="utf-8"?> <odoo> <template id="qunit_suite_tests" name="my addon tests" inherit_id="web.qunit_suite_tests"> <xpath expr="//script[last()]" position="after"> <script type="text/javascript" src="/my_addon/static/tests/utils_tests.js"/> </xpath> </template> </odoo>
Перезапустіть сервер і оновіть my_addon або зробіть це з інтерфейсу (щоб переконатися, що новий тестовий файл завантажено)
Додайте тестовий приклад після визначення набору підтестів utils:
QUnit.test("some test case that we want to test", function (assert) { assert.expect(1); var result = utils.myFunction(someArgument); assert.strictEqual(result, expectedResult); });
Відвідайте /web/tests/, щоб переконатися, що тест виконано
Допоміжні функції та спеціальні твердження¶
Без сторонньої допомоги досить важко протестувати деякі частини Odoo. Зокрема, представлення є складними, оскільки вони взаємодіють із сервером і можуть виконувати багато rpc, які потрібно імітувати. Ось чому ми розробили кілька спеціалізованих допоміжних функцій, розташованих у test_utils.js.
Функції макетного тестування: ці функції допомагають налаштувати тестове середовище. Найважливішим випадком використання є висміювання відповідей, наданих сервером Odoo. Ці функції використовують макетний сервер. Це клас javascript, який симулює відповіді на найпоширеніші методи моделі: read, search_read, nameget, …
Помічники DOM: корисні для симуляції подій/дій щодо певної цілі. Наприклад, testUtils.dom.click виконує клацання по цілі. Зауважте, що це безпечніше, ніж робити це вручну, оскільки воно також перевіряє, чи ціль існує та чи є видимою.
створити помічники: це, ймовірно, найважливіші функції, експортовані test_utils.js. Ці помічники корисні для створення віджетів із макетом середовища та великою кількістю дрібних деталей, щоб якомога більше імітувати реальні умови. Найважливішим, безумовно, є createView.
qunit assertions: QUnit можна розширити за допомогою спеціальних тверджень. Для Odoo ми часто тестуємо деякі властивості DOM. Ось чому ми зробили деякі твердження, щоб допомогти з цим. Наприклад, твердження containsOnce приймає widget/jQuery/HtmlElement і селектор, а потім перевіряє, чи ціль містить точно один збіг для селектора css.
Наприклад, за допомогою цих помічників ось як може виглядати простий тест форми:
QUnit.test('simple group rendering', function (assert) {
assert.expect(1);
var form = testUtils.createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<group>' +
'<field name="foo"/>' +
'</group>' +
'</form>',
res_id: 1,
});
assert.containsOnce(form, 'table.o_inner_group');
form.destroy();
});
Зверніть увагу на використання помічника testUtils.createView та твердження containsOnce. Крім того, контролер форми було належним чином знищено в кінці тесту.
Кращі практики¶
Без особливого порядку:
усі тестові файли слід додати в some_addon/static/tests/
для виправлень помилок переконайтеся, що тест не вдається без виправлення помилок і проходить разом із ним. Це гарантує, що він дійсно працює.
намагайтеся мати мінімальну кількість коду, необхідного для роботи тесту.
зазвичай два невеликих тести краще, ніж один великий. Менший тест легше зрозуміти та виправити.
завжди прибирання після тесту. Наприклад, якщо ваш тест створює екземпляр віджета, він має знищити його в кінці.
немає необхідності мати завершене і повне покриття коду. Але додавання кількох тестів дуже допомагає: це гарантує, що ваш код не зламано повністю, і щоразу, коли помилку виправлено, набагато простіше додати тест до існуючого набору тестів.
якщо ви хочете перевірити якесь негативне твердження (наприклад, що HtmlElement не має певного класу css), тоді спробуйте додати позитивне твердження в той самий тест (наприклад, виконавши дію, яка змінює стан). Це допоможе уникнути знищення тесту в майбутньому (наприклад, якщо буде змінено клас css).
Поради¶
запуск лише одного тесту: ви можете (тимчасово!) змінити визначення QUnit.test(…) на QUnit.only(…). Це корисно, щоб переконатися, що QUnit виконує лише цей конкретний тест.
прапор налагодження: більшість функцій утиліти create мають режим налагодження (активується параметром debug: true). У цьому випадку цільовий віджет буде розміщено в DOM замість прихованого специфічного фікстура qunit, і більше інформації буде зареєстровано. Наприклад, усі імітовані мережеві комунікації будуть доступні в консолі.
під час роботи над невдалим тестом зазвичай додають позначку налагодження, а потім коментують кінець тесту (зокрема, виклик знищення). Завдяки цьому можна безпосередньо бачити стан віджета, а ще краще – маніпулювати віджетом, натискаючи/взаємодіючи з ним.
Інтеграційне тестування¶
Тестування коду Python і коду JS окремо дуже корисно, але це не доводить, що веб-клієнт і сервер працюють разом. Щоб зробити це, ми можемо написати інший тип тесту: тури. Тур – це міні-сценарій якогось цікавого ділового потоку. Він пояснює послідовність кроків, яких слід виконати. Потім виконавець тесту створить браузер PhantomJs, направить його на відповідну URL-адресу та імітує клацання та введення відповідно до сценарію.
Написання тестового туру¶
Структура¶
Щоб написати тестовий тур для your_module
, почніть зі створення необхідних файлів:
your_module
├── ...
├── static
| └── tests
| └── tours
| └── your_tour.js
├── tests
| ├── __init__.py
| └── test_calling_the_tour.py
└── __manifest__.py
Тоді ви можете:
update
__manifest__.py
to addyour_tour.js
in the assets.'assets': { 'web.assets_tests': [ 'your_module/static/tests/tours/your_tour.js', ], },
оновіть
__init__.py
в папціtests
, щоб імпортуватиtest_calling_the_tour
.
Перегляньте також
Javascript¶
Налаштуйте свій тур, зареєструвавши його.
/** @odoo-module **/ import tour from 'web_tour.tour'; tour.register('rental_product_configurator_tour', { url: '/web', // Here, you can specify any other starting url test: true, }, [ // Your sequence of steps ]);
Додайте будь-який крок, який хочете.
Every step contains at least a trigger. You can either use the predefined steps or write your own personalized step.
Ось кілька прикладів кроків:
Example
// First step
tour.stepUtils.showAppsMenuItem(),
// Second step
{
trigger: '.o_app[data-menu-xmlid="your_module.maybe_your_module_menu_root"]',
edition: 'community' // Optional
}, {
// Third step
},
Example
{
trigger: '.js_product:has(strong:contains(Chair floor protection)) .js_add',
extra_trigger: '.oe_advanced_configurator_modal', // This ensure we are in the wizard
},
Example
{
trigger: 'a:contains("Add a product")',
// Extra-trigger to make sure a line is added before trying to add another one
extra_trigger: '.o_field_many2one[name="product_template_id"] .o_external_button',
},
Ось кілька можливих аргументів для ваших індивідуальних кроків:
trigger: Selector/element to
run
an action on. The tour will wait until the element exists and is visible beforerun
-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 aninput
, otherwiseclick
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:
click
Clicks the element, performing all the relevant intermediate events.
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 withfocusout
andblur
events.drag_and_drop target
Simulates the dragging of the trigger element over to the
target
.
edition: Optional,
Якщо ви не вкажете випуск, крок буде активним і в community, і в enterprise.
Іноді на enterprise чи в community крок буде іншим. Потім ви можете написати два кроки: один для enterprise версії та інший для community.
Як правило, ви хочете вказати випуск для кроків, які використовують головне меню, оскільки головні меню відрізняються в community та на enterprise.
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).
Важливо
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.
Перегляньте також
Python¶
To start a tour from a python test, make the class inherit from
HTTPCase
, and call start_tour
:
def test_your_test(self):
# Optional Setup
self.start_tour("/web", 'your_module.your_tour_name', login="admin")
# Optional verifications
Поради щодо налагодження¶
Observing tours in a browser¶
There are two ways with different tradeoffs:
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
odoo.startTour(tour_name);
in the javascript console, or by enabling tests mode by setting ?debug=tests
in
the URL, then selecting Start Tour in the debug menu and picking a
tour:

- 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
Порада
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
orbrowser_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.
Скріншоти та скрінкасти під час тестування browser_js¶
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
'/tmp/odoo_tests/{db_name}/screenshots/'
Починаючи з Odoo 13.0, було додано два нові аргументи командного рядка для керування цією поведінкою: --screenshots
і --screencasts
Introspecting / debugging steps¶
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.
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:
Have a step with a
run() { debugger; }
action.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
Тестування продуктивності¶
Кількість запитів¶
One of the ways to test performance is to measure database queries. Manually, this can be tested with the
--log-sql
CLI parameter. If you want to establish the maximum number of queries for an operation,
you can use the assertQueryCount()
method, integrated in Odoo test classes.
with self.assertQueryCount(11):
do_something()