@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 +9 -0
- package/package.json +1 -1
- package/tests/step-definitions/odoo.steps.js +79 -11
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-
|
|
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
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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
|
/**
|