Skip to content

fix(spp_cel_widget): make the tours load on Odoo 19 so web.assets_tests stops failing every backend tour (#551) - #552

Merged
gonzalesedwin1123 merged 11 commits into
19.0from
fix-551-cel-widget-tour-import
Sep 29, 2026
Merged

gonzalesedwin1123 merged 11 commits into
19.0from
fix-551-cel-widget-tour-import

Conversation

@gonzalesedwin1123

@gonzalesedwin1123 gonzalesedwin1123 commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

Fixes #551.

Problem

spp_cel_widget/static/tests/tours/cel_widget_tour.js is in web.assets_tests, which the backend page (web.webclient_bootstrap) loads whenever tests are enabled or ?debug=tests is on, without the ignore_missing_deps exemption the frontend layout gets. Two Odoo 17 leftovers stopped the file from loading on 19.0, and each made Odoo's module loader log a console error. The tour runner fails on any console error, so every backend tour failed on a database with spp_cel_widget installed, and it is auto_install.

  1. import {stepUtils} from "@web_tour/tour_service/tour_utils": that module doesn't exist in Odoo 19. It's @web_tour/tour_utils (web_tour/static/src/tour_utils.js). Loader error: "needed by other modules but have not been defined: @web_tour/tour_service/tour_utils".
  2. Not in the issue: with the import fixed, the file still throws on load. Odoo 19 validates web_tour.tours entries against {name, steps, url, wait_for}, and all 10 tours set the pre-18 key test: true: "Validation error for key "cel_widget_basic_rendering" in registry "web_tour.tours": Invalid object: unknown key 'test'". So the one-line fix proposed in the issue is not enough on its own.

We never saw this because the test image has no Chrome (Odoo skips every browser test) and nothing in the repo calls start_tour.

Fix

  • Import stepUtils from @web_tour/tour_utils.
  • Remove test: true from the 10 tours. Every tour is a test tour in Odoo 19, so the key has no replacement. url, steps, the step keys and all run: actions (click, edit, functions) are valid in 19.
  • spp_cel_widget 19.0.2.0.0 → 19.0.2.0.1 with a HISTORY fragment. README/index.html are left for CI's pinned generator.

Tests

  • Unit + integration (spp_cel_widget/tests/test_assets.py, runs in normal CI): lists every JS module defined on the backend test page (web.assets_web + web.assets_tests, via ir.asset._get_asset_paths and Odoo's own js_transpiler) and asserts that every import in this module's JS resolves. 5 unit tests cover the helpers, so the check can't pass by matching nothing. Pre-fix it fails with exactly {'/spp_cel_widget/static/tests/tours/cel_widget_tour.js': ['@web_tour/tour_service/tour_utils']}. Every other import in the module resolves. Post-fix: 0 failed, 0 error(s) of 33 tests (27 existing + 6 new). The log's WARNING lines are identical to this branch's pre-fix run (10 distinct, all pre-existing); I didn't take a separate 19.0 baseline.
  • E2E (e2e/tests/01-spp-starter-spmis.spec.ts, new test 24): opens /odoo?debug=tests on the SP-MIS stack (which installs spp_cel_widget through spp_programs) and asserts the module loader logs no errors and injects no error banner. It waits on odoo.loader state, not networkidle, which never settles because of the bus websocket worker. It's generic, so it guards every installed module's test assets. Run locally (tests 01 + 24) at each stage:
    • before: fails on the missing @web_tour/tour_service/tour_utils;
    • after the import fix only: fails on unknown key 'test'. This is how finding 2 above was found; the static check can't see a runtime registry validation;
    • after both fixes: passes, and all 10 cel_widget_* tours are registered in the browser.
    • Note: the e2e workflow runs on push to 19.0, not on PRs.

All pre-commit hooks pass on the changed files, including eslint and semgrep.

Review notes (adversarial review run before marking ready)

No blockers. The review re-verified the fix and both tests against Odoo 19 source. It also ran the parsing helpers over every static/**/*.js in the image: the transpiler output always matched, and the first odoo.define was always the module path. Applied:

  • The defined-modules scan missed hand-written odoo.define( calls whose arguments start on the next line (spreadsheet's @odoo/o-spreadsheet wrapper). Now tolerant, with a unit test mirroring that file that fails on the old regex.
  • The e2e wait comment wrongly called the sentinel @web/../tests/legacy/utils the bundle's first module. Any module from web.assets_tests works.

Considered, not changed:

  • Test 24 is the last test of a describe.serial suite, so it is skipped if tests 01–23 fail, and it runs post-merge only. The Python test is the PR-time guard.
  • The static check can't see runtime registry validation (how the test key slipped past it). That's what test 24 is for.
  • file_open on a non-string full_path (external URL / DB ir.asset): nothing adds either to these bundles in the repo or the image. Not hardened speculatively.
  • Test 24 matches the four module-loader messages only, not every console error, to avoid noise.
  • Runtime: the integration test takes ~0.15s on a 51-module stack. It transpiles only this module's files.

Not changed → follow-up issue

The tours are loadable now, but still dead. No Python test runs them, and every one navigates via spp_programs.spp_manager_menu_root / menu_eligibility_managers, which don't exist in OpenSPP2 (legacy openspp-modules UI). spp_cel_widget/tests/README.md documents tour commands that run nothing. Out of scope here, so it's filed as #553 (revive them against the module's own spp.cel.widget.demo wizard, or delete them).

…est page

Reproduces #551: cel_widget_tour.js imports the Odoo 17 module
@web_tour/tour_service/tour_utils, which no Odoo 19 file defines, so the
module loader logs a console error and every backend tour fails. Tours
never run in CI (no Chrome in the test image), so the check resolves
imports statically with Odoo's own transpiler.
…errors

One unresolvable import in any installed module's web.assets_tests
makes Odoo's module loader log a console error, which fails every
backend tour (#551). Test 24 opens /odoo?debug=tests on the SP-MIS
stack and asserts the loader reports no errors and injects no error
banner.
…/tour_utils

Odoo 19 has no @web_tour/tour_service/tour_utils module, so the tours
file failed to load in web.assets_tests and the module loader's console
error failed every backend tour on databases with spp_cel_widget
installed (auto_install). Fixes #551.
Odoo 19 validates web_tour.tours entries against {name, steps, url,
wait_for}, so "test: true" made the tours module throw on load with
"unknown key 'test'", a module loader console error that still failed
every backend tour after the import fix. Every tour is a test tour in
Odoo 19, so the key has no replacement. Refs #551.
@codecov

codecov Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 77.18%. Comparing base (1a3c591) to head (e6beb9a).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             19.0     #552      +/-   ##
==========================================
+ Coverage   76.91%   77.18%   +0.27%     
==========================================
  Files         704      733      +29     
  Lines       45774    47666    +1892     
==========================================
+ Hits        35205    36793    +1588     
- Misses      10569    10873     +304     
Flag Coverage Δ
spp_api_v2_cycles 71.03% <ø> (ø)
spp_api_v2_entitlements 70.23% <ø> (ø)
spp_api_v2_programs 92.22% <ø> (ø)
spp_api_v2_simulation 71.19% <ø> (ø)
spp_base_common 91.07% <ø> (ø)
spp_case_entitlements 100.00% <ø> (ø)
spp_case_programs 100.00% <ø> (ø)
spp_cel_event 85.34% <ø> (?)
spp_cel_load_testing 98.11% <ø> (ø)
spp_cel_widget 65.76% <ø> (?)
spp_cr_type_assign_program 92.50% <ø> (ø)
spp_dci_compliance 93.01% <ø> (ø)
spp_dci_demo 94.28% <ø> (ø)
spp_dci_indicators 96.23% <ø> (?)
spp_dci_server_social 89.57% <ø> (?)
spp_programs 67.58% <ø> (ø)
spp_registry 89.00% <ø> (ø)
spp_security 69.56% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.
see 29 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The defined-modules scan missed hand-written odoo.define calls whose
arguments start on the next line (e.g. spreadsheet's @odoo/o-spreadsheet
wrapper), so importing one would have been reported as unresolved. The
e2e comment called the sentinel module the bundle's first; it is not,
and any module from web.assets_tests works.
@gonzalesedwin1123
gonzalesedwin1123 marked this pull request as ready for review September 24, 2026 03:56

@kneckinator kneckinator left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Edwin, nice work, especially finding the test: true rejection that the issue missed. I checked both fixes and both tests against Odoo 19 source: @web_tour/tour_utils is the right path, TourSchema in web_tour/static/src/js/tour_service.js only allows {name, steps, url, wait_for}, and the backend page loads web.assets_tests without ignore_missing_deps (webclient_templates.xml:306 vs :55). The e2e wait is sound too: the loader sets checkErrorProm = null and logs its errors in the same microtask, so a poll that sees null comes after the logging. No other module uses the old import path or the test key.

Approving. The inline comments are non-blocking. The three small ones I'd like to see fixed are the odoo.define scan scope, the loose unit-test assertion and pageerror. The rest are optional.

Two broader points, not for this PR:

  • The import check only covers files under /spp_cel_widget/, but the same failure can come from any spp_* module. Moving the helper somewhere shared (e.g. spp_base_common) and running it over every installed spp_* module would guard the whole class of bug. Worth a follow-up issue.
  • The 10 tours still load on every backend test page while testing nothing. The next Odoo tour API change could break every backend tour again, and spp_cel_widget is auto_install. That makes #553 worth picking up soon rather than later.

Comment thread spp_cel_widget/tests/test_assets.py Outdated
if header and header["alias"]:
names.add(header["alias"])
# Files that call odoo.define by hand, e.g. the "@odoo/owl" wrapper in web/static/lib.
names.update(match["name"] for match in ODOO_DEFINE_RE.finditer(content))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this runs ODOO_DEFINE_RE over the raw source of every file in both bundles. An odoo.define("x", [ in a comment, JSDoc example or string adds a module that nothing defines, and with DOTALL the .+? name can run across lines into a garbage name. It can only make the check miss a problem, never fail wrongly, but it can hide a real missing import. Transpiled modules never contain hand-written defines, so scanning only when not is_odoo_module(url, content) would remove the false matches.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 169a3acf. defined_module_names now only scans the raw source for hand-written odoo.define( calls when
not is_odoo_module(url, content). A transpiled module gets its name from its path and header alias only. The new
test_defined_module_names_ignores_define_text_in_a_transpiled_module puts a JSDoc odoo.define("@example/not_a_module", …)
in an @odoo-module file. It failed before the change and passes now.

Comment thread spp_cel_widget/tests/test_assets.py Outdated
with file_open(TOUR_FILE.lstrip("/")) as tour_file:
dependencies = module_dependencies(TOUR_FILE, tour_file.read())
self.assertTrue(
any(name.startswith("@web_tour/") for name in dependencies),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the old broken path @web_tour/tour_service/tour_utils also passes this check, so the test passes both before and after the fix. Asserting "@web_tour/tour_utils" in dependencies would pin the fix at unit level too.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 3d121269. It's now assertIn("@web_tour/tour_utils", dependencies). With the import put back to the pre-19
@web_tour/tour_service/tour_utils, it fails with '@web_tour/tour_utils' not found in ['@web/core/registry', '@web_tour/tour_service/tour_utils'], so the fix is pinned at unit level too.

Comment thread spp_cel_widget/tests/test_assets.py Outdated
def _backend_test_page_scripts(self):
scripts = {}
for bundle in BACKEND_TEST_BUNDLES:
for path, full_path, _bundle, _last_modified in self.env["ir.asset"]._get_asset_paths(bundle, {}):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: this rebuilds Odoo's asset pipeline by hand (_get_asset_paths + file_open + transpile_javascript/is_odoo_module). self.env["ir.qweb"]._get_asset_bundle(bundle).javascripts already gives JavascriptAsset objects with .url, .is_transpiled and the transpiled .content. It handles DB-backed/URL ir.asset entries (the file_open limitation you noted) and follows any future change in how Odoo decides what to transpile.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 946a3daa. _backend_test_page_scripts now returns JavascriptAssets from
self.env["ir.qweb"]._get_asset_bundle(bundle, css=False).javascripts. The test takes .is_transpiled from Odoo and
reads dependencies from the transpiled .content, so there's no local re-transpile and attachment-backed ir.asset
entries are covered.

One thing I kept from before: the defined-names side still needs the source as written. The @odoo-module alias= header
and hand-written defines live there, while the transpiled output appends the alias define with backticks, which
ODOO_DEFINE_RE doesn't match. So it reads WebAsset.content.fget(asset), the same base-class property that
JavascriptAsset.is_transpiled and .content read themselves. I checked the API against Odoo 19's assetsbundle.py and
ir_qweb.py. The test still goes red on the old import path (2 of 34 failed).

scripts[path] = script.read()
return scripts

def test_imports_resolve_on_the_backend_test_page(self):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: nothing that runs on PRs guards the second bug. If someone adds test: true (or any key outside {name, steps, url, wait_for}) to a tour, PR CI stays green and only test 24 catches it, after merge. A cheap static check on the keys passed to registry.category("web_tour.tours").add(...) in own_modules would catch it at PR time. I know you weighed this and chose to leave it to test 24, so take it or leave it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've left this to e2e test 24, as before. The check needs the top-level keys of the object literal passed to
registry.category("web_tour.tours").add(...). steps is an array of object literals whose own keys (trigger, run,
content…) sit at deeper nesting, so a regex can't tell the two levels apart reliably. Doing it properly means
brace-matching that is aware of strings, comments and template literals, or a JS parser in the Python test. That's more
machinery than the bug class warrants, and it would still miss keys built at runtime. Test 24 checks what actually fails,
Odoo's TourSchema validation at load time.

The PR-time gap you point out is real: test 24 runs on merge, not on PR. I'd rather close it by running test 24 in PR CI
(or making #553's tours go away) than with a partial static check. Happy to revisit if you feel strongly.

await login(page);
console.log("✅ Logged in as admin");

page.on("console", onConsole);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this only listens for console messages matching the four loader patterns. An uncaught exception from a test asset (a script that throws outside odoo.define, or a factory whose failure the loader doesn't report later) also fails the tour runner, and this test would still pass. Also collecting page.on("pageerror") and asserting that list is empty closes the gap for about two lines.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in e6beb9a3. Test 24 now collects page.on("pageerror") and asserts expect(pageErrors).toEqual([]), and detaches
the listener in the finally. For the red check I temporarily added
setTimeout(() => { throw new Error("pageerror-probe"); }) to the tour file, so the throw happened outside anything the
loader reports. The test failed with + "pageerror-probe". Without the probe, e2e 01 and 24 pass.

Comment thread e2e/tests/01-spp-starter-spmis.spec.ts Outdated
null,
{timeout: 30_000}
);
await page.waitForLoadState("load");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this looks redundant. The sentinel is in the deferred web.assets_tests script, and the loader's error report has already run by the time checkErrorProm === null is seen. It suggests a timing dependency that doesn't exist. Harmless either way.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 73329dfb: removed. You're right that the sentinel is defined by the deferred web.assets_tests script, and the
loader has reported by the time checkErrorProm === null. Test 24 still passes without the wait.

…the tour-file unit test

The old assertion accepted any @web_tour/ import, so it passed with the
pre-19 @web_tour/tour_service/tour_utils path too (PR #552 review).
… odoo.define calls

A transpiled module never contains a hand-written define, so an
odoo.define("x", [ in its comments, JSDoc or strings added a module
nothing defines and could hide a real missing import (PR #552 review).
…asset bundles

Use ir.qweb._get_asset_bundle(...).javascripts instead of rebuilding the
pipeline from ir.asset paths and file_open: Odoo's JavascriptAsset decides
what is transpiled and serves the transpiled content, and it also reads
ir.asset entries stored as attachments (PR #552 review).
The sentinel is defined by the deferred web.assets_tests script, and the
loader has reported its errors by the time checkErrorProm is null, so the
extra waitForLoadState("load") only suggested a timing dependency that
does not exist (PR #552 review).
An uncaught exception from a test asset, such as a script that throws
outside odoo.define, also fails the tour runner without a module-loader
console message. Collect pageerror events and assert there are none
(PR #552 review).
@gonzalesedwin1123

Copy link
Copy Markdown
Member Author

Thanks for the careful review. I checked all six inline comments and changed five of them. Each change is its own
commit on e6beb9a3:

  • 3d121269: the tour-file unit test now asserts the exact @web_tour/tour_utils path.
  • 169a3acf: hand-written odoo.define calls are only scanned in files that aren't transpiled, and there's a
    regression test for it.
  • 946a3daa: script collection now goes through ir.qweb._get_asset_bundle(...).javascripts.
  • 73329dfb: dropped the redundant waitForLoadState("load").
  • e6beb9a3: test 24 also fails on pageerror.

I proved every tightened check red before calling it green:

  • 3d121269 and 169a3acf: with the tour import put back to @web_tour/tour_service/tour_utils, 3 of 34 tests
    failed: the new define-scan test, the tightened import test and the integration test. After the fix, 34 of 34 pass.
  • 946a3daa: the bundle-based integration test still fails on the old path (2 of 34 failed) and passes on the fix.
  • e6beb9a3: a temporary setTimeout(() => { throw new Error("pageerror-probe"); }) in the tour file made test 24
    fail with pageErrors = ["pageerror-probe"]. With the probe removed, e2e 01 and 24 pass (2 passed, 1.8 min).

The static tour-key check stays with test 24; the reasons are in the thread. I filed #578 for your shared
spp_* import-check idea. +1 on #553 soon.

@gonzalesedwin1123
gonzalesedwin1123 merged commit 971c9c0 into 19.0 Sep 29, 2026
35 checks passed
@gonzalesedwin1123
gonzalesedwin1123 deleted the fix-551-cel-widget-tour-import branch September 29, 2026 10:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spp_cel_widget: cel_widget_tour.js imports the Odoo 17 path @web_tour/tour_service/tour_utils, which breaks web.assets_tests on 19.0

2 participants