---
url: 'https://dxpr.com/hc/documentation/learn-dxpr-builder/development/testing'
title: 'Testing | DXPR'
author:
  name: docs-sync
  url: 'https://dxpr.com/users/docs-sync'
date: '2026-10-23T14:31:17+00:00'
copyright: 'Copyright DXPR. All rights reserved.'
type: article
summary: 'The console, end-to-end and lint checks and how to run them'
tags:
  - Tooling
image: 'https://dxpr.com/themes/custom/dxpr_com/images/dxpr-share-card.jpg'
published: true
og:
  site_name: DXPR
  updated_time: '2026-10-23T14:31:17+0000'
  'image:alt': 'DXPR, powered by Drupal: AI-powered Drupal for marketers.'
schema:
  '@context': 'https://schema.org'
  '@graph':
    -
      mainEntity: {  }
    -
      '@type': TechArticle
      headline: Testing
      description: 'The console, end-to-end and lint checks and how to run them'
      image:
        '@type': ImageObject
        url: 'https://dxpr.com/themes/custom/dxpr_com/images/dxpr-share-card.jpg'
        width: '1200'
        height: '630'
      datePublished: '2026-10-23T14:31:17+0000'
      dateModified: '2026-10-23T14:31:17+0000'
      author:
        '@type': Organization
        '@id': 'https://dxpr.com/#organization'
        name: DXPR
        url: 'https://dxpr.com/'
        logo:
          '@type': ImageObject
          url: 'https://dxpr.com/themes/custom/dxpr_com/dxpr-logo-dark.svg'
          width: '1348'
          height: '501'
      publisher:
        '@type': Organization
        '@id': 'https://dxpr.com/#organization'
        name: DXPR
        url: 'https://dxpr.com/'
        logo:
          '@type': ImageObject
          url: 'https://dxpr.com/themes/custom/dxpr_com/dxpr-logo-dark.svg'
          width: '1348'
          height: '501'
      mainEntityOfPage: 'https://dxpr.com/hc/documentation/learn-dxpr-builder/development/testing'
      articleSection: Tooling
    -
      '@type': WebPage
      '@id': 'https://dxpr.com/hc/documentation/learn-dxpr-builder/development/testing'
      description: 'The console, end-to-end and lint checks and how to run them'
      breadcrumb:
        '@type': BreadcrumbList
        itemListElement:
          -
            '@type': ListItem
            position: 1
            name: Home
            item: 'https://dxpr.com/'
      publisher:
        '@type': Organization
        '@id': 'https://dxpr.com/#organization'
        name: DXPR
      inLanguage: en
---
[Help Center](/hc)/[Documentation](/hc/documentation) /[Learn DXPR Builder](/hc/documentation/learn-dxpr-builder) /[Extend](/hc/documentation/learn-dxpr-builder/extend) /[Tooling](/hc/documentation/learn-dxpr-builder/extend/tooling) # Testing.

The console, end-to-end and lint checks and how to run them

 Learn DXPR BuilderExtendTooling

   ## In this guide

 Related guides

- [Contributing](/hc/documentation/learn-dxpr-builder/development/contributing)
- [Drush commands](/hc/documentation/learn-dxpr-builder/development/drush-commands)

DXPR Builder has four kinds of automated checks: a Puppeteer test of the editor, Bash end-to-end tests for the Drush commands, a Selenium visual regression suite in the separate `dxpr_maven` repository, and linters. This page lists each one, how to run it locally and how CI runs it.

## What exists

| Check | Where it lives | Runs on |
|---|---|---|
| Editor console and layout test | `test/test-editor.js` | Pull requests that touch JavaScript, HTML or `test/` (`.github/workflows/fe-test.yaml`) |
| Drush end-to-end tests | `scripts/drush-e2e/` | Pull requests that touch PHP, YAML or Markdown (`.github/workflows/review.yaml`) |
| Selenium visual regression suite | `dxpr_maven` repository | PR comment commands (`.github/workflows/e2e-command.yaml`, `e2e-bs3-command.yml`, `e2eV2-command.yaml`) |
| PHP coding standards (phpcs) | `scripts/linters/run-drupal-lint.sh`, `phpcs.xml.dist` | Pull requests that touch PHP, YAML or Markdown; every push to `3.x` |
| PHPStan | `scripts/linters/run-drupal-check.sh` | Pull requests that touch PHP, YAML or Markdown; every push to `3.x` |
| ESLint, Stylelint, Rust Clippy | `scripts/linters/` | Pull requests, filtered by changed paths |
| Deprecation detectors | `.github/workflows/deprecations.yml` | Pull requests that touch `js/` or `dxpr_builder/build/` |
| Docs checks (markdownlint, `check_docs.py`, Vale) | `.markdownlint-cli2.yaml`, `docs/lint/`, `.vale.ini` | Pull requests that touch `docs/` (`review.yaml`) |

