@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 +21 -0
- package/README.md +114 -2
- package/package.json +55 -4
- package/tests/features/README.md +18 -0
- package/tests/features/accordion.feature +31 -0
- package/tests/features/content-examples-mega-menu.feature +34 -0
- package/tests/features/homepage.feature +25 -0
- package/tests/features/main-navigation.feature +37 -0
- package/tests/step-definitions/README.md +25 -0
- package/tests/step-definitions/typo3.steps.js +257 -0
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
|
-
#
|
|
1
|
+
# typo3-steps
|
|
2
2
|
|
|
3
|
-
|
|
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": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
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
|
+
});
|