@webship-js/django-cms-steps 0.0.0-stage → 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Webship.co
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,100 @@
1
- # Temporary Holding Version
1
+ # django-cms-steps
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Custom [webship-js](https://www.npmjs.com/package/webship-js) step definitions
4
+ for testing [django CMS](https://www.django-cms.org/) sites: Django admin
5
+ login/logout (as a user from the `users` registry), the CMS toolbar, toolbar
6
+ navigation (including the sideframe overlay), the page tree, and the
7
+ "no pages yet" welcome screen.
8
+
9
+ `@webship-js/django-cms-steps` is a **plugin** for webship-js — it does not replace it. It
10
+ ships one file, `tests/step-definitions/django-cms.steps.js`, of extra `Given`/`When`/`Then`
11
+ steps that you load alongside webship-js's own step library in your project's
12
+ `cucumber.js`. There is no `custom.js`; every step this package provides
13
+ lives in `django-cms.steps.js`.
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ npm install --save-dev webship-js @webship-js/django-cms-steps
19
+ ```
20
+
21
+ Both packages are devDependencies of your test project. `webship-js` is a
22
+ peer dependency of `@webship-js/django-cms-steps`.
23
+
24
+ ## Quick start
25
+
26
+ Add this package's steps to your `cucumber.js` `require` array, next to
27
+ webship-js's own:
28
+
29
+ ```js
30
+ // cucumber.js
31
+ module.exports = {
32
+ default: {
33
+ requireModule: ['ts-node/register'],
34
+ require: [
35
+ 'node_modules/webship-js/tests/step-definitions/**/*.js',
36
+ 'node_modules/@webship-js/django-cms-steps/tests/step-definitions/*.steps.js',
37
+ 'tests/step-definitions/**/*.js', // your own project-specific steps, if any
38
+ ],
39
+ paths: ['tests/features/**/*.feature'],
40
+ worldParameters: {
41
+ launchUrl: process.env.LAUNCH_URL || 'http://localhost',
42
+ // Accounts for "a logged in user with the username "<key>"". Keep the
43
+ // password out of the repo: read it from an environment variable.
44
+ users: {
45
+ admin: {
46
+ username: process.env.DJANGOCMS_ADMIN_USER || 'admin',
47
+ password: process.env.DJANGOCMS_ADMIN_PASS || 'admin',
48
+ },
49
+ },
50
+ },
51
+ },
52
+ };
53
+ ```
54
+
55
+ Then write a feature using the new steps:
56
+
57
+ ```gherkin
58
+ Feature: CMS toolbar
59
+
60
+ Scenario: Admin sees the CMS toolbar and opens the page tree
61
+ Given I am a logged in user with the username "admin"
62
+ Then I should see the CMS toolbar
63
+ When I click "Pages" in the CMS toolbar menu
64
+ Then the CMS sideframe should be showing "/admin/cms/pagecontent/"
65
+ ```
66
+
67
+ ```bash
68
+ npx cucumber-js --config cucumber.js
69
+ ```
70
+
71
+ ## Steps
72
+
73
+ | Step | Type |
74
+ | --- | --- |
75
+ | `I am a logged in user with the username "<key>"` (reads `worldParameters.users`) | Given |
76
+ | `I am not logged in` (clears cookies and storage, asserts the admin login form) | Given |
77
+ | `I log out` | When |
78
+ | `I should see the CMS toolbar` / `I should not see the CMS toolbar` | Then |
79
+ | `I click "<label>" in the CMS toolbar menu` | When |
80
+ | `the CMS sideframe should be showing "<url part>"` | Then |
81
+ | `I am on the django CMS page tree` | Given |
82
+ | `I should see the page tree` | Then |
83
+ | `I should see "<title>" in the page tree` | Then |
84
+ | `I should see the django CMS welcome screen` | Then |
85
+
86
+ Every step's regex and Gherkin examples are in the JSDoc block above it in
87
+ [`tests/step-definitions/django-cms.steps.js`](tests/step-definitions/django-cms.steps.js).
88
+
89
+ ## Testing this package itself
90
+
91
+ This repository also contains its own test suite (`tests/features/`) that
92
+ exercises the steps in `tests/step-definitions/django-cms.steps.js`. It
93
+ doesn't bundle a demo site — point it at any django CMS install you have via
94
+ `LAUNCH_URL`. CI builds a throwaway django CMS site with
95
+ [ddev](https://ddev.com/) at run time to test against on every push/PR — see
96
+ [.github/workflows/test.yml](.github/workflows/test.yml).
97
+
98
+ ## License
99
+
100
+ [MIT](LICENSE) © Webship.co
package/index.js ADDED
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * django-cms-steps
5
+ *
6
+ * Custom webship-js step definitions for testing django CMS sites.
7
+ *
8
+ * This module has no exported functions of its own — requiring
9
+ * `tests/step-definitions/django-cms.steps.js` (directly, or via the glob
10
+ * below) is what registers the Given/When/Then steps with @cucumber/cucumber.
11
+ *
12
+ * Consumers wire this package into their cucumber.js `require` array,
13
+ * see README.md for details.
14
+ */
15
+
16
+ const path = require('path');
17
+
18
+ const STEP_DEFINITIONS_GLOB = path.join(
19
+ 'node_modules',
20
+ '@webship-js/django-cms-steps',
21
+ 'tests',
22
+ 'step-definitions',
23
+ '*.steps.js'
24
+ );
25
+
26
+ module.exports = {
27
+ stepDefinitionsGlob: STEP_DEFINITIONS_GLOB,
28
+ stepDefinitionsPath: path.join(__dirname, 'tests', 'step-definitions', 'django-cms.steps.js'),
29
+ };
package/package.json CHANGED
@@ -1,6 +1,56 @@
1
1
  {
2
2
  "name": "@webship-js/django-cms-steps",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "2.0.0-beta1",
4
+ "description": "Custom webship-js step definitions for testing django CMS sites (admin login, the CMS toolbar, toolbar navigation, and the page tree).",
5
+ "main": "index.js",
6
+ "files": [
7
+ "index.js",
8
+ "tests/step-definitions",
9
+ "README.md",
10
+ "LICENSE"
11
+ ],
12
+ "scripts": {
13
+ "test": "BROWSER=chromium cucumber-js --config cucumber.js",
14
+ "test:chromium": "BROWSER=chromium cucumber-js --config cucumber.js",
15
+ "test:firefox": "BROWSER=firefox cucumber-js --config cucumber.js",
16
+ "test:webkit": "BROWSER=webkit cucumber-js --config cucumber.js",
17
+ "report": "generate-reports"
18
+ },
19
+ "keywords": [
20
+ "webship-js",
21
+ "django",
22
+ "django-cms",
23
+ "cucumber",
24
+ "playwright",
25
+ "bdd",
26
+ "testing",
27
+ "step-definitions"
28
+ ],
29
+ "author": "Webship.co",
30
+ "license": "MIT",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/webship/django-cms-steps.git"
34
+ },
35
+ "bugs": {
36
+ "url": "https://github.com/webship/django-cms-steps/issues"
37
+ },
38
+ "homepage": "https://github.com/webship/django-cms-steps#readme",
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "engines": {
43
+ "node": ">= 20.0"
44
+ },
45
+ "peerDependencies": {
46
+ "webship-js": "^2.0.6"
47
+ },
48
+ "devDependencies": {
49
+ "webship-js": "^2.0.6",
50
+ "ts-node": "^10",
51
+ "typescript": "~5.4.0"
52
+ },
53
+ "overrides": {
54
+ "@cucumber/cucumber": "^12"
55
+ }
56
+ }
@@ -0,0 +1,321 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * django-cms-steps / tests/step-definitions/django-cms.steps.js
5
+ *
6
+ * Custom webship-js step definitions for testing django CMS sites:
7
+ * Django admin authentication, the CMS toolbar and its navigation menu,
8
+ * the page tree, and the "no pages yet" welcome screen.
9
+ *
10
+ * These steps assume the standard webship-js World: `this.page`
11
+ * (Playwright Page), `this.context` (BrowserContext), `this.launchUrl`,
12
+ * and `this.parameters` (cucumber.js worldParameters).
13
+ *
14
+ * Selectors below were captured against a real django CMS 5 install
15
+ * (Django admin login form, the `#cms-top` / `.cms-toolbar` toolbar, and
16
+ * the `.cms-pagetree` page-tree admin). Every step documents its Gherkin
17
+ * phrasing in the JSDoc block right above it.
18
+ */
19
+
20
+ const { Given, When, Then } = require('@cucumber/cucumber');
21
+ const assert = require('assert');
22
+ const { gotoUrl, smartSettle } = require('webship-js/tests/step-definitions/webship');
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // Shared selectors (django CMS toolbar / Django admin)
26
+ // ---------------------------------------------------------------------------
27
+
28
+ const SELECTORS = {
29
+ // The CMS toolbar is mounted as #cms-top and always carries the
30
+ // .cms-toolbar class on its inner bar, whether or not there are any
31
+ // pages yet.
32
+ toolbar: '#cms-top',
33
+ toolbarBar: '#cms-top .cms-toolbar',
34
+ toolbarNav: '#cms-top .cms-toolbar-item-navigation',
35
+ // Django's own admin login form (served at /admin/login/).
36
+ loginForm: '#login-form',
37
+ loginUsername: '#id_username',
38
+ loginPassword: '#id_password',
39
+ // The page-tree change list at /admin/cms/pagecontent/.
40
+ pageTree: '.cms-pagetree-jstree, .cms-pagetree',
41
+ pageTreeEmpty: '.cms-pagetree-empty',
42
+ // The "no pages yet" welcome screen shown on a fresh install.
43
+ welcomeScreen: '.cms-welcome-heading, .cms-welcome-section, .cms-welcome, #cms-welcome-config',
44
+ };
45
+
46
+ function siteRoot(launchUrl) {
47
+ return String(launchUrl).replace(/\/$/, '');
48
+ }
49
+
50
+ async function settle(world, fallback) {
51
+ await smartSettle(world.page, (world.minWaitTime && world.minWaitTime.page) || fallback || 3000);
52
+ }
53
+
54
+ /**
55
+ * Look up a named user from cucumber.js worldParameters.users
56
+ * (`{ <key>: { username, password } }`), throwing a helpful error if it is
57
+ * not configured.
58
+ */
59
+ function resolveUser(parameters, userKey) {
60
+ const users = (parameters && parameters.users) || {};
61
+ const entry = users[userKey];
62
+ if (!entry) {
63
+ throw new Error(
64
+ `User "${userKey}" is not configured. Add it to worldParameters.users in cucumber.js. ` +
65
+ `Known users: ${Object.keys(users).join(', ') || '(none)'}.`
66
+ );
67
+ }
68
+ const username = entry.username || userKey;
69
+ const password = typeof entry === 'string' ? entry : entry.password;
70
+ if (!password) {
71
+ throw new Error(
72
+ `User "${userKey}" has no password configured. Set worldParameters.users.${userKey}.password ` +
73
+ 'in cucumber.js, ideally from an environment variable.'
74
+ );
75
+ }
76
+ return { username, password };
77
+ }
78
+
79
+ /**
80
+ * Open the toolbar's site-name dropdown. It opens on click, toggling a
81
+ * "-hover" class via JS; it is not a plain CSS :hover menu.
82
+ */
83
+ async function openToolbarSiteMenu(page) {
84
+ await page.waitForSelector(SELECTORS.toolbarNav, { state: 'visible', timeout: 10000 });
85
+ const siteMenu = page.locator(`${SELECTORS.toolbarNav} > li`).first();
86
+ await siteMenu.click();
87
+ }
88
+
89
+ // ---------------------------------------------------------------------------
90
+ // Authentication (Django admin login)
91
+ // ---------------------------------------------------------------------------
92
+
93
+ /**
94
+ * Log in through the Django admin login form as a user from the `users`
95
+ * registry in cucumber.js worldParameters (`{ <key>: { username, password } }`,
96
+ * with the password read from an environment variable so no credential has
97
+ * to live in a feature file).
98
+ *
99
+ * The CMS toolbar is only injected on pages served through the CMS, not on
100
+ * the raw Django admin index, so the login redirects back to the front end
101
+ * ("/"). A default django CMS project (as scaffolded by the `python -m cms`
102
+ * installer) serves a single, unprefixed language, so URLs here are bare:
103
+ * no "/en/" (or other language code) segment.
104
+ *
105
+ * Example #1: Given I am a logged in user with the username "admin"
106
+ * Example #2: Given we are a logged in user with username "admin"
107
+ * Example #3: Given I am a logged in user with "admin"
108
+ * Example #4: And a logged in user with the username "admin"
109
+ * Example #5: Given I am a logged in user with the username "admin" user
110
+ *
111
+ */
112
+ Given(/^(I am |we are )*a logged in user with( the)*( username)* "([^"]*)?"( user)*$/, async function (pronounCase, theCase, usernameCase, userKey, userCase) {
113
+ const { username, password } = resolveUser(this.parameters, userKey);
114
+ const page = this.page;
115
+ await gotoUrl(page, `${siteRoot(this.launchUrl)}/admin/login/?next=/`);
116
+ await page.waitForSelector(SELECTORS.loginUsername, { state: 'visible', timeout: 10000 });
117
+ await page.fill(SELECTORS.loginUsername, username);
118
+ await page.fill(SELECTORS.loginPassword, password);
119
+ await Promise.all([
120
+ page.waitForNavigation({ waitUntil: 'domcontentloaded', timeout: 30000 }).catch(() => {}),
121
+ page.evaluate((formSelector) => {
122
+ const submit = document.querySelector(`${formSelector} input[type="submit"], ${formSelector} button[type="submit"]`);
123
+ if (!submit) throw new Error(`The login form "${formSelector}" has no submit button.`);
124
+ submit.click();
125
+ }, SELECTORS.loginForm),
126
+ ]);
127
+ await settle(this);
128
+ const stillOnLogin = await page.locator(`${SELECTORS.loginForm} .errornote`).count();
129
+ if (stillOnLogin) {
130
+ const text = (await page.locator(`${SELECTORS.loginForm} .errornote`).innerText()).trim();
131
+ throw new Error(`Login as "${username}" was rejected: ${text}`);
132
+ }
133
+ });
134
+
135
+ /**
136
+ * Make sure nobody is logged in: clear the browser's cookies and web
137
+ * storage, load the Django admin login page again, and assert its login
138
+ * form (`#login-form`) is showing.
139
+ *
140
+ * Example #1: Given I am not logged in
141
+ * Example #2: Given we are not logged in
142
+ * Example #3: And not logged in
143
+ * Example #4: Given I am not logged in
144
+ * Example #5: And we are not logged in
145
+ *
146
+ */
147
+ Given(/^(I am |we are )*not logged in$/, async function (pronounCase) {
148
+ await this.context.clearCookies();
149
+ await this.page.evaluate(() => {
150
+ try { window.localStorage.clear(); } catch (e) { /* storage may be unavailable */ }
151
+ try { window.sessionStorage.clear(); } catch (e) { /* storage may be unavailable */ }
152
+ }).catch(() => {});
153
+ const loginUrl = `${siteRoot(this.launchUrl)}/admin/login/`;
154
+ if (this.page.url().startsWith(loginUrl)) {
155
+ await this.page.reload({ waitUntil: 'domcontentloaded' });
156
+ } else {
157
+ await gotoUrl(this.page, loginUrl);
158
+ }
159
+ await this.page.waitForSelector(SELECTORS.loginForm, { state: 'visible', timeout: 10000 });
160
+ await settle(this, 1000);
161
+ });
162
+
163
+ /**
164
+ * Log out of the Django admin / CMS toolbar.
165
+ *
166
+ * django CMS's "Logout <username>" toolbar link submits an AJAX POST
167
+ * (Django's admin logout view rejects a bare GET), so this step opens the
168
+ * toolbar's site-name menu and clicks through it rather than navigating
169
+ * to /admin/logout/ directly.
170
+ *
171
+ * Example #1: When I log out
172
+ * Example #2: When we log out
173
+ * Example #3: And I log out
174
+ *
175
+ */
176
+ When(/^(I |we )*log out$/, async function (pronounCase) {
177
+ await openToolbarSiteMenu(this.page);
178
+ const logoutLink = this.page.locator(`${SELECTORS.toolbarNav} a`).filter({ hasText: /Logout/i });
179
+ await Promise.all([
180
+ this.page.waitForNavigation({ waitUntil: 'domcontentloaded', timeout: 30000 }).catch(() => {}),
181
+ logoutLink.first().click(),
182
+ ]);
183
+ await settle(this);
184
+ });
185
+
186
+ // ---------------------------------------------------------------------------
187
+ // CMS toolbar
188
+ // ---------------------------------------------------------------------------
189
+
190
+ /**
191
+ * Assert the django CMS toolbar is (or is not) visible. It renders for
192
+ * logged-in staff users and never for anonymous visitors.
193
+ *
194
+ * Example #1: Then I should see the CMS toolbar
195
+ * Example #2: Then I should not see the CMS toolbar
196
+ * Example #3: And we should see the CMS toolbar
197
+ * Example #4: But we should not see the CMS toolbar
198
+ *
199
+ */
200
+ Then(/^(I |we )*should( not)* see the CMS toolbar$/, async function (pronounCase, notCase) {
201
+ if (notCase) {
202
+ const count = await this.page.locator(SELECTORS.toolbar).count();
203
+ assert.strictEqual(count, 0, 'CMS toolbar should not be visible but it is.');
204
+ return;
205
+ }
206
+ await this.page.waitForSelector(SELECTORS.toolbarBar, { state: 'visible', timeout: 10000 });
207
+ });
208
+
209
+ /**
210
+ * Open the toolbar's site-name menu and click a link by its visible text
211
+ * (e.g. the "Pages...", "Users...", "Administration...", "Aliases...", or
212
+ * "User settings..." entries). Matching is case-insensitive and ignores a
213
+ * trailing ellipsis, so "Pages" matches "Pages...".
214
+ *
215
+ * Most of these links carry `data-rel="sideframe"`: django CMS opens them
216
+ * in an in-page iframe overlay (`.cms-sideframe`) rather than navigating
217
+ * the main page. Assert on the sideframe with the step below, not with
218
+ * webship-js's generic "I should be on ..." page-URL steps.
219
+ *
220
+ * Example #1: When I click "Pages" in the CMS toolbar menu
221
+ * Example #2: When I click "Users" in the CMS toolbar menu
222
+ * Example #3: And we click "Administration" in the CMS toolbar menu
223
+ *
224
+ */
225
+ When(/^(I |we )*click "([^"]*)" in the CMS toolbar menu$/, async function (pronounCase, linkText) {
226
+ await openToolbarSiteMenu(this.page);
227
+ const label = linkText.trim().replace(/[.…]+$/, '');
228
+ const escaped = label.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
229
+ const link = this.page
230
+ .locator(`${SELECTORS.toolbarNav} a`)
231
+ .filter({ hasText: new RegExp(`^\\s*${escaped}(?:\\.\\.\\.|…)?\\s*$`, 'i') });
232
+ await link.first().click();
233
+ await settle(this, 1000);
234
+ });
235
+
236
+ /**
237
+ * Assert the CMS sideframe overlay is open and its iframe is showing a
238
+ * URL containing the given substring.
239
+ *
240
+ * Example #1: Then the CMS sideframe should be showing "/admin/cms/pagecontent/"
241
+ * Example #2: Then the CMS sideframe should be showing "/admin/auth/user/"
242
+ * Example #3: And the CMS sideframe should be showing "/admin/"
243
+ *
244
+ */
245
+ Then(/^the CMS sideframe should be showing "([^"]*)"$/, async function (urlSubstring) {
246
+ const iframe = this.page.locator('.cms-sideframe-frame iframe').first();
247
+ await iframe.waitFor({ state: 'attached', timeout: 10000 });
248
+ const src = await iframe.getAttribute('src');
249
+ assert.ok(
250
+ src && src.includes(urlSubstring),
251
+ `Expected the CMS sideframe to be showing a URL containing "${urlSubstring}" but it was showing "${src}".`
252
+ );
253
+ });
254
+
255
+ // ---------------------------------------------------------------------------
256
+ // Page tree
257
+ // ---------------------------------------------------------------------------
258
+
259
+ /**
260
+ * Navigate directly to the django CMS page-tree admin
261
+ * (/admin/cms/pagecontent/).
262
+ *
263
+ * Example #1: Given I am on the django CMS page tree
264
+ * Example #2: Given we are on the django CMS page tree
265
+ * Example #3: And on the django CMS page tree
266
+ *
267
+ */
268
+ Given(/^(I am |we are )*on the django CMS page tree$/, async function (pronounCase) {
269
+ await gotoUrl(this.page, `${siteRoot(this.launchUrl)}/admin/cms/pagecontent/`);
270
+ await settle(this);
271
+ });
272
+
273
+ /**
274
+ * Assert the page tree admin has rendered (either populated or the
275
+ * "no pages yet" empty state).
276
+ *
277
+ * Example #1: Then I should see the page tree
278
+ * Example #2: Then we should see the page tree
279
+ * Example #3: And should see the page tree
280
+ *
281
+ */
282
+ Then(/^(I |we )*should see the page tree$/, async function (pronounCase) {
283
+ await this.page.waitForSelector(SELECTORS.pageTree, { state: 'visible', timeout: 10000 });
284
+ });
285
+
286
+ /**
287
+ * Assert a page with the given title is listed in the page tree.
288
+ *
289
+ * Example #1: Then I should see "Home" in the page tree
290
+ * Example #2: Then we should see "About" in the page tree
291
+ * Example #3: And should see "Contact" in the page tree
292
+ *
293
+ */
294
+ Then(/^(I |we )*should see "([^"]*)" in the page tree$/, async function (pronounCase, title) {
295
+ await this.page.waitForSelector(SELECTORS.pageTree, { state: 'visible', timeout: 10000 });
296
+ const entry = this.page.locator('.cms-pagetree-jstree').getByText(title, { exact: false });
297
+ await entry.first().waitFor({ state: 'visible', timeout: 10000 });
298
+ });
299
+
300
+ // ---------------------------------------------------------------------------
301
+ // Welcome screen (fresh install, no pages yet)
302
+ // ---------------------------------------------------------------------------
303
+
304
+ /**
305
+ * Assert the django CMS "no pages yet" welcome screen is showing. django CMS
306
+ * renders it for the site root while the site has no pages at all, for a
307
+ * signed-in user: its script sends an anonymous visitor to the login form, so
308
+ * log in first.
309
+ *
310
+ * Example #1: Then I should see the django CMS welcome screen
311
+ * Example #2: Then we should see the django CMS welcome screen
312
+ * Example #3: And should see the django CMS welcome screen
313
+ *
314
+ */
315
+ Then(/^(I |we )*should see the django CMS welcome screen$/, async function (pronounCase) {
316
+ // The welcome wrapper itself can report no box of its own (its parts are positioned), so the
317
+ // assertion is on a part a visitor reads: the heading or a section.
318
+ await this.page.waitForSelector(SELECTORS.welcomeScreen, { state: 'attached', timeout: 10000 });
319
+ const part = this.page.locator('.cms-welcome-heading, .cms-welcome-section, #cms-welcome-config').first();
320
+ await part.waitFor({ state: 'visible', timeout: 10000 });
321
+ });