The only PHPUnit test in the repository is `modules/dxpr_builder_media/tests/src/FunctionalJavascript/MediaEntityBrowserTest.php`for the legacy media submodule. No GitHub workflow runs it; the drupal.org GitLab pipeline (`.gitlab-ci.yml`) uses the standard DrupalCI templates, whose PHPUnit job is not switched off there.

## Editor console test

`test/test-editor.js` starts a static server on port 3000 (`test/start-standalone-server.js`) and opens four standalone pages from `test/standalone-editor/` in headless Chrome: `test-bs5-anon.html`, `test-bs5-cms-block.html`, `test-bs5-editor.html` and `test-bs5-clean-html.html`. It fails when:

- a page throws a JavaScript error;
- the full editor is not ready within 2000 ms, or no off-screen controls are left deferred after load (the performance gate);
- element controls overlap in Power user mode (the baseline is zero overlapping pairs);
- the saved HTML of the full editor page is larger than the reference of 32,094 characters;
- a page-specific check fails, for example the parsing of clean Bootstrap HTML, the breadcrumb, the Panel types, the local video **Muted** setting or the CMS block preview.

The pages load the built files from `dxpr_builder/`, so build first.

1. Install dependencies:

    ```bash
    npm install

    ```
2. Build the editor bundle. The CI script runs these tasks in this order (`test/scripts/run-fe-tests.sh`):

    ```bash
    npx grunt concat
    npx grunt exec:gen
    npx grunt babel
    npx grunt terser
    npx grunt sass
    npx grunt postcss

    ```
3. Run the test:

    ```bash
    npm run fe-test

    ```

    Use `npm run fe-test:headed` to watch the browser, and `npm run fe-test:dev` to serve the standalone pages without running assertions.

In CI, `.github/workflows/fe-test.yaml` runs the same test through Docker:

```bash
docker compose --profile fe-test run --rm fe-test

```

## Drush end-to-end tests

`scripts/drush-e2e/run-e2e-tests.sh` installs a fresh Drupal 11 site with SQLite inside a Composer container, symlinks the module into it, enables it, creates a Basic page content type with the `dxpr_builder_text` formatter, a page template and a user template, then runs every `scripts/drush-e2e/test-*.sh` file:

| File | Commands under test |
|---|---|
| `test-cleartext-key-update.sh` | Update 9026 clears a clear-text product key left under Key module storage, and keeps it when the chosen Key is gone (`cleartext-key-update.php`) |
| `test-cms-cache-tags.sh` | Cache invalidation of block and view listings (`cms-cache-tags.php`) |
| `test-elements.sh` | `drush dxb:element:list` |
| `test-migration-summary.sh` | Singular and plural wording of the migration column summary (`migration-summary.php`) |
| `test-migrations.sh` | `drush dxb:migrate` and content migration regressions (`migration-regressions.php`) |
| `test-page-controller.sh` | The page controller builds for its routes (`page-controller.php`) |
| `test-page-create.sh` | `drush dxb:page:create` |
| `test-page-get-update.sh` | `drush dxb:page:get`, `drush dxb:page:update` |
| `test-page-module.sh` | The Drag and Drop Page module ships every configuration it lists (`page-module.php`) |
| `test-profile-buttons.sh` | Text editor buttons of builder profiles (`profile-buttons.php`) |
| `test-profile-order.sh` | Which profile applies by weight, and the order of the profile list (`profile-order.php`) |
| `test-settings-form.sh` | Saving and validating the product key on **General Settings**, and the licence sync it triggers (`settings-form.php`) |
| `test-setup-ai.sh` | `drush dxb:setup-ai` |
| `test-templates.sh` | `drush dxb:template:list` |
| `test-user-licenses-page.sh` | The licence card shows once on **User Licenses** and still shows on **People** (`user-licenses-page.php`) |
| `test-user-settings.sh` | Each editor's own editor settings (`user-settings.php`) |
| `test-user-templates.sh` | `drush dxb:user-template:list`, `drush dxb:user-template:create` |

Run all of them:

```bash
docker compose --profile drush-e2e run --rm e2e-test

```

Run one file by passing part of its name; the script matches `scripts/drush-e2e/test-*<filter>*.sh`:

