@webship-js/typo3-steps 0.0.0-stage → 2.0.0-alpha1

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,115 @@
1
- # Temporary Holding Version
1
+ # typo3-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
+ Cucumber step definitions for testing TYPO3 sites with
4
+ [webship-js](https://webship.co/docs/webship-js/2.0.x) — a BDD browser
5
+ automation harness built on Playwright + Cucumber-js.
6
+
7
+ `@webship-js/typo3-steps` adds a `typo3.steps.js` step file plus `.feature` files that
8
+ cover the main navigation (including the Bootstrap 5 "Content Examples"
9
+ mega-menu dropdown and the collapsed mobile navbar), page headings, and the
10
+ Bootstrap accordion content element of a TYPO3 install running the official
11
+ [`typo3/cms-introduction`](https://packagist.org/packages/typo3/cms-introduction)
12
+ demo content on the `bk2k/bootstrap_package` frontend theme.
13
+
14
+ > Published to [npmjs.com](https://www.npmjs.com/package/@webship-js/typo3-steps)
15
+ > as `@webship-js/typo3-steps`.
16
+
17
+ ## Requirements
18
+
19
+ - Node.js >= 20
20
+ - A webship-js 2.0 test project
21
+
22
+ ## Install
23
+
24
+ ```bash
25
+ npm install --save-dev @webship-js/typo3-steps
26
+ ```
27
+
28
+ ## Quick start
29
+
30
+ 1. Add `@webship-js/typo3-steps`'s step file to your webship-js `cucumber.js`
31
+ `require` array:
32
+
33
+ ```javascript
34
+ require: [
35
+ 'node_modules/webship-js/tests/step-definitions/**/*.js',
36
+ 'node_modules/@webship-js/typo3-steps/tests/step-definitions/typo3.steps.js',
37
+ 'tests/step-definitions/**/*.js',
38
+ ],
39
+ ```
40
+
41
+ 2. Point `launchUrl` at your TYPO3 site:
42
+
43
+ ```javascript
44
+ worldParameters: {
45
+ launchUrl: process.env.LAUNCH_URL || 'https://your-typo3-site.example',
46
+ },
47
+ ```
48
+
49
+ 3. Copy a feature file from [`tests/features/`](tests/features/) into your
50
+ project's `tests/features/` directory and adjust the menu items / page
51
+ titles to match your site's content.
52
+
53
+ 4. Run the suite:
54
+
55
+ ```bash
56
+ npx cucumber-js
57
+ ```
58
+
59
+ ## What's included
60
+
61
+ | Path | Purpose |
62
+ | --- | --- |
63
+ | `tests/step-definitions/typo3.steps.js` | TYPO3-specific Cucumber steps (main-menu navigation, mobile navbar, mega-menu dropdown, page-heading assertion, accordion content element). |
64
+ | `tests/features/*.feature` | Feature files exercising the steps against a locally DDEV-built `typo3/cms-introduction` site, desktop + mobile — see [`tests/features/README.md`](tests/features/README.md). |
65
+ | `.github/workflows/test.yml` | CI: builds a default TYPO3 site with [DDEV](https://ddev.com/), then runs the dry-run + full suite against it on every push/PR. |
66
+
67
+ ## Step catalogue (summary)
68
+
69
+ | Step | Type |
70
+ | --- | --- |
71
+ | `I open the "<name>" menu item` | When |
72
+ | `I should see the "<name>" menu item` | Then |
73
+ | `I open the (content examples )mega menu` | When |
74
+ | `I open the "<name>" dropdown link` | When |
75
+ | `I should see the "<name>" dropdown link` | Then |
76
+ | `I should be viewing the "<title>" page` | Then |
77
+ | `I open the "<label>" accordion panel` | When |
78
+ | `I should see "<phrase>" in the "<label>" accordion panel` | Then |
79
+
80
+ Full details and Gherkin examples for every step are documented inline in
81
+ [`tests/step-definitions/typo3.steps.js`](tests/step-definitions/typo3.steps.js)
82
+ and summarized in [`tests/step-definitions/README.md`](tests/step-definitions/README.md).
83
+
84
+ ## Running the tests in this repo
85
+
86
+ The CI workflow builds its own TYPO3 site with DDEV on every run (see
87
+ [`.github/workflows/test.yml`](.github/workflows/test.yml)). To run the same
88
+ suite locally:
89
+
90
+ ```bash
91
+ npm install
92
+
93
+ # Build a TYPO3 v13 site with the introduction package via DDEV
94
+ mkdir .typo3-site && cd .typo3-site
95
+ ddev config --project-type=typo3 --docroot=public --php-version=8.3 --project-name=typo3-intro --auto
96
+ ddev start
97
+ ddev composer create-project "typo3/cms-base-distribution:^13" . --no-interaction
98
+ ddev typo3 setup --server-type=other --driver=mysqli --host=db --port=3306 \
99
+ --dbname=db --username=db --password=db \
100
+ --admin-username=admin --admin-user-password='Introduction!23' \
101
+ --project-name="TYPO3 Introduction" --no-interaction --force
102
+ ddev composer require typo3/cms-introduction --no-interaction
103
+ # extension:setup activates the introduction package and auto-imports its
104
+ # demo content + site config — no separate import step needed.
105
+ ddev exec vendor/bin/typo3 extension:setup
106
+ ddev exec vendor/bin/typo3 cache:flush
107
+ cd ..
108
+
109
+ # Run the suite against the local DDEV site
110
+ LAUNCH_URL=https://typo3-intro.ddev.site npx cucumber-js
111
+ ```
112
+
113
+ ## License
114
+
115
+ MIT — see [LICENSE](LICENSE).
package/package.json CHANGED
@@ -1,6 +1,57 @@
1
1
  {
2
2
  "name": "@webship-js/typo3-steps",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "2.0.0-alpha1",
4
+ "description": "Cucumber step definitions for testing TYPO3 sites with webship-js.",
5
+ "main": "tests/step-definitions/typo3.steps.js",
6
+ "files": [
7
+ "tests/step-definitions/typo3.steps.js",
8
+ "tests/features",
9
+ "README.md",
10
+ "LICENSE"
11
+ ],
12
+ "scripts": {
13
+ "test": "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
+ "test:headed": "HEADLESS=false cucumber-js --config cucumber.js",
18
+ "generate-reports": "generate-reports"
19
+ },
20
+ "keywords": [
21
+ "typo3",
22
+ "webship-js",
23
+ "cucumber",
24
+ "cucumber-js",
25
+ "playwright",
26
+ "bdd",
27
+ "testing",
28
+ "e2e",
29
+ "gherkin"
30
+ ],
31
+ "author": "Webship.co",
32
+ "license": "MIT",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/webship/typo3-steps.git"
36
+ },
37
+ "bugs": {
38
+ "url": "https://github.com/webship/typo3-steps/issues"
39
+ },
40
+ "homepage": "https://github.com/webship/typo3-steps#readme",
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "engines": {
45
+ "node": ">= 20.0"
46
+ },
47
+ "peerDependencies": {
48
+ "webship-js": "^2.0.6"
49
+ },
50
+ "devDependencies": {
51
+ "tsx": "^4.23.0",
52
+ "webship-js": "^2.0.6"
53
+ },
54
+ "overrides": {
55
+ "@cucumber/cucumber": "^12"
56
+ }
57
+ }
@@ -0,0 +1,18 @@
1
+ # Features
2
+
3
+ Gherkin feature files exercising a locally DDEV-built TYPO3 v13 site running
4
+ the official `typo3/cms-introduction` demo content with the
5
+ `bk2k/bootstrap_package` frontend theme.
6
+
7
+ | Feature file | Covers |
8
+ | --- | --- |
9
+ | `homepage.feature` | Home page loads with its hero heading and the top-level main-menu items are present (desktop + mobile). |
10
+ | `main-navigation.feature` | Navigating to the top-level pages (Features, Customize, Pages) and back to the homepage via the main menu (desktop + mobile). |
11
+ | `content-examples-mega-menu.feature` | Opening the "Content Examples" mega-menu dropdown and reaching a nested content-example page (Quote, Accordion) through it (desktop + mobile). |
12
+ | `accordion.feature` | Expanding the Bootstrap accordion panels on the Accordion content-example page and reading each panel's content (desktop + mobile). |
13
+
14
+ Scenarios tagged `@mobile` set the `xs` breakpoint (375px) so the navbar
15
+ collapses behind the Bootstrap `.navbar-toggler`.
16
+
17
+ Custom TYPO3 steps live in `../step-definitions/typo3.steps.js`; everything
18
+ else uses webship-js's built-in step vocabulary.
@@ -0,0 +1,31 @@
1
+ Feature: Accordion content element
2
+ As a visitor
3
+ I want to expand the accordion panels on the Accordion content-example page
4
+ So that I can read each panel's content without leaving the page
5
+
6
+ Background:
7
+ Given I am on "/content-examples/interactive/accordion"
8
+
9
+ Scenario: The accordion page loads with its panels
10
+ Then I should be viewing the "Accordions" page
11
+ And I should see "Media Left"
12
+ And I should see "Media Right"
13
+ And I should see "No Media"
14
+
15
+ Scenario: Expand a panel and read its content
16
+ When I open the "Media Left" accordion panel
17
+ Then I should see "short video" in the "Media Left" accordion panel
18
+
19
+ Scenario: Expand another panel and read its content
20
+ When I open the "Media Right" accordion panel
21
+ Then I should see "meanest place" in the "Media Right" accordion panel
22
+
23
+ Scenario: Expand the text-only panel and read its content
24
+ When I open the "No Media" accordion panel
25
+ Then I should see "Lorem ipsum" in the "No Media" accordion panel
26
+
27
+ @mobile
28
+ Scenario: Expand a panel on a mobile viewport
29
+ When I set the viewport to the "xs" breakpoint
30
+ And I open the "No Media" accordion panel
31
+ Then I should see "Lorem ipsum" in the "No Media" accordion panel
@@ -0,0 +1,34 @@
1
+ Feature: Content Examples mega menu
2
+ As a visitor
3
+ I want to reach a content-example page through the "Content Examples" mega menu
4
+ So that I can browse the individual content-element demos
5
+
6
+ Scenario: The mega menu lists content-example links
7
+ Given I am on the homepage
8
+ When I open the mega menu
9
+ Then I should see the "Accordion" dropdown link
10
+ And I should see the "Quote" dropdown link
11
+ And I should see the "Gallery" dropdown link
12
+
13
+ Scenario: Open the Quote page through the mega menu
14
+ Given I am on the homepage
15
+ When I open the mega menu
16
+ And I open the "Quote" dropdown link
17
+ Then I should be on "/content-examples/text/quote"
18
+ And I should be viewing the "Quote" page
19
+
20
+ Scenario: Open the Accordion page through the mega menu
21
+ Given I am on the homepage
22
+ When I open the mega menu
23
+ And I open the "Accordion" dropdown link
24
+ Then I should be on "/content-examples/interactive/accordion"
25
+ And I should be viewing the "Accordions" page
26
+
27
+ @mobile
28
+ Scenario: Open a content-example page through the mega menu on a mobile viewport
29
+ Given I am on the homepage
30
+ When I set the viewport to the "xs" breakpoint
31
+ And I reload the page
32
+ And I open the mega menu
33
+ And I open the "Quote" dropdown link
34
+ Then I should be on "/content-examples/text/quote"
@@ -0,0 +1,25 @@
1
+ Feature: Home page
2
+ As a visitor
3
+ I want the TYPO3 Introduction home page to load with its key content and navigation
4
+ So that I know the site is working before I explore further
5
+
6
+ Background:
7
+ Given I am on the homepage
8
+
9
+ Scenario: Home page loads with its hero heading
10
+ Then I should be viewing the "enterprise CMS" page
11
+ And I should see "Possibilities"
12
+
13
+ Scenario: Main menu shows the top-level items
14
+ Then I should see the "Home" menu item
15
+ And I should see the "Features" menu item
16
+ And I should see the "Customize" menu item
17
+ And I should see the "Pages" menu item
18
+ And I should see the "Content Examples" menu item
19
+
20
+ @mobile
21
+ Scenario: Main menu is reachable on a mobile viewport
22
+ When I set the viewport to the "xs" breakpoint
23
+ And I reload the page
24
+ Then I should see the "Features" menu item
25
+ And I should see the "Content Examples" menu item
@@ -0,0 +1,37 @@
1
+ Feature: Main navigation
2
+ As a visitor
3
+ I want to move between the site's top-level pages via the main menu
4
+ So that I can reach the content I'm looking for
5
+
6
+ Scenario: Open the Features page from the main menu
7
+ Given I am on the homepage
8
+ When I open the "Features" menu item
9
+ Then I should be on "/features"
10
+ And I should be viewing the "feature set" page
11
+
12
+ Scenario: Open the Customize page from the main menu
13
+ Given I am on the homepage
14
+ When I open the "Customize" menu item
15
+ Then I should be on "/customize"
16
+ And I should be viewing the "Incredible flexible" page
17
+
18
+ Scenario: Open the Pages page from the main menu
19
+ Given I am on the homepage
20
+ When I open the "Pages" menu item
21
+ Then I should be on "/pages"
22
+ And I should be viewing the "Page Layouts" page
23
+
24
+ Scenario: Navigate back to the homepage after visiting another page
25
+ Given I am on the homepage
26
+ When I open the "Features" menu item
27
+ Then I should be on "/features"
28
+ When I go to the homepage
29
+ Then I should be on the homepage
30
+
31
+ @mobile
32
+ Scenario: Open a top-level page on a mobile viewport
33
+ Given I am on the homepage
34
+ When I set the viewport to the "xs" breakpoint
35
+ And I reload the page
36
+ And I open the "Pages" menu item
37
+ Then I should be on "/pages"
@@ -0,0 +1,25 @@
1
+ # Step Definitions
2
+
3
+ Custom TYPO3 step definitions layered on top of webship-js's built-in
4
+ vocabulary. They target a locally DDEV-built TYPO3 v13 site running the
5
+ official `typo3/cms-introduction` demo content with the
6
+ `bk2k/bootstrap_package` frontend theme.
7
+
8
+ `typo3.steps.js` adds the steps that webship-js's generic
9
+ click / fill / see vocabulary does not cover:
10
+
11
+ | Step | Purpose |
12
+ | --- | --- |
13
+ | `When I open the "<name>" menu item` | Click a top-level main-navigation link (Home, Features, Customize, Pages, Content Examples). Expands the collapsed navbar first on mobile. |
14
+ | `Then I should (not )see the "<name>" menu item` | Assert a top-level main-navigation link is / is not present. |
15
+ | `When I open the (content examples )mega menu` | Open the "Content Examples" Bootstrap 5 mega-menu dropdown via its toggle button. |
16
+ | `When I open the "<name>" dropdown link` | Click a nested link inside the open mega-menu dropdown (Quote, Accordion, Gallery, …). |
17
+ | `Then I should (not )see the "<name>" dropdown link` | Assert a nested mega-menu link is / is not present. |
18
+ | `Then I should be viewing the "<title>" page` | Assert the current page's hero (`h1.carousel-header`) or content heading (`h1.element-header`) includes the given text. |
19
+ | `When I open the "<label>" accordion panel` | Expand a Bootstrap accordion panel by its header-button label. |
20
+ | `Then I should see "<phrase>" in the "<label>" accordion panel` | Assert the expanded accordion panel's body is visible and contains the phrase. |
21
+
22
+ Every step supports the `(I |we )*` pronoun prefix, carries a JSDoc block
23
+ with Gherkin examples, and wraps risky locator actions in a friendly
24
+ try/catch so a missing selector reports a clear hint instead of a raw
25
+ Playwright timeout. Waits use webship-js's `smartSettle` — no static sleeps.
@@ -0,0 +1,257 @@
1
+ 'use strict';
2
+
3
+ // TYPO3-specific step definitions for a locally DDEV-built TYPO3 v13 site
4
+ // running the official `typo3/cms-introduction` demo content with the
5
+ // `bk2k/bootstrap_package` frontend theme.
6
+ //
7
+ // Covers: the Bootstrap 5 main navigation (top-level links, the
8
+ // "Content Examples" mega-menu dropdown and its nested links, and the
9
+ // collapsed mobile navbar), the per-page hero / element heading, and the
10
+ // Bootstrap accordion on the "Content Examples > Interactive > Accordion"
11
+ // page. Steps already covered by webship-js's generic
12
+ // click / fill / see vocabulary are not duplicated here — see README.md.
13
+ //
14
+ // All locator interactions are wrapped in a friendly try/catch so a missing
15
+ // selector reports a clear TYPO3-flavoured hint instead of a raw Playwright
16
+ // timeout stack.
17
+
18
+ const { When, Then } = require('@cucumber/cucumber');
19
+ const assert = require('assert');
20
+ const { smartSettle } = require('webship-js/tests/step-definitions/webship');
21
+
22
+ /**
23
+ * Run a locator action and rethrow with a short, friendly explanation on
24
+ * failure instead of Playwright's raw timeout stack — keeps failures
25
+ * readable when the site's markup changes.
26
+ */
27
+ async function actOrExplain(label, target, fn) {
28
+ try {
29
+ return await fn();
30
+ } catch (err) {
31
+ throw new Error(`Could not ${label} "${target}": ${err.message.split('\n')[0]}`);
32
+ }
33
+ }
34
+
35
+ /**
36
+ * On narrow (mobile) viewports the whole navbar collapses behind the
37
+ * Bootstrap `.navbar-toggler`. Expand it first if the nav is hidden so
38
+ * nav links become clickable — a no-op when the nav is already open (or
39
+ * at desktop widths where the toggler is hidden).
40
+ */
41
+ async function ensureNavExpanded(page) {
42
+ const nav = page.locator('#mainnavigation').first();
43
+ if (await nav.isVisible().catch(() => false)) return;
44
+ const toggler = page.locator('.navbar-toggler').first();
45
+ if (await toggler.isVisible().catch(() => false)) {
46
+ await toggler.click({ force: true }).catch(() => {});
47
+ await nav.waitFor({ state: 'visible', timeout: 10000 }).catch(() => {});
48
+ await smartSettle(page, 500);
49
+ }
50
+ }
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // Main navigation — top-level links
54
+ // ---------------------------------------------------------------------------
55
+
56
+ /**
57
+ * Click a top-level main navigation link (`.nav-link-main`) by its visible
58
+ * text — e.g. Home, Features, Customize, Pages, Content Examples. On a
59
+ * mobile viewport the collapsed navbar is expanded first.
60
+ *
61
+ * Example #1: When I open the "Features" menu item
62
+ * Example #2: When I open the "Customize" menu item
63
+ * Example #3: When we open the "Pages" menu item
64
+ * Example #4: And I open the "Content Examples" menu item
65
+ * Example #5: When I open the "Home" menu item
66
+ *
67
+ */
68
+ When(/^(I |we )*open the "([^"]*)?" menu item$/, async function (pronounCase, name) {
69
+ const page = this.page;
70
+ await ensureNavExpanded(page);
71
+ const link = page
72
+ .locator('#mainnavigation a.nav-link-main .nav-link-text', { hasText: name })
73
+ .first();
74
+ await actOrExplain('open menu item', name, async () => {
75
+ await link.waitFor({ state: 'visible', timeout: 10000 });
76
+ await link.click();
77
+ });
78
+ await smartSettle(page, 1000);
79
+ });
80
+
81
+ /**
82
+ * Assert a top-level main navigation link with the given text is (or is
83
+ * not) present in the main navigation. On a mobile viewport the collapsed
84
+ * navbar is expanded first so its links are rendered.
85
+ *
86
+ * Example #1: Then I should see the "Features" menu item
87
+ * Example #2: And I should see the "Content Examples" menu item
88
+ * Example #3: Then we should see the "Pages" menu item
89
+ * Example #4: And I should not see the "Checkout" menu item
90
+ * Example #5: Then should see the "Customize" menu item
91
+ *
92
+ */
93
+ Then(/^(I |we )*should( not)* see the "([^"]*)?" menu item$/, async function (pronounCase, notCase, name) {
94
+ await ensureNavExpanded(this.page);
95
+ const count = await this.page
96
+ .locator('#mainnavigation a.nav-link-main .nav-link-text', { hasText: name })
97
+ .count();
98
+ if (notCase) {
99
+ assert.ok(count === 0, `Expected no "${name}" menu item, but found ${count}.`);
100
+ } else {
101
+ assert.ok(count > 0, `Expected a "${name}" menu item, but found none.`);
102
+ }
103
+ });
104
+
105
+ // ---------------------------------------------------------------------------
106
+ // Main navigation — "Content Examples" mega-menu dropdown
107
+ // ---------------------------------------------------------------------------
108
+
109
+ /**
110
+ * Open the "Content Examples" mega-menu dropdown by clicking its Bootstrap
111
+ * toggle button (`.nav-link-toggle[data-bs-toggle="dropdown"]`). The
112
+ * dropdown is JS-driven (Bootstrap 5), not hover/CSS-only, so an explicit
113
+ * click is required before its nested links can be reached. On a mobile
114
+ * viewport the collapsed navbar is expanded first.
115
+ *
116
+ * Example #1: When I open the mega menu
117
+ * Example #2: When we open the mega menu
118
+ * Example #3: And I open the mega menu
119
+ * Example #4: When I open the content examples mega menu
120
+ * Example #5: And we open the mega menu
121
+ *
122
+ */
123
+ When(/^(I |we )*open the (content examples )*mega menu$/, async function (pronounCase, contentCase) {
124
+ const page = this.page;
125
+ await ensureNavExpanded(page);
126
+ const toggle = page.locator('#mainnavigation .nav-style-mega .nav-link-toggle').first();
127
+ await actOrExplain('open mega menu', 'Content Examples', async () => {
128
+ await toggle.waitFor({ state: 'visible', timeout: 10000 });
129
+ await toggle.click();
130
+ });
131
+ await smartSettle(page, 500);
132
+ });
133
+
134
+ /**
135
+ * Click a nested link inside the open "Content Examples" mega-menu dropdown
136
+ * (`.dropdown-menu-mega a.nav-link-dropdown`) by its visible text — e.g.
137
+ * Quote, Accordion, Gallery, Table. Open the mega menu first with
138
+ * "I open the mega menu".
139
+ *
140
+ * Example #1: When I open the "Accordion" dropdown link
141
+ * Example #2: When I open the "Quote" dropdown link
142
+ * Example #3: When we open the "Gallery" dropdown link
143
+ * Example #4: And I open the "Table" dropdown link
144
+ * Example #5: When I open the "Carousel" dropdown link
145
+ *
146
+ */
147
+ When(/^(I |we )*open the "([^"]*)?" dropdown link$/, async function (pronounCase, name) {
148
+ const page = this.page;
149
+ const link = page
150
+ .locator('.dropdown-menu-mega a.nav-link-dropdown', { hasText: name })
151
+ .first();
152
+ await actOrExplain('open dropdown link', name, async () => {
153
+ await link.waitFor({ state: 'visible', timeout: 10000 });
154
+ await link.click();
155
+ });
156
+ await smartSettle(page, 1000);
157
+ });
158
+
159
+ /**
160
+ * Assert a nested link with the given text is (or is not) present in the
161
+ * open "Content Examples" mega-menu dropdown. Open the mega menu first.
162
+ *
163
+ * Example #1: Then I should see the "Accordion" dropdown link
164
+ * Example #2: And I should see the "Quote" dropdown link
165
+ * Example #3: Then we should see the "Gallery" dropdown link
166
+ * Example #4: And I should not see the "Recipes" dropdown link
167
+ * Example #5: Then should see the "Table" dropdown link
168
+ *
169
+ */
170
+ Then(/^(I |we )*should( not)* see the "([^"]*)?" dropdown link$/, async function (pronounCase, notCase, name) {
171
+ const count = await this.page
172
+ .locator('.dropdown-menu-mega a.nav-link-dropdown', { hasText: name })
173
+ .count();
174
+ if (notCase) {
175
+ assert.ok(count === 0, `Expected no "${name}" dropdown link, but found ${count}.`);
176
+ } else {
177
+ assert.ok(count > 0, `Expected a "${name}" dropdown link, but found none.`);
178
+ }
179
+ });
180
+
181
+ // ---------------------------------------------------------------------------
182
+ // Page heading
183
+ // ---------------------------------------------------------------------------
184
+
185
+ /**
186
+ * Assert the current page's main heading equals or includes the given
187
+ * title. Landing-style pages render their heading in the `h1.carousel-header`
188
+ * hero; content-example detail pages (e.g. Accordion, Quote) render it in
189
+ * `h1.element-header`. Whichever is present first is read.
190
+ *
191
+ * Example #1: Then I should be viewing the "Accordions" page
192
+ * Example #2: And I should be viewing the "Quote" page
193
+ * Example #3: Then we should be viewing the "Page Layouts" page
194
+ * Example #4: Then I should be viewing the "Incredible flexible" page
195
+ * Example #5: And should be viewing the "Content Management Made Easy" page
196
+ *
197
+ */
198
+ Then(/^(I |we )*should be viewing the "([^"]*)?" page$/, async function (pronounCase, title) {
199
+ const heading = this.page.locator('h1.carousel-header, h1.element-header, main h1, h1').first();
200
+ const text = (await actOrExplain('read the page heading for', title, () => heading.innerText())).trim();
201
+ assert.ok(text.includes(title), `Expected the page heading to include "${title}", but it was "${text}".`);
202
+ });
203
+
204
+ // ---------------------------------------------------------------------------
205
+ // Bootstrap accordion (Content Examples > Interactive > Accordion)
206
+ // ---------------------------------------------------------------------------
207
+
208
+ /**
209
+ * Expand a Bootstrap accordion panel (`.accordion-item`) by clicking its
210
+ * header button (`.accordion-button`) matching the given label. The button
211
+ * toggles the associated `.accordion-collapse`, revealing its body.
212
+ *
213
+ * Example #1: When I open the "Media Left" accordion panel
214
+ * Example #2: When I open the "Media Right" accordion panel
215
+ * Example #3: When we open the "Media Top" accordion panel
216
+ * Example #4: And I open the "No Media" accordion panel
217
+ * Example #5: When I open the "Media Bottom" accordion panel
218
+ *
219
+ */
220
+ When(/^(I |we )*open the "([^"]*)?" accordion panel$/, async function (pronounCase, label) {
221
+ const button = this.page
222
+ .locator('.accordion-item .accordion-button', { hasText: label })
223
+ .first();
224
+ await actOrExplain('open accordion panel', label, async () => {
225
+ await button.waitFor({ state: 'visible', timeout: 10000 });
226
+ await button.click();
227
+ });
228
+ await smartSettle(this.page, 1000);
229
+ });
230
+
231
+ /**
232
+ * Assert the body of the accordion panel with the given label is visible
233
+ * and contains the given phrase. Locates the panel via its header button,
234
+ * then reads the `.accordion-body` inside its `.accordion-item`. Open the
235
+ * panel first with "I open the ... accordion panel".
236
+ *
237
+ * Example #1: Then I should see "short video" in the "Media Left" accordion panel
238
+ * Example #2: And I should see "meanest place" in the "Media Right" accordion panel
239
+ * Example #3: Then we should see "Lorem ipsum" in the "No Media" accordion panel
240
+ * Example #4: Then I should see "larger version" in the "Media Top" accordion panel
241
+ * Example #5: And should see "TYPO3" in the "Media Left" accordion panel
242
+ *
243
+ */
244
+ Then(/^(I |we )*should see "([^"]*)?" in the "([^"]*)?" accordion panel$/, async function (pronounCase, phrase, label) {
245
+ const item = this.page
246
+ .locator('.accordion-item', { has: this.page.locator('.accordion-button', { hasText: label }) })
247
+ .first();
248
+ const body = item.locator('.accordion-body').first();
249
+ await actOrExplain('read the accordion panel', label, () =>
250
+ body.waitFor({ state: 'visible', timeout: 10000 })
251
+ );
252
+ const text = (await body.innerText()).trim();
253
+ assert.ok(
254
+ text.includes(phrase),
255
+ `Expected the "${label}" accordion panel to include "${phrase}", but it was "${text}".`
256
+ );
257
+ });