cypress-json-tests
Cypress JSON Tests
A Cypress-based testing framework specifically designed for Drupal projects. This tool allows you to define end-to-end (E2E) workflows and HTTP status code validation tests entirely through a single JSON/JSON5 configuration file, without needing to write custom Cypress JavaScript code for most common test scenarios.
Features
- JSON / JSON5-Driven Testing: Define your test cases, UI interactions, and assertions entirely in a
config.json5(orconfig.json) file. Supports JSON5 features such as single-line and multi-line comments, trailing commas, and single-quoted strings. - Drupal Integration: Built-in commands to interact with Drupal via Drush (supports DDEV, Lando, and Ahoy environments). Includes automated user login, blocking/unblocking, and role management.
- Status Code Validation: Easily verify that specific URLs return expected HTTP status codes for different user roles (e.g., ensuring anonymous users get 403 on admin pages).
- UI Workflow Validation: Simulate complex user journeys including filling forms, interacting with CKEditor 5, changing moderation states, validating text and meta tags, and managing content creation/deletion.
- Resilient Execution: Each scenario runs as an individual test so a failure does not halt the entire suite (though workflow scenarios may be order-coupled when sharing content such as
/testing-page). Status checks evaluate all URLs in a list and report all mismatches together rather than stopping at the first failure. - Consolidated Reporting: Generates a formatted summary report in the console upon completion and outputs detailed report artifacts (
reports/validation-report.mdandreports/validation-report.json).
Installation
Install the package as a development dependency alongside Cypress (requires Cypress >=15.10.0):
npm install cypress-json-tests --save-dev
Running Cypress Tests
You can run the tests directly from your project root without extracting or copying files from node_modules. Point the runner to your project's fixtures folder containing config.json5 or config.json, set your site's baseUrl, and optionally specify your local environment's Drush prefix (e.g. ddev, lando, ahoy):
Interactive Mode (GUI)
npx cypress-json-tests open --config "fixturesFolder=./fixtures,baseUrl=https://example.com" --expose drush_prefix=ddev
Headless Mode (CLI)
npx cypress-json-tests run --config "fixturesFolder=./fixtures,baseUrl=https://example.com" --expose drush_prefix=ddev
Options Reference:
fixturesFolder: Relative or absolute path to the folder containing yourconfig.json5orconfig.jsonfile.baseUrl: The base URL of your target Drupal site (replacehttps://example.comwith your actual site URL).--expose drush_prefix=<env>: (Optional) Environment wrapper to prefix Drush commands (ddev,lando,ahoy). Omit ifdrushis available directly on your hostPATH.
Hot Reloading in Interactive Mode (cypress open)
The test runner watches your fixtures folder for changes and syncs the configuration automatically:
- Step & check modifications: Edits to scenario steps, URLs, form actions, and check assertions in
config.json5(orconfig.json) reload and re-run automatically upon saving. - Scenario list changes: Adding, renaming, or removing top-level scenarios is synchronized via a local cache file required by the spec runner, allowing Mocha to register updated scenarios immediately upon saving.
- Parse errors: A missing or invalid config file fails the run with the parser message rather than silently re-running the last good configuration.
Configuration Format (config.json5 / config.json)
Create a config.json5 (or config.json) file in your fixtures folder to define your test suite. JSON5 syntax is supported, allowing comments and trailing commas. Example structure:
{
"skip_antibot": false,
"users": {
"author": "author",
"approver": "approver",
"structure": "structure",
"site_administrator": "site-administrator"
},
"scenarios": {
"Anonymous user status": [
{
"url": "/",
"role": "anonymous",
"status": 200
},
{
"url": "/admin",
"role": "anonymous",
"status": 403
},
{
"url": "/zzz",
"role": "anonymous",
"status": 404
}
],
"Structure user status": [
{
"url": "/admin/structure/taxonomy",
"role": "structure",
"status": 200
}
],
"Article URL": [
{
"url": "/node/add/article",
"role": "author",
"action": {
"fill_values": {
"#edit-title-0-value": "Testing article",
"#edit-field-summary-0-value": "Test article summary."
},
"select_values": {
"#edit-field-article-type": "429"
},
"click": [
"#edit-submit"
]
}
},
{
"clean_this": true,
"url": "/news/testing-article",
"role": "author",
"checks": [
{
"contain": {
"h1": "Testing article"
}
},
{
"meta": {
"title": "Testing article | Drupal 11",
"description": "Test article summary."
}
}
]
}
],
"Workflow: Changing Page State": [
{
"name": "Open edit page",
"clean_this": true,
"url": "/testing-page",
"role": "author",
"action": {
"click": [
".page__admin .btn:contains('Edit')"
]
}
},
{
"name": "Update moderation state and menu settings",
"role": "author",
"action": {
"select_values": {
"#edit-moderation-state-0-state": "needs_review"
},
"click": [
"#edit-menu",
"#edit-menu-enabled",
"#edit-submit"
]
}
}
]
}
}
Top-Level Properties:
skip_antibot: Boolean. Iftrue, grants the"skip antibot"permission to the anonymous role before testing and removes it during cleanup.users: Key-value map of role identifiers to actual Drupal usernames (e.g."site_administrator": "site-administrator").- Account Lifecycle: In the test setup (
before()hook), all mapped accounts are automatically unblocked via Drush (cy.unblockUserByUsername()) and Two-Factor Authentication (TFA) settings are cleared if thetfamodule is active. Once all tests complete (after()hook), all test accounts are re-blocked via Drush (cy.blockUserByUsername()). - Session Authentication: During tests, Cypress logs in as the mapped username using Drush and caches the session via
cy.session(). Subsequent logins for that role are restored instantly from cache.
- Account Lifecycle: In the test setup (
scenarios: Map of validation scenario names to arrays of step objects.
Core Data Structures & Concepts
The test suite is structured around four primary concepts: Scenario, Request, Action, and Check.
Scenario (Array of Requests)
└── Request (Single navigation, probe, or page interaction)
├── Action (User input, dropdowns, clicks)
└── Checks (Array of Check assertion objects: status, content, metadata)
1. Scenario
A Scenario represents an end-to-end user journey or validation suite registered as an individual Cypress test (though scenarios within a workflow may be order-coupled).
- Location: Defined as keys under the top-level
"scenarios"object. - Structure:
Field Type Required Description Scenario Name (Key) stringRequired Unique human-readable title of the test (e.g. "Workflow: Changing from Draft to Needs review as Author").Value Request[]Required Ordered array of Request objects executed in sequence.
2. Request
A Request represents a single step within a Scenario. It can probe an HTTP endpoint, navigate to a page, perform actions, and/or assert conditions.
- Location: Items within a Scenario array.
- Structure:
Field Type Required Description namestringOptional Descriptive label for the step (logged in test execution reports). urlstringOptional Path or URI to visit or check (e.g. "/testing-page","/node/add/page"). Omit to continue on the current page.rolestringOptional User role for authentication (maps to users, or"anonymous"). Defaults to"anonymous"when omitted.statusnumberOptional Expected HTTP status code (e.g. 200,403,404). The URL is probed via fast headlesscy.request()without DOM rendering, not visited — putaction/checksfor that page in a separate request.clean_thisbooleanOptional If true, the URL alias is deleted via Drush before tests run and cleaned up after completion.actionActionOptional Action object containing user interactions to perform on the page. checksCheck[]Optional Array of Check objects containing assertions to verify on the page.
Note on Sessions: The authenticated session persists across consecutive requests that share the same role.
cy.session()is only called when therolechanges or on the first request.
3. Action
An Action defines form inputs, dropdown selections, rich text edits, and click interactions to execute on the active page.
- Location: The
actionproperty of aRequest. - Structure:
Field Type Required Description Shape / Example fill_valuesobjectOptional Map of CSS selectors to text strings to type into input or textarea fields (clears existing field content before typing). { "<css_selector>": "text value" }select_valuesobjectOptional Map of CSS selectors to option values to select in <select>elements.{ "<css_selector>": "option_value" }ck5_valuesobjectOptional Map of CKEditor 5 element IDs to HTML strings to set. { "<editor_id>": "<p>Content</p>" }clickstring[]|stringOptional CSS selector(s) to click sequentially (e.g. clicking a <details>summary like"#edit-menu summary"to expand a section before clicking inputs inside it). Confirmation modals (e.g. "Yes") are confirmed automatically.["#edit-menu summary", "#edit-menu-enabled", "#edit-submit"]
4. Check
A Check defines assertions to verify against the current page or HTTP response.
- Location: Items within the
checksarray property of aRequest. - Structure:
Field Type Required Description Shape / Example containobjectOptional Asserts that element matching selector contains specified text. { "h1": "Testing page" }not_containobjectOptional Asserts that element matching selector does not contain specified text (safe if absent). { ".body": "Draft" }exists_and_containobjectOptional Asserts that element exists in the DOM and contains specified text. { ".alert": "Success" }exists_but_not_containobjectOptional Asserts that element exists in the DOM but does not contain specified text. { ".page__admin .btn": "Delete" }metaobjectOptional Validates <head>tags:titleand/ordescription.{ "title": "Page Title", "description": "Summary" }selectobjectOptional Asserts that <select>element contains all specified option values.{ "#edit-state": ["draft", "needs_review"] }
Step Execution Sequence
Within each step (Request), operations execute in this deterministic sequence:
- Session / Role Switch: If
rolediffers from the active session, logs in viacy.drupalLogin()or clears cookies/storage foranonymous. - Navigation or HTTP Probe:
- If
statusis set: executes a fast headless HTTP check viacy.request(step.url). The URL is probed, not visited — putaction/checksfor that page in a separate request. - If
statusis omitted: navigates the browser to the page viacy.visit(step.url).
- If
- Form Inputs (
action.fill_values): Overwrites existing field values by clearing<input>and<textarea>fields first and then typing the specified value viacy.get().clear()andcy.get().type(). - Rich Text (
action.ck5_values): Sets HTML content in CKEditor 5 instances viaeditor.setData(). - Dropdown Selections (
action.select_values): Selects options in<select>elements viacy.get().select(). - Checks & Assertions (
checks): Validates DOM content, options, and<head>metadata. Runs before clicks so that form states and available dropdown choices can be verified before form submission. - Clicks (
action.click): Sequentially clicks specified CSS selectors (e.g. clicking a collapsible summary"#edit-menu summary"to expand an accordion, checkboxes, buttons). Automatically confirms modal confirmation dialogs ("Yes").
Test Reports
Upon test completion, a consolidated summary report is printed to the terminal console and saved as artifacts:
reports/validation-report.md: Markdown summary table with status, duration, and failure details (ideal for PR comments or CI summaries).reports/validation-report.json: JSON report for CI/CD metrics.
Available Cypress Commands/Helpers
This package provides several custom Cypress commands/helpers specifically for Drupal testing:
cy.drush(command, args, options): Executes a Drush command with the environment prefix.cy.drupalLogin(username): Generates a random password, sets it via Drush, and logs the user in via the login form.cy.drupalLoginWithUID(uid): Logs a user in by user ID via Drush one-time login link (user:login).cy.loginUserByUsername(username): Logs a user in by username via Drush one-time login link (uli).cy.drupalLogout(username): Logs the user out by removing their session via Drush.cy.drupalLogoutAll(): Logs out all users by clearing cookies and truncating the sessions table via Drush.cy.setPasswordToUsername(username, password): Sets a user's password via Drushuser:password(upwd).cy.blockUserByUsername(username)/cy.unblockUserByUsername(username): Blocks/unblocks user accounts via Drush (resets TFA user data when TFA is enabled).cy.isModuleEnabled(module_machine_name): Checks whether a Drupal module is currently enabled via Drush.cy.deletePageByAlias(pageUrl): Resolves the given URL alias to a node ID via Drush and deletes it, if a matching node exists.