PHPackages                             thronedigital/smoke - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. thronedigital/smoke

ActiveDrupal-module

thronedigital/smoke
===================

Automated smoke testing for Drupal. Auto-detects installed modules, runs Playwright browser tests, and provides a dashboard and Drush CLI.

1.4.18(2mo ago)010GPL-2.0-or-laterPHP

Since Feb 16Pushed 2mo agoCompare

[ Source](https://github.com/DanePete/smoke)[ Packagist](https://packagist.org/packages/thronedigital/smoke)[ RSS](/packages/thronedigital-smoke/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependencies (1)Versions (41)Used By (0)

Smoke
=====

[](#smoke)

**Automated smoke testing for Drupal.** One module. One command. Full coverage.

Smoke auto-detects what's installed on your Drupal site — Webform, Commerce, Search API — and runs [Playwright](https://playwright.dev) browser tests against it. Results show up in a clean admin dashboard or via Drush CLI. Built for support teams running Drupal on [DDEV](https://ddev.com).

For a full description, visit the [project page](https://www.drupal.org/project/smoke).

Submit bug reports and feature suggestions, or track changes in the [issue queue](https://www.drupal.org/project/issues/smoke).

---

Table of Contents
-----------------

[](#table-of-contents)

- [Quick Start](#quick-start)
- [Requirements](#requirements)
- [Running Tests](#running-tests)
- [Admin Dashboard](#admin-dashboard)
- [Drush Commands](#drush-commands)
- [What It Tests](#what-it-tests)
- [Configuration](#configuration)
- [Custom URLs](#custom-urls)
- [Adding Custom Tests](#adding-custom-tests)
- [Plugin System](#plugin-system)
- [After Module Updates](#after-module-updates)
- [Architecture](#architecture)
- [Troubleshooting](#troubleshooting)
- [Uninstall &amp; Cleanup](#uninstall--cleanup)
- [Maintainers](#maintainers)
- [License](#license)

---

Requirements
------------

[](#requirements)

- **Drupal** 10 or 11
- **DDEV** local development environment
- **Node.js** 18+ (included in DDEV containers)
- **Composer** (managed via DDEV)

No third-party DDEV addons are required. Smoke handles Playwright and Chromium installation directly during setup.

---

Install
-------

[](#install)

```
ddev composer require drupal/smoke
ddev drush en smoke -y
```

---

Setup
-----

[](#setup)

After enabling the module, run the host setup script from your **project root** (where `.ddev/` lives):

```
bash web/modules/contrib/smoke/scripts/host-setup.sh
```

Or run setup entirely inside the container:

```
ddev drush smoke:setup
```

### What setup does

[](#what-setup-does)

1. Verifies DDEV is running and Node.js is available
2. Installs npm dependencies for the Playwright test suites
3. Downloads the **Chromium** browser (~180 MiB one-time download)
4. Tries to install Chromium’s system dependencies (e.g. `libnss3`, `libatk1.0-0`) via `npx playwright install-deps chromium` in non-interactive mode. If that step fails (e.g. no sudo, or your image doesn’t allow it), the script continues and prints the manual command to run.
5. **When the Webform module is enabled:** prompts for the **webform machine name** to use for smoke tests (e.g. `contact_us`, `smoke_test`). If that webform doesn’t exist, it is created with standard Name, Email, and Message fields. The legacy `smoke_test` webform is removed when you choose a different ID so the test uses your custom form.
6. Generates the test configuration — scans your site for installed modules, webforms, commerce stores, search pages, etc.
7. Creates a `smoke_bot` test user and role for authentication tests
8. Verifies all test suites are wired up
9. Installs a DDEV post-start hook (`config.smoke.yaml`) that auto-regenerates config on `ddev start`

If the script appears to hang, it has been updated so the system-deps step no longer waits for input. If you see a warning that system deps could not be installed, run inside the container: `ddev exec "sudo npx playwright install-deps chromium"`. When running with `--silent` (e.g. from the DDEV post-start hook), the webform prompt is skipped and the existing setting is kept.

### What gets installed where

[](#what-gets-installed-where)

WhatWhereSizenpm packages`web/modules/contrib/smoke/playwright/node_modules/`~50 MiBChromium browser`~/.cache/ms-playwright/chromium-*` (inside container)~180 MiBSystem librariesContainer OS packages (libnspr4, libnss3, etc.)~20 MiBTest config`web/modules/contrib/smoke/playwright/.smoke-config.json`&lt;1 KBDDEV hook`.ddev/config.smoke.yaml`&lt;1 KBThe Chromium browser is cached per-user inside the DDEV container. If multiple projects use Smoke, they share the same browser cache. The cache persists across `ddev restart` but is removed on `ddev delete`.

### Re-running setup

[](#re-running-setup)

If you install new modules (e.g. add Webform or Commerce) and want Smoke to detect them:

```
ddev drush smoke:setup
```

This regenerates the test config. Browsers are only downloaded on first run.

---

Running Tests
-------------

[](#running-tests)

### Show status and detected suites

[](#show-status-and-detected-suites)

```
ddev drush smoke
```

### Run all tests

[](#run-all-tests)

```
ddev drush smoke --run
```

Tests run sequentially with a live progress bar showing which suite is executing:

```
  Smoke Tests — My Site
  https://my-site.ddev.site
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  Core Pages        ✓ 8 passed  3.7s
  Authentication    ✓ 5 passed  2.6s
  ▸ Webform           ━━━━━━━▸────────────  3/8 suites  17s

```

### Run a specific suite

[](#run-a-specific-suite)

```
ddev drush smoke:suite core_pages
ddev drush smoke:suite auth
ddev drush smoke:suite webform
ddev drush smoke:suite commerce
ddev drush smoke:suite search
ddev drush smoke:suite health
ddev drush smoke:suite sitemap
ddev drush smoke:suite content
ddev drush smoke:suite accessibility
```

### Auto-fix common failures

[](#auto-fix-common-failures)

```
ddev drush smoke:fix              # Analyze last results and fix what it can
ddev drush smoke:fix --sitemap    # Regenerate the XML sitemap
ddev drush smoke:fix --all        # Fix all known issues
```

### Test a remote site (post-deploy)

[](#test-a-remote-site-post-deploy)

After deploying to Pantheon (or any remote host), run the same tests against the live URL:

```
ddev drush smoke --run --target=https://test-mysite.pantheonsite.io
ddev drush smoke --run --target=https://live-mysite.pantheonsite.io
```

Or test a single suite:

```
ddev drush smoke:suite core_pages --target=https://test-mysite.pantheonsite.io
```

When using `--target`, tests run from your local DDEV Playwright install against the remote URL.

#### What runs on remote vs. what skips

[](#what-runs-on-remote-vs-what-skips)

BehaviourSuites / TestsWhy**Runs normally**Core Pages (all 8), Commerce, Search, Accessibility, Health (CSS/JS assets, login page check)These are anonymous — no login required**Auto-skips**Auth (invalid login, smoke\_bot login), Health (admin status, cron, dblog), Content (create/delete node)These need `smoke_bot` which only exists locally**Tries first, skips on 404**Webform (configured form, default smoke\_test)The default form is auto-created locally; deploy the webform config to run on remote too**Skips if module missing**SitemapOnly runs when `simple_sitemap` or `xmlsitemap` is installed#### Making webform tests work on remote

[](#making-webform-tests-work-on-remote)

The configured webform (default `smoke_test`) is auto-created locally when set to `smoke_test`; it won’t exist on Pantheon unless you deploy it. The test tries to load the form and skips gracefully on 404.

To make webform tests run on Pantheon too:

1. Export config locally: `ddev drush config:export -y`
2. Commit the exported webform config (e.g. `webform.webform.smoke_test.yml` or your configured `webform.webform.{id}.yml`) in `config/sync`
3. Deploy to Pantheon and import config: `drush config:import -y`

Once that webform exists on the remote, webform tests will start passing there.

### List detected suites

[](#list-detected-suites)

```
ddev drush smoke:list
```

Shows which suites were detected, whether they're enabled, and their last status.

---

Admin Dashboard
---------------

[](#admin-dashboard)

### Results

[](#results)

Visit **Reports &gt; Smoke Tests** (`/admin/reports/smoke`) to:

- See a summary of the last test run (passed / failed / skipped)
- View per-suite results with individual test names and durations
- Click **Run All Tests** or run individual suites from the UI
- See failure details and error messages inline
- **Regenerate sitemap** directly from the Sitemap suite card

### Settings

[](#settings)

Visit **Configuration &gt; Development &gt; Smoke** (`/admin/config/development/smoke`) to:

- Enable or disable individual test suites
- Set the **test webform ID** (default `smoke_test`) so the Webform suite tests your agency or company form — see [Configuration](#configuration)
- Add custom URLs to test (see [Custom URLs](#custom-urls))
- Adjust the per-test timeout

Access requires the `administer smoke tests` permission.

---

Drush Commands
--------------

[](#drush-commands)

CommandAliasDescription`drush smoke:run``drush smoke`Show status, or run all tests with `--run``drush smoke:run --run``drush smoke --run`Run all enabled test suites with progress bar`drush smoke:run --run --target=URL`—Run tests against a remote site`drush smoke:suite {id}`—Run a single suite (e.g. `webform`, `auth`, `core_pages`)`drush smoke:suite {id} --target=URL`—Run one suite against a remote site`drush smoke:list`—Show detected suites, enabled status, and last results`drush smoke:setup`—Install dependencies, browsers, generate config, verify test user`drush smoke:fix``sfix`Analyze last results and auto-fix common issues`drush smoke:fix --sitemap`—Regenerate the XML sitemap`drush smoke:fix --all`—Fix all detected issuesCommandDescription----------------------`drush smoke`Status landing page`drush smoke --run`Run all tests with live progress bar`drush smoke --run --quick`Fast sanity check (core\_pages + auth only)`drush smoke --run --suite=SUITE`Run specific suites`drush smoke --run --parallel`Run suites in parallel (faster)`drush smoke --run --watch`Watch mode for test development`drush smoke --run --junit=FILE`CI-friendly JUnit XML output`drush smoke --run --html=DIR`Interactive HTML report`drush smoke --run --target=URL`Run against a remote site`drush smoke:suite webform`Run a single suite`drush smoke:fix`Auto-fix detected issues (`--sitemap`, `--all`)`drush smoke:list`List detected suites`drush smoke:setup`Set up Playwright environment`drush smoke:init`Set up VS Code/Cursor integration and custom test directory`drush smoke:unit`Run the module's PHPUnit tests`drush smoke:pantheon`Show Pantheon site info or run tests (if smoke\_pantheon enabled)`drush smoke:pantheon:set`Set Pantheon site name`drush smoke:pantheon:check`Validate Pantheon site and auth with Terminus`drush smoke:pantheon:sites`List all Pantheon sites you have access to---

What It Tests
-------------

[](#what-it-tests)

SuiteTriggers WhenWhat's Checked**Core Pages**AlwaysHomepage returns 200, login page returns 200, no PHP fatal errors, no JS console errors, no broken images, no mixed content, 404 page renders (not WSOD), 403 page renders (not WSOD)**Authentication**AlwaysLogin form renders, invalid credentials show error, `smoke_bot` can log in and reach the profile page, password reset page exists**Webform**`webform` module enabledSubmits the configured webform (default `smoke_test`, or set in settings), fills all fields, submits, and confirms success**Commerce**`commerce` module enabledProduct catalog pages load, cart endpoint responds, checkout endpoint responds, store exists**Search**`search_api` or `search` module enabledSearch page loads, search form is present on the page**Health**AlwaysAdmin status report has no errors, cron has run recently, CSS/JS assets load without 404s, no PHP errors in dblog, login page cache headers correct**Sitemap**`simple_sitemap` or `xmlsitemap` module`/sitemap.xml` returns 200, contains valid XML with URLs, no PHP errors**Content**`page` content type existsCreates a test Basic Page, verifies it renders, deletes it — full content pipeline check**Accessibility**Alwaysaxe-core WCAG 2.1 AA scan on homepage and login page, fails on critical/serious violations, best-practice scan (informational)### Auto-detection

[](#auto-detection)

Suites are **automatically detected** based on installed modules. If Commerce isn't installed, the Commerce suite is skipped entirely — no errors, no configuration needed.

### Webform suite and configurable form

[](#webform-suite-and-configurable-form)

When the Webform module is detected, Smoke runs tests against the webform set in **Test webform ID** (Configuration &gt; Development &gt; Smoke). Default is `smoke_test`: if that form doesn’t exist, Smoke creates it (Name, Email, Message). You can set any other webform machine name (e.g. `contact_us`) to test your own form; that form must already exist and be open.

---

Configuration
-------------

[](#configuration)

### Config file

[](#config-file)

Smoke stores its settings in `smoke.settings`:

```
suites:
  core_pages: true
  auth: true
  webform: true
  commerce: true
  search: true
  health: true
  sitemap: true
  content: true
  accessibility: true
webform_id: smoke_test
custom_urls: []
timeout: 10000
```

Edit via the admin UI at `/admin/config/development/smoke` or export/import with Drupal's config system.

### Test webform ID

[](#test-webform-id)

The **Webform** suite submits a single webform and checks for success. By default it uses the `smoke_test` webform (auto-created if missing). You can point it at any existing webform so the test is tailored to your agency or company:

- **`smoke_test`** (default) — Smoke creates this form if it doesn’t exist (Name, Email, Message).
- **Any other machine name** (e.g. `contact_us`, `quote_request`) — Smoke uses that webform if it exists and is open; no auto-creation.

Set **Test webform ID** in Configuration &gt; Development &gt; Smoke, or in config as `webform_id: contact_us`.

### Test timeout

[](#test-timeout)

The `timeout` value (in milliseconds) controls how long each individual test waits before failing. Default is `10000` (10 seconds). For slow environments, increase to `20000` or `30000`.

### Generated test config

[](#generated-test-config)

When you run `drush smoke:setup` or `drush smoke`, Smoke generates a `.smoke-config.json` file inside its `playwright/` directory. This JSON file contains:

- The site's base URL (from `DDEV_PRIMARY_URL`)
- Site title
- All detected suites and their metadata (webform fields, commerce flags, search paths, etc.)
- Auth credentials for the `smoke_bot` test user
- Timeout settings

Playwright reads this file at runtime. **You don't edit this file directly** — it's regenerated from Drupal's state on each setup or test run.

---

Custom URLs
-----------

[](#custom-urls)

Add extra pages to test via the admin settings form or directly in config:

```
# In config/sync or via /admin/config/development/smoke
custom_urls:
  - /about
  - /pricing
  - /contact
  - /products
```

Each custom URL is checked for:

- HTTP 200 response
- No PHP fatal errors in the response body

---

Adding Custom Tests
-------------------

[](#adding-custom-tests)

Smoke ships with built-in suites, but you can easily add your own. Tests are standard [Playwright spec files](https://playwright.dev/docs/writing-tests) in TypeScript.

### Quick start: add a spec file

[](#quick-start-add-a-spec-file)

Create a new `.spec.ts` file in `playwright/suites/` inside the module:

```
# Find where the module lives
ddev drush eval "echo DRUPAL_ROOT . '/' . \Drupal::service('extension.list.module')->getPath('smoke');"

# Create your test file
vim web/modules/contrib/smoke/playwright/suites/my-pages.spec.ts
```

### Example: test specific pages

[](#example-test-specific-pages)

```
import { test, expect } from '@playwright/test';
import { assertHealthyPage } from '../src/helpers';

test.describe('My Pages', () => {

  test('/about returns 200', async ({ page }) => {
    await assertHealthyPage(page, '/about');
  });

  test('/pricing returns 200', async ({ page }) => {
    await assertHealthyPage(page, '/pricing');
  });

  test('/contact has a form', async ({ page }) => {
    await page.goto('/contact');
    await expect(page.locator('form')).toBeVisible();
  });

});
```

### Example: test an authenticated page

[](#example-test-an-authenticated-page)

```
import { test, expect } from '@playwright/test';
import { loadConfig } from '../src/config-reader';
import { loginAsSmokeBot } from '../src/helpers';

const config = loadConfig();
const auth = config.suites.auth;

test.describe('Member Pages', () => {

  test('dashboard requires login', async ({ page }) => {
    const response = await page.goto('/dashboard');
    // Should redirect to login or return 403
    expect([200, 403]).toContain(response?.status());
  });

  test('smoke_bot can access dashboard', async ({ page }) => {
    await loginAsSmokeBot(
      ```
      Commands

        ▸ drush smoke                      Status landing page
        ▸ drush smoke --run                 Run all tests
        ▸ drush smoke --run --quick         Fast sanity check (core_pages + auth)
        ▸ drush smoke --run --suite=SUITE   Run specific suites
        ▸ drush smoke --run --parallel      Run suites in parallel (faster)
        ▸ drush smoke --run --watch         Watch mode for test development
        ▸ drush smoke --run --junit=FILE    CI-friendly JUnit XML output
        ▸ drush smoke --run --html=DIR      Interactive HTML report
        ▸ drush smoke --run --target=URL    Run against a remote site
        ▸ drush smoke:suite webform         Run a single suite
        ▸ drush smoke:fix                   Auto-fix detected issues (--sitemap, --all)
        ▸ drush smoke:list                  List detected suites
        ▸ drush smoke:setup                 Set up Playwright environment
        ▸ drush smoke:init                  VS Code/Cursor integration and custom test directory
        ▸ drush smoke:unit                  Run the module's PHPUnit tests
        ▸ drush smoke:pantheon              Show Pantheon site info or run tests (if smoke_pantheon enabled)
        ▸ drush smoke:pantheon:set          Set Pantheon site name
        ▸ drush smoke:pantheon:check        Validate Pantheon site and auth with Terminus
        ▸ drush smoke:pantheon:sites        List all Pantheon sites you have access to
      ```
import { isSuiteEnabled } from '../src/config-reader';

// Re-use an existing suite flag, or check your own way
const hasWebform = isSuiteEnabled('webform');

test.describe('Contact Flow', () => {
  test.skip(!hasWebform, 'Webform not installed.');

  test('contact page has phone field', async ({ page }) => {
    await page.goto('/webform/contact_us');
    await expect(page.getByLabel('Phone Number')).toBeVisible();
  });
});
```

### Available helpers

[](#available-helpers)

These are imported from `../src/helpers`:

HelperWhat it does`assertHealthyPage(page, path)`Navigates to the path, asserts HTTP 200, checks for PHP fatal errors`assertNoJsErrors(page, path)`Navigates and captures any JavaScript console errors`loginAsSmokeBot(page, user, pass)`Logs into Drupal with the smoke\_bot test user`fillField(page, label, type)`Fills a form field by label, using smart defaults based on type (email, tel, textarea, etc.)### Available config readers

[](#available-config-readers)

These are imported from `../src/config-reader`:

FunctionWhat it does`loadConfig()`Returns the full config object (base URL, suites, timeout, etc.)`isSuiteEnabled(suiteId)`Returns `true` if a suite is detected and enabled`getSuiteConfig(suiteId)`Returns the config object for a specific suite### Naming conventions

[](#naming-conventions)

- File: `playwright/suites/my-thing.spec.ts` (use dashes, not underscores)
- Describe block: `test.describe('My Thing', () => { ... })`
- Tests show up automatically — no registration needed

### After adding tests

[](#after-adding-tests)

Reinstall npm dependencies and run:

```
ddev drush smoke:setup    # Reinstalls dependencies, regenerates config
ddev drush smoke --run    # Run all tests including your new ones
```

### Tips

[](#tips)

- Keep tests **fast**. Each test should be under 5 seconds. This is smoke testing, not QA.
- Use `assertHealthyPage()` as your go-to — it checks HTTP 200 + no PHP fatals in one call.
- Use `test.skip()` to conditionally skip tests that depend on specific modules or config.
- Don't use `waitForLoadState('networkidle')` — it hangs on sites with analytics or long-polling.
- `loadConfig().baseUrl` gives you the full site URL if you need it.

---

After Module Updates
--------------------

[](#after-module-updates)

This is the primary use case Smoke was built for. After running `composer update` on contrib modules, run smoke tests to verify nothing broke.

### Manual workflow

[](#manual-workflow)

```
ddev composer update
ddev drush cr
ddev drush smoke --run
```

If you've added or removed modules (e.g. added `webform`), regenerate the config first:

```
ddev drush smoke:setup
ddev drush smoke --run
```

### Automatic: Composer scripts

[](#automatic-composer-scripts)

Add Composer scripts to your **project root** `composer.json` so smoke tests run automatically after every `composer install` or `composer update`. If any test fails, the command exits non-zero — broken updates are caught immediately.

```
{
  "scripts": {
    "post-update-cmd": [
      "./vendor/bin/drush cr",
      "./vendor/bin/drush smoke --run"
    ],
    "post-install-cmd": [
      "./vendor/bin/drush cr",
      "./vendor/bin/drush smoke --run"
    ]
  }
}
```

If your `composer.json` already has `post-update-cmd` or `post-install-cmd` entries, append the two lines to the existing arrays instead of replacing them.

For DDEV users running `ddev composer update`, the scripts execute inside the container where Drush is available at `./vendor/bin/drush`. Running `smoke:setup` will offer to add these scripts automatically.

### Enforce before push: git pre-push hook

[](#enforce-before-push-git-pre-push-hook)

To prevent pushing when smoke tests fail, add a git pre-push hook. Create `.git/hooks/pre-push` in your project root:

```
#!/usr/bin/env bash
# Smoke pre-push hook — tests must pass before pushing.
echo "Running smoke tests..."
ddev drush smoke --run
exit $?
```

Then make it executable:

```
chmod +x .git/hooks/pre-push
```

Now `git push` will run the full smoke suite first. If any test fails, the push is blocked.

> **Tip:** To share this hook with your team, store it in a tracked directory (e.g. `scripts/hooks/pre-push`) and have each developer symlink it: `ln -sf ../../scripts/hooks/pre-push .git/hooks/pre-push`

---

Agency Setup: Global Playwright Installation
--------------------------------------------

[](#agency-setup-global-playwright-installation)

For agencies managing many Drupal sites, downloading Chromium (~180 MiB) for every project is wasteful. Two approaches exist for sharing a single browser installation across projects.

### Option 1: Host-side Global Install (Recommended)

[](#option-1-host-side-global-install-recommended)

Install Playwright once on your Mac, outside of DDEV. This is the **recommended approach** because:

- The VS Code / Cursor Playwright extension discovers the installation automatically
- Browser updates happen once, not per-project
- Tests can run from your IDE or from `ddev` commands

#### Setup

[](#setup-1)

Run the global setup script (included in the module):

```
bash web/modules/contrib/smoke/scripts/global-setup.sh
```

This script:

1. Creates `~/.playwright-smoke/` with a standalone Node/Playwright installation
2. Installs only Chromium (not Firefox or WebKit) — saves ~300 MiB
3. Adds the installation to your shell PATH (`~/.zshrc` or `~/.bashrc`)
4. Sets `PLAYWRIGHT_BROWSERS_PATH` so all projects share the same browser

After running the script, restart your terminal or run `source ~/.zshrc`.

#### Verify

[](#verify)

```
playwright --version
# Should output: Version 1.x.x

ls ~/.playwright-smoke/browsers
# Should show chromium-* directory
```

#### VS Code / Cursor Integration

[](#vs-code--cursor-integration)

Once Playwright is installed globally:

1. Open your Drupal project in VS Code or Cursor
2. Run `ddev drush smoke:init` to create a project-level `playwright.config.ts`
3. Install the **Playwright Test for VS Code** extension
4. Click the beaker icon → the extension finds your Smoke suites automatically

Tests run directly from your IDE using the global browser — no per-project setup needed.

### Option 2: DDEV Shared Docker Volume

[](#option-2-ddev-shared-docker-volume)

If you prefer keeping everything inside Docker, you can share the browser cache across all DDEV projects using a named volume.

#### Setup

[](#setup-2)

Copy the example config to your DDEV global config directory:

```
mkdir -p ~/.ddev/global_config.d
cp web/modules/contrib/smoke/scripts/docker-compose.playwright-cache.yaml \
   ~/.ddev/global_config.d/docker-compose.playwright-cache.yaml
ddev restart
```

Now all DDEV projects share the same `ddev-playwright-browsers` Docker volume.

#### First-time browser install

[](#first-time-browser-install)

After adding the global config, install browsers once from any project:

```
ddev exec "npx playwright install chromium"
```

The browser is stored in the shared volume. Future projects skip the download.

#### Per-project setup (still required)

[](#per-project-setup-still-required)

Each project still needs npm dependencies:

```
ddev exec "cd web/modules/contrib/smoke/playwright && npm install"
```

But the ~180 MiB browser download only happens once.

### Comparison

[](#comparison)

ApproachBrowser DiskIDE SupportRuns InHost-side global~180 MiB total✅ FullMac + DDEVDDEV shared volume~180 MiB total❌ LimitedDDEV onlyPer-project (default)~180 MiB × projects✅ FullDDEV onlyFor most agencies, **host-side global** is the best choice: one install, full IDE support, runs anywhere.

---

Architecture
------------

[](#architecture)

```
smoke/
├── src/
│   ├── Controller/
│   │   └── DashboardController.php   # Admin UI — results, run tests, sitemap regen
│   ├── Form/
│   │   └── SettingsForm.php           # Config form — suites, URLs, timeout
│   ├── Service/
│   │   ├── ModuleDetector.php         # Scans site for testable features
│   │   ├── ConfigGenerator.php        # Writes JSON bridge file for Playwright
│   │   └── TestRunner.php             # Spawns Playwright, parses results
│   └── Commands/
│       ├── SmokeRunCommand.php        # drush smoke:run (with progress bar)
│       ├── SmokeSuiteCommand.php      # drush smoke:suite {id}
│       ├── SmokeListCommand.php       # drush smoke:list
│       ├── SmokeSetupCommand.php      # drush smoke:setup (installs browsers)
│       └── SmokeFixCommand.php        # drush smoke:fix (auto-fix failures)
├── playwright/
│   ├── playwright.config.ts           # Playwright config — reads .smoke-config.json
│   ├── src/
│   │   ├── config-reader.ts           # Loads Drupal-generated JSON config
│   │   └── helpers.ts                 # Shared helpers (login, assertions)
│   └── suites/
│       ├── core-pages.spec.ts         # Homepage, login, critical pages
│       ├── auth.spec.ts               # Authentication flow
│       ├── webform.spec.ts            # Webform render, submit, validation
│       ├── commerce.spec.ts           # Commerce catalog, cart, checkout
│       ├── search.spec.ts             # Search page and form
│       ├── health.spec.ts             # Admin status, cron, assets, dblog
│       ├── sitemap.spec.ts            # XML sitemap validation
│       ├── content.spec.ts            # Content creation round-trip
│       └── accessibility.spec.ts      # axe-core WCAG 2.1 AA scan
├── scripts/
│   └── host-setup.sh                  # One-command host-side setup
├── templates/
│   └── smoke-dashboard.html.twig      # Admin dashboard template
├── config/
│   ├── install/smoke.settings.yml     # Default settings
│   └── schema/smoke.schema.yml        # Config schema
└── css/
    └── dashboard.css                  # Dashboard styles

```

### Data flow

[](#data-flow)

```
Drupal (PHP)                          Node (TypeScript)
┌──────────────┐                      ┌──────────────────┐
│ ModuleDetector│──detects modules──>  │                  │
│              │                      │ .smoke-config.json│
│ConfigGenerator│──writes config───>  │                  │
└──────────────┘                      └────────┬─────────┘
                                               │
                                      ┌────────▼─────────┐
                                      │   Playwright      │
                                      │   (Chromium)      │
                                      │   runs .spec.ts   │
                                      └────────┬─────────┘
                                               │
┌──────────────┐                      ┌────────▼─────────┐
│  TestRunner   │/dev/null

# Remove the addon
ddev add-on remove codingsasi/ddev-playwright   # or Lullabot/ddev-playwright
ddev restart
```

#### 6. Remove Playwright from the host system (if installed outside DDEV)

[](#6-remove-playwright-from-the-host-system-if-installed-outside-ddev)

If you ever ran `npx playwright install` on your **host machine** (outside DDEV), Playwright stores browsers in your user home directory:

OSBrowser cache location**macOS**`~/Library/Caches/ms-playwright/`**Linux**`~/.cache/ms-playwright/`**Windows**`%USERPROFILE%\AppData\Local\ms-playwright\`To remove:

```
# macOS
rm -rf ~/Library/Caches/ms-playwright

# Linux
rm -rf ~/.cache/ms-playwright
```

This frees 200-500 MiB depending on how many browser types were installed.

#### 7. Verify cleanup

[](#7-verify-cleanup)

After all steps, confirm nothing remains:

```
# Module should be gone
ddev drush pm:list --filter=smoke
# Should return empty table

# No DDEV hook
ls .ddev/config.smoke.yaml 2>/dev/null
# Should say "No such file"

# No browser cache in container
ddev exec "ls ~/.cache/ms-playwright 2>/dev/null"
# Should say "No such file or directory"

# No test stub
ls test/playwright 2>/dev/null
# Should say "No such file"
```

### What Smoke does NOT touch

[](#what-smoke-does-not-touch)

For reference, these are things Smoke **never** modifies:

- Your site's content, configuration, or database (except the `smoke_bot` user/role, which is removed on uninstall)
- Your `.ddev/config.yaml` or other DDEV config files (it only creates `.ddev/config.smoke.yaml`)
- Your `composer.json` beyond the `drupal/smoke` package entry
- Any files outside the module directory and `.ddev/config.smoke.yaml`
- Global npm packages or global Playwright installations

---

Maintainers
-----------

[](#maintainers)

- [thronedigital](https://www.drupal.org/u/thronedigital)

---

License
-------

[](#license)

GPL-2.0-or-later

###  Health Score

37

—

LowBetter than 83% of packages

Maintenance82

Actively maintained with recent releases

Popularity5

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity46

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

40

Last Release

89d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/25e37a6918d20e4770a540fbdb1d97e9da5b81ce58b9802d69e55d8836a0a6dd?d=identicon)[DanePete](/maintainers/DanePete)

---

Top Contributors

[![DanePete](https://avatars.githubusercontent.com/u/3259683?v=4)](https://github.com/DanePete "DanePete (3 commits)")

### Embed Badge

![Health badge](/badges/thronedigital-smoke/health.svg)

```
[![Health](https://phpackages.com/badges/thronedigital-smoke/health.svg)](https://phpackages.com/packages/thronedigital-smoke)
```

###  Alternatives

[drupal/core-recommended

Locked core dependencies; require this project INSTEAD OF drupal/core.

6939.5M343](/packages/drupal-core-recommended)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
