@webship-js/odoo-steps 2.0.0-alpha1 → 2.0.0-beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -15,6 +15,7 @@ It is a **plugin for webship-js** — it does not run standalone. It ships one f
15
15
  - Jobs listing
16
16
  - `/contactus` form
17
17
  - `/web/login` login form + authentication (and the invalid-login error path)
18
+ - Sessions — log in as a named account from `worldParameters.users`, submit the login form robustly (an in-page submit that an overlay cannot block), and `Given I am not logged in` to end a session and land on the login form; for fast suites, snapshot an authenticated session once with webship-js's own `save the auth state` / `restore the auth state` steps
18
19
  - 404 page
19
20
 
20
21
  See [tests/step-definitions/odoo.steps.js](tests/step-definitions/odoo.steps.js) for the full list of steps with Gherkin examples.
@@ -43,6 +44,14 @@ module.exports = {
43
44
  paths: ['tests/features/**/*.feature'],
44
45
  worldParameters: {
45
46
  launchUrl: process.env.LAUNCH_URL || 'https://your-odoo-site.example',
47
+ // Named accounts for `Given I am a logged in user with the "<key>" user`.
48
+ // Keeps passwords out of feature files; override per environment.
49
+ users: {
50
+ admin: {
51
+ username: process.env.ODOO_ADMIN_USER || 'admin',
52
+ password: process.env.ODOO_ADMIN_PASSWORD || 'admin',
53
+ },
54
+ },
46
55
  // ...
47
56
  },
48
57
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webship-js/odoo-steps",
3
- "version": "2.0.0-alpha1",
3
+ "version": "2.0.0-beta1",
4
4
  "description": "Odoo-specific Cucumber step definitions plugin for webship-js — BDD testing for the main pages of any Odoo Website site (Playwright + Cucumber).",
5
5
  "main": "tests/step-definitions/odoo.steps.js",
6
6
  "files": [
@@ -25,6 +25,30 @@ const { Given, When, Then } = require('@cucumber/cucumber');
25
25
  const assert = require('assert');
26
26
  const { gotoUrl, waitForPageLoad, fillField } = require('webship-js/tests/step-definitions/webship');
27
27
 
28
+ // Fill and submit Odoo's login form. The submit is dispatched from inside the
29
+ // page rather than with a pointer click: a fixed overlay (a cookie bar, a chat
30
+ // widget) can cover the button and make a real click wait out its
31
+ // actionability timeout, while the form's own submit cannot be covered.
32
+ // A successful login redirects to the backend webclient (/web), which keeps a
33
+ // permanent longpoll connection open (Discuss/bus), so networkidle never
34
+ // fires there and the shared settle helpers would not settle: wait for the
35
+ // navigation and DOM content only. An invalid login stays on /web/login
36
+ // without a full navigation, so this resolves quickly either way.
37
+ async function submitLogin(page, username, password) {
38
+ await fillField(page, 'login', username);
39
+ await fillField(page, 'password', password);
40
+ await Promise.all([
41
+ page.waitForNavigation({ waitUntil: 'domcontentloaded', timeout: 15000 }).catch(() => {}),
42
+ page.evaluate(() => {
43
+ const form = document.querySelector('form[action="/web/login"]');
44
+ const submit = form && form.querySelector('button[type="submit"]');
45
+ if (submit) { submit.click(); return; }
46
+ if (form) form.submit();
47
+ }),
48
+ ]);
49
+ await page.waitForLoadState('domcontentloaded', { timeout: 15000 }).catch(() => {});
50
+ }
51
+
28
52
  // ---------------------------------------------------------------------------
29
53
  // Core layout: #wrapwrap, top menu, footer
30
54
  // ---------------------------------------------------------------------------
@@ -472,17 +496,61 @@ Then(/^(I |we )*should see the Odoo (?:sign-?in|login) form$/, async function (p
472
496
  *
473
497
  */
474
498
  When(/^(I |we )*(?:log in|sign in) to Odoo as "([^"]*)?" with password "([^"]*)?"$/, async function (pronounCase, username, password) {
475
- await fillField(this.page, 'login', username);
476
- await fillField(this.page, 'password', password);
477
- await this.page.locator('form[action="/web/login"] button[type="submit"]').first().click();
478
- // A successful login redirects to the backend webclient (/web), which
479
- // keeps a permanent longpoll connection open (Discuss/bus) — so
480
- // networkidle never fires there and the shared waitForPageLoad()/
481
- // smartSettle() helper (built around network-idle + ajax-quiet
482
- // detection) will not settle. Wait for DOM content only instead; an
483
- // invalid login stays on /web/login without a full navigation, so this
484
- // resolves quickly either way.
485
- await this.page.waitForLoadState('domcontentloaded', { timeout: 15000 }).catch(() => {});
499
+ await submitLogin(this.page, username, password);
500
+ });
501
+
502
+ /**
503
+ * Log in as a user configured in cucumber.js `worldParameters.users`, so no
504
+ * password appears in a feature file. Each key names one account:
505
+ * `{ admin: { username: 'admin', password: process.env.ODOO_ADMIN_PASSWORD } }`.
506
+ * The login screen is opened first, so this step works from any page.
507
+ *
508
+ * Example #1: Given I am a logged in user with the "admin" user
509
+ * Example #2: Given I am a logged in user with the username "admin"
510
+ * Example #3: Given we are a logged in user with "portal"
511
+ * Example #4: And a logged in user with the "admin" user
512
+ * Example #5: Given I am a logged in user with the username "demo" user
513
+ *
514
+ */
515
+ Given(/^(I am |we are )*a logged in user with( the)*( username)* "([^"]*)?"( user)*$/, async function (pronounCase, theCase, usernameCase, userKey, userCase) {
516
+ const users = (this.parameters && this.parameters.users) || {};
517
+ const entry = users[userKey];
518
+ if (!entry) {
519
+ throw new Error(`User "${userKey}" is not configured. Add it to worldParameters.users in cucumber.js. Known users: ${Object.keys(users).join(', ') || '(none)'}.`);
520
+ }
521
+ const username = entry.username || userKey;
522
+ const password = typeof entry === 'string' ? entry : entry.password;
523
+ if (!password) {
524
+ throw new Error(`User "${userKey}" has no password configured. Set worldParameters.users.${userKey}.password in cucumber.js, ideally from an environment variable.`);
525
+ }
526
+ await gotoUrl(this.page, this.launchUrl + '/web/login');
527
+ await this.page.locator('#login').waitFor({ state: 'visible', timeout: 10000 });
528
+ await submitLogin(this.page, username, password);
529
+ });
530
+
531
+ /**
532
+ * End any authenticated session and land on the login screen as a visitor:
533
+ * cookies and web storage are cleared, `/web/login` is opened and the core
534
+ * login form is confirmed. Use it at the start of a scenario that must run
535
+ * anonymously after an earlier one logged in.
536
+ *
537
+ * Example #1: Given I am not logged in
538
+ * Example #2: Given we are not logged in
539
+ * Example #3: And I am not logged in
540
+ * Example #4: Given not logged in
541
+ * Example #5: But I am not logged in
542
+ *
543
+ */
544
+ Given(/^(I am |we are )*not logged in$/, async function (pronounCase) {
545
+ await this.context.clearCookies();
546
+ try {
547
+ await this.page.evaluate(() => {
548
+ try { localStorage.clear(); } catch { /* storage may be unavailable */ }
549
+ try { sessionStorage.clear(); } catch { /* storage may be unavailable */ }
550
+ });
551
+ } catch { /* the page may not be on a real URL yet */ }
552
+ await gotoUrl(this.page, this.launchUrl + '/web/login');
553
+ await this.page.locator('form[action="/web/login"]').waitFor({ state: 'visible', timeout: 10000 });
486
554
  });
487
555
 
488
556
  /**