digitalist-se / behat-tools
Behat tools
Package info
github.com/digitalist-se/behat-tools
Type:package
pkg:composer/digitalist-se/behat-tools
Requires
- php: >=7.4
- ext-curl: *
- behat/behat: ^3.0.13
- behat/mink-extension: ^2.3.1
Requires (Dev)
- behat/mink-goutte-driver: ^1.1
- digtitalist-se/behat-tools: dev-master
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-master
- v2.1.0
- v2.0.12
- v2.0.11
- v2.0.10
- v2.0.9
- v2.0.8
- v2.0.7
- 2.0.6
- v2.0.5
- 2.0.4
- 2.0.3
- 2.0.2
- 2.0.1
- 2.0.0
- 1.0.15
- 1.0.14
- 1.0.13
- 1.0.12
- 1.0.11
- 1.0.10
- 1.0.9
- 1.0.8
- 1.0.7
- 1.0.6
- 1.0.5
- 1.0.4
- 1.0.3
- 1.0.2
- 1.0.1
- 1.0.0
- v0.0.4
- v0.0.3
- v0.0.2
- v0.0.1
- dev-ckeditor5-dropdowns-and-field-steps
- dev-dedupe-wysiwyg-step
- dev-ckeditor5-context
- dev-process-entity-fields
This package is auto-updated.
Last update: 2026-10-16 12:42:44 UTC
README
Behat related tools
Set up
Add this to the repositories section of your composer.json
{
"type": "vcs",
"url": "https://github.com/digitalist-se/behat-tools"
},
Execute:
composer require --dev digitalist-se/behat-tools
Add to your behat.yml:
default: suites: default: contexts: - digitalistse\BehatTools\Context\CommonContext
Add more Contexts depending on your needs using the same structure.
Entity Context
You can set the format of the dates for each field in the behat.yml like this:
default: suites: default: parameters: entity_context: datetime_format: announcement: publish_date: 'Y-m-d' unpublish_date: 'Y-m-d' announcement_tracker: created_date: 'Y-m-d\TH:i:s' sent_date: 'Y-m-d\TH:i:s' read_date: 'Y-m-d\TH:i:s'
In that case you could use relative date in php format like:
Given a "license_tracker" entity exists with the properties: | label | status | uid:user:mail | field_license:license:title | created | activated | expire | first_notification | second_notification | service_requirement_expired | | TRK10 | 1 | license-01@test.com | Test license 1 | 6 month ago | 6 month ago | 91 days | tomorrow | 61 days | tomorrow | | TRK10 | 1 | license-02@test.com | Test license 1 | 6 month ago | 6 month ago | 90 days | today | 60 days | tomorrow | | TRK10 | 1 | license-03@test.com | Test license 1 | 6 month ago | 6 month ago | 89 days | yesterday | 59 days | tomorrow |
Entity fields support
Entity fields are supported like in the following example: (<field_type>) <field_name>.<field_property>
- Field with simple structure (e.g.: boolean, textfield, etc.):
field_archived.value - Field type with more complex structure:
(daterange) field_date.value
NOTE: Only
daterangetype is supported at the moment. If you need to support more complex fields you can add processing toEntityContext::processEntityFields.
Example using entity fields and properties
Given a "license_tracker" entity exists with the properties: | label | status | uid:user:mail | field_archived.value | (daterange) field_date.value | (daterange) field_date.end_value | | TRK10 | 1 | license-01@test.com | 1 | 6 month ago | 6 month ago | | TRK10 | 1 | license-02@test.com | 0 | 6 month ago | 6 month ago | | TRK10 | 1 | license-03@test.com | 1 | 6 month ago | 6 month ago |
Screenshot Context
The ScreenshotContext needs display sizes declared in your behat.yml under the suite settings key screenshot_context. Define a desktop size and any number of named mobile devices to ensure predictable, comparable screenshots.
Recommended configuration:
default: suites: default: contexts: - digitalistse\BehatTools\Context\ScreenshotContext screenshot_context: screenshot_path: '%paths.base%/screenshots-build' # required desktop_size: # strongly recommended width: 1920 height: 1080 mobile_devices: # optional, add any devices you want iphone8: width: 375 height: 667 ipad: width: 768 height: 1024 desktop_subfolder: 'desktop' # optional (default: desktop) mobile_subfolder: 'mobile' # optional (default: mobile) horizontal_overflow: fail # optional: fail (default) | warn | off
Notes:
- One desktop screenshot is always taken using
desktop_size(defaults to 1920x1080 if not set). - One additional screenshot is taken per entry in
mobile_devices, saved under themobile_subfolderusing the device name in the filename. - If no
mobile_devicesare configured, only the desktop screenshot is produced. - Mobile devices are rendered with Chrome's device emulation (
Emulation.setDeviceMetricsOverridevia chromedriver), not by resizing the window. A headed Chrome refuses windows narrower than about 500px, so resizing silently produced 500px "mobile" captures. Drivers other than chromedriver fall back to resizing. Projects upgrading from a resize-based version get mobile captures at the configured width and need to re-baseline them once. - Every captured size is checked for horizontal overflow: when the page is wider than the viewport the step fails after the screenshots were written, naming the outermost elements that stick out (elements inside an
overflow-x: auto|scroll|hiddenancestor are ignored). Configure withhorizontal_overflow: fail(default),warn(print only) oroff. Then the page should not overflow horizontallyruns the same check at the current window size without taking a screenshot.
Legacy configuration (backwards compatibility):
default: suites: default: screenshot_context: screenshot_path: '%paths.base%/screenshots-build' display_sizes: desktop: width: 1920 height: 1080 mobile: width: 375 height: 667
When using display_sizes, the desktop size is read from display_sizes.desktop, and a single mobile device named mobile is inferred from display_sizes.mobile. Other keys (e.g. tablet) are ignored by the current implementation. Prefer the recommended desktop_size + mobile_devices format for multiple devices.
CKEditor5Context
Drives CKEditor 5 on Drupal text fields in a real browser (@javascript
scenarios). Add it to behat.yml:
- digitalistse\BehatTools\Context\CKEditor5Context
Fields are addressed by the locator in Drupal's form item wrapper, so body
means div.js-form-item-body-0-value; a paragraph field looks like
field-sections-0-subform-field-text. Toolbar buttons are addressed by their
English label and matched against the editor's own translation, so Bold
finds Fet on a Swedish UI.
| Step | What it does |
|---|---|
I fill in wysiwyg on field "body" with "<p>Text</p>" |
Sets the editor content with setData(), bypassing the UI. Defined in CommonContext, so it is available without this context. |
the wysiwyg on field "body" should use the "basic_html" format |
Asserts the active text format. |
the wysiwyg toolbar on field "body" should have the items "heading, bold, link" |
Compares the configured toolbar items (without separators) with the registered CKEditor components and the rendered buttons. CKEditor only console-warns when a plugin fails to load, so this is the check that catches a silently missing button. |
I select all text in the wysiwyg on field "body" |
Selects the whole document in the editor model. |
the wysiwyg on field "body" should be ready |
Waits up to ten seconds for the editor instance, which attaches after the field's AJAX load. |
I press the "Bold" button in the wysiwyg toolbar on field "body" |
Real click on a toolbar button found by its English label. Dropdowns such as Heading are found by their tooltip, since their label shows the current value. |
I choose "Heading 2" from the heading dropdown in the wysiwyg toolbar on field "body" |
Opens the heading dropdown and clicks the entry, matched by its text or its ck-heading_heading2 class. |
I switch the text format of field "body" to "full_html" |
Changes the text format select, confirms Drupal's dialog when the field has content, and waits for the new editor. |
I move the cursor to the end of the wysiwyg on field "body" |
Appends an empty paragraph after the last block and puts the cursor there, so an insert lands after the content. |
I blur the wysiwyg on field "body" |
Closes balloons, collapses the selection and blurs the editor, so a screenshot shows no transient UI. |
the wysiwyg on field "body" should contain '<strong>Text</strong>' |
Substring check on editor.getData(), retried for five seconds. Single quotes, so HTML attributes can be asserted. |
I submit the wysiwyg link form with "https://example.com/" |
After pressing Link: types the URL into the URL input (not the "displayed text" input) and submits with Enter. |
I insert the image "/core/misc/druplicon.png" through the wysiwyg toolbar on field "body" |
Presses Insert image and submits the by-URL form, for formats with image upload disabled. |
I upload the image "images/photo.jpg" through the wysiwyg toolbar on field "body" |
For formats with image upload enabled: puts the file, relative to the Mink files_path, into the toolbar's file input and waits for Drupal to store it. |
the wysiwyg source editing area on field "body" should contain '<strong>' |
After pressing Source: checks the source textarea. |
Balloon forms are submitted with Enter or a scripted click on purpose. A
WebDriver click inside a balloon scrolls the page to the top, the balloon
target leaves the viewport and CKEditor parks the balloon off screen. Typing
into balloon inputs uses raw WebDriver keystrokes because Mink's setValue()
blurs the field afterwards, which closes the balloon.
Example, on a site where editors use restricted_html:
@api @javascript
Scenario: Toolbar buttons format text and the formatting survives saving
Given I am logged in as a user with the "editor" role
And I am on "/node/add/page"
Then the wysiwyg toolbar on field "body" should have the items "heading, bold, italic, link, sourceEditing"
When I fill in wysiwyg on field "body" with "<p>Toolbar test text</p>"
And I select all text in the wysiwyg on field "body"
And I press the "Bold" button in the wysiwyg toolbar on field "body"
Then the wysiwyg on field "body" should contain '<strong>Toolbar test text</strong>'
When I press the "Link" button in the wysiwyg toolbar on field "body"
And I submit the wysiwyg link form with "https://example.com/"
Then the wysiwyg on field "body" should contain '<a href="https://example.com/">'