```bash
docker compose --profile drush-e2e run --rm e2e-test page-create

```

To add a test, create `scripts/drush-e2e/test-<name>.sh`, call `section "<Title>"`, and use the helpers from `scripts/drush-e2e/_helpers.sh`: `assert_success`, `assert_fail`, `assert_eq`, `assert_has`, `assert_not_empty`, `assert_runs` and `assert_dry_run`. Each helper takes a description and a command string.

In CI this suite is the "Run Drush E2E tests" job in `.github/workflows/review.yaml`. It runs when a pull request changes `.php`, `.module`, `.inc`, `.install`, `.yml`, `.md`, `composer.json`or `composer.lock` files.

## Selenium visual regression suite

The browser tests are Java TestNG suites in the `dxpr_maven`repository. They run against a Docker stack defined in `docker-compose.test.yml` (Selenium hub, Chrome nodes and a Maven container) on a site installed from this repository. You do not run them from a local checkout; you trigger them from a pull request comment. `.github/workflows/command-dispatcher.yml` turns the comment into a workflow run.

| Command | Suite | Workflow |
|---|---|---|
| `/e2e` | `chrome.editor.bs5.parallel.xml` plus the anonymous BS5 suite | `e2e-command.yaml` |
| `/e2e-bs3` | `chrome.editor.bs3.parallel.xml` | `e2e-bs3-command.yml` |
| `/e2eV2` | BS5 editor and anonymous suites on the DXPR CMS stack | `e2eV2-command.yaml` |
| `/qa-demo-2x-dxpr_theme6x-tests` | QA demo with DXPR Theme 6.x | `qa-demo-2x-dxpr_theme6x-tests-command.yaml` |

Options, as documented in `CONTRIBUTING.md`:

- `groups=row-element-test,icon-element-test` runs the named `<test name="...">` groups from the suite XML.
- `tests=RowColumnLayoutVisualTest` runs the named test classes.
- `maven_ref=branch-name` uses a `dxpr_maven` branch, which is how a builder change and its test update are reviewed together.

The workflow uploads `target/reports/**` and the screenshots as a build artifact and posts the result as a comment on the pull request, listing failures or tests that only passed on retry. Each suite job has a 90-minute timeout.

## Linters and static analysis

Each linter is a Docker Compose service, so no local toolchain is required beyond Docker.

| Service | Command | What it checks |
|---|---|---|
| `drupal-lint` | `docker compose run --rm drupal-lint` | phpcs with PHPCompatibility (PHP 8.3 and up), `Drupal` and `DrupalPractice` standards |
| `drupal-check-D11` | `docker compose run --rm drupal-check-D11` | PHPStan level 5 with `mglaman/phpstan-drupal` inside a fresh `drupal/recommended-project` |
| `eslint` | `docker compose run --rm eslint` | ESLint on `js/` and `dxpr_builder/build/`, plus a spellcheck of `dxpr_builder/extracted_t_functions.js` |
| `stylelint` | `docker compose run --rm stylelint` | Stylelint on `dxpr_builder/sass/` and `sass/` |
| `rust-lint` | `docker compose run --rm rust-lint` | Clippy on the Rust sources in `rust/` |
| `no-jquery`, `no-underscore`, `html-in-js` | `docker compose run --rm <service>` | Deprecation detectors from `.github/workflows/deprecations.yml` |

Auto-fix variants exist for phpcs, ESLint and Stylelint (`drupal-lint-auto-fix`, `eslint-auto-fix`, `stylelint-auto-fix`). The same checks are available without Docker through `npm run lint:scss`, `npm run lint:no-jquery`, `npm run lint:no-underscore`and `npm run lint:html-in-js`.

`npm install` also installs a Husky pre-commit hook (`.husky/pre-commit`). It runs the matching auto-fix and lint services on the staged JavaScript, PHP and SCSS files and the Rust sources, and `docs/video-tools/bin/lint-videos.mjs` when files under `docs/` are staged.

`.github/workflows/review.yaml` runs the linters that match the changed paths on every pull request. `.github/workflows/main.yaml`runs `drupal-lint`, `eslint`, `drupal-check-D11` and `rust-lint` on every push to `3.x`. Both fail the build when a check fails and write a summary with the offending files.

## What's next?

- [Contributing](/hc/documentation/learn-dxpr-builder/development/contributing) for the pull request process and the `maven_ref` workflow for test updates.
- [Drush commands](/hc/documentation/learn-dxpr-builder/development/drush-commands) for the commands the Bash suite exercises.