Files
computer-playbook/roles/web-app-odoo/files/playwright/addons/account.spec.js
kevinveenbirkenbachandClaude Opus 5.5 373d5c9dc7 fix(web-app-odoo): run the onion suite on full Chromium so its origin is secure
The shared Playwright config passes --unsafely-treat-insecure-origin-as-secure
for every .onion origin in the environment, but Playwright's default
chrome-headless-shell ignores that switch. On the tor axis the odoo origin
therefore stayed an insecure context: navigator.clipboard was undefined and
Odoo 20's onboarding tour crashed with "Cannot read properties of undefined
(reading 'writeText')" and "Cannot destructure property 'TourInteractive'",
opening error dialogs that the addon specs read as a missing module (odoo#2,
swarm tor, main run 38038541631).

When ODOO_BASE_URL is an onion URL the odoo specs now use channel "chromium",
the full Chromium build in the Playwright image, and block service workers;
clearnet runs keep every option unchanged. The worker is blocked because a
secure context lets Odoo register /web/service-worker.js, whose fetch handler
answers a failed navigation with a cached page or /odoo/offline: gotoOnion
would get a 200 instead of the transport error it retries on. The switch stays
in the role: a use.channel in the shared config would also reach the Firefox
launch of onion-test.js, which rejects it with "Unsupported firefox channel",
and would move every other role's onion run to a browser none of them was
tested on.

Verified with Playwright 1.63 in the runner's staging layout and the real
odoo specs: an onion ODOO_BASE_URL resolves channel "chromium" and
serviceWorkers "block", also for the contexts _shared.js creates itself, and
launches chromium-<rev>/chrome-linux64/chrome; a clearnet one, and HEAD's
specs on onion, keep the headless shell and "allow". All spec files stay on
one worker, which the suite's one-worker test requires. Against Odoo 20's
service worker with a dropped connection, full Chromium returned the cached
/odoo page with status 200, while with the worker blocked page.goto threw
net::ERR_CONNECTION_FAILED as the headless shell does. In the v1.64.0 image
full Chromium made no :443 attempt to an onion host and the onion origin
reported isSecureContext with crypto.subtle, which the headless shell did not.
A local deploy of web-app-odoo variant 2 over Tor ran the odoo suite on
/ms-playwright/chromium-1248/chrome-linux64/chrome and passed all seven addon
specs, the one-worker test and the guest flow without a retry; in the variant
0 round the suites of the other roles kept chrome-headless-shell.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 18:35:22 +02:00

57 lines
3.2 KiB
JavaScript

const { test, expect } = require("@playwright/test");
const { resolveTimeout } = require("../timeouts");
const { skipUnlessAddonEnabled } = require("../addon-gating");
const shared = require("../_shared");
test.use(shared.onionOptions);
test("addon account: Accounting/Invoicing module is installed and serves its customer-invoices action", async ({ browser }) => {
skipUnlessAddonEnabled("account");
const { context, page } = await shared.authenticatedContext(browser);
try {
// Odoo 19 resolves the account module's Customer Invoices action at the
// /odoo/invoicing deep-link (title "Invoices"); the Accounting dashboard at
// /odoo/accounting only shows journal cards. When the account module is not
// installed both routes degrade to the default home app (Discuss), which has
// none of the invoice vocabulary asserted below.
await shared.openModule(page, "odoo/invoicing");
const errorPage = page.locator(".o_error_dialog, .o_error_detail").or(
page.getByText(/invalid action|action.*not found|sorry,?\s*(this page|the page).*(not found|does not exist)|page not found|404 not found/i)
);
await expect(
errorPage,
"opening the Invoicing action must not render an Odoo error/not-found/invalid-action page; when account is enabled but the module is not installed the action xmlid does not exist and Odoo errors instead of rendering the app"
).toHaveCount(0);
const invoicesAction = page
.locator(".o_list_view, .o_list_renderer, .o_kanban_view, .o_kanban_renderer")
.first();
await expect(
invoicesAction,
"the account.move customer-invoices action (list/kanban) must render — its presence proves the account module's server action is actually wired, not just that some Odoo page loaded"
).toBeVisible({ timeout: resolveTimeout(120_000) });
const accountSurface = page
.locator(".o_action_manager")
.getByText(/customer invoice|register payment|invoice date|tax excluded/i)
.or(page.getByRole("button", { name: /^new$/i }))
.or(page.locator(".o_breadcrumb, .o_control_panel").filter({ hasText: /invoic/i }))
.first();
await expect(
accountSurface,
"the Invoicing surface must expose account-domain content (a Customer Invoice / Register Payment / Invoice Date / Tax Excluded label, the New create button, or an Invoices breadcrumb) — present only because the account module installed and registered account.move views; a bare home shell does NOT satisfy this, distinguishing a real install from silent degradation"
).toBeVisible({ timeout: resolveTimeout(120_000) });
await expect(
page.locator(".o_action_manager"),
"the loaded action must expose Accounting/Invoicing domain vocabulary (customer invoice / invoice / payment / tax / journal / bill), proving the account module — not a generic Odoo surface — is what rendered; if account is enabled but the module did not install, the route degrades to the home app and this text is absent, so the test MUST fail here"
).toContainText(/customer invoice|invoice|payment|tax excluded|journal|vendor bill/i, { timeout: resolveTimeout(120_000) });
} finally {
await page.close().catch(() => {});
await context.close().catch(() => {});
}
});