ScreenshotNeo

BlogGuides

Best Practices for Testing Drupal Websites

Choose the right Drupal test layer, build isolated tests, and verify that browser and JavaScript tests actually run.

By the ScreenshotNeo team4 October 20267 min read

Use the lightest test layer that exercises the behavior you need: unit tests for isolated logic, Kernel tests for code that needs selected Drupal services, functional tests for site behavior, and FunctionalJavascript tests for behavior that depends on JavaScript or AJAX. Add performance assertions when query or cache regressions matter. A useful test suite does not need every layer; it needs tests that exercise meaningful behavior and whose prerequisites are actually available.

1. Choose a test layer for the behavior

Test type Use it for Setup and boundary
Unit Isolated business logic and classes with minimal dependencies. Does not boot a Drupal site. Use Drupal’s UnitTestCase base class.
Kernel Integration requiring a bootstrapped kernel and a minimal set of extensions; selected HTTP output or status, REST, and AJAX checks. Generally lighter than a full functional site when only selected pieces are needed. Normal form submission and session behavior are unavailable or differ.
Functional Site behavior, rendered pages, permissions, and interactions that need a full Drupal instance and simulated browser. Each test starts with a fresh instance. Create its users, configuration, modules, and content explicitly.
FunctionalJavascript Real JavaScript or AJAX behavior in a browser. Needs a working browser and WebDriver/ChromeDriver setup; it is slower and requires more tooling.
Nightwatch JavaScript testing using the framework documented by the Drupal project. Choose it according to the project’s test setup; the cited guidance does not establish it as a replacement for every PHPUnit browser test.

Choose based on the behavior covered, Drupal boot and fixture requirements, runtime and tooling cost, and how closely the test needs to match a real browser. Avoid treating line coverage as a quality score on its own: focus unit tests on behavior rather than structure and wiring, and do not test every line just to increase a percentage. See Drupal’s test type guidance and PHPUnit in Drupal.

2. Set up PHPUnit for the project

Use the Drupal project’s Composer layout, PHPUnit configuration, and compatible PHP and Drupal versions. For new tests, Drupal recommends its base classes: UnitTestCase, KernelTestBase, BrowserTestBase, and WebDriverTestBase. PHPUnit is the standard testing framework for Drupal 8 and later. Paths below assume the project provides vendor/bin/phpunit; depending on the Composer layout, vendor may be beside or above the Drupal root.

# Run the project's configured test suite
./vendor/bin/phpunit

# Run one test file
./vendor/bin/phpunit web/modules/custom/example/tests/src/Unit/ExampleTest.php

Use the actual test path and binary location in your repository. Drupal’s running PHPUnit guide documents configuration, environment variables, and output paths. Depending on the test setup, you may need SIMPLETEST_BASE_URL and SIMPLETEST_DB. BROWSERTEST_OUTPUT_DIRECTORY sets the output location for Kernel and functional test artifacts. Unit tests do not require a working Drupal installation; Kernel and browser tests need additional services.

3. Write tests around outcomes and isolation

Keep each test tied to a meaningful result: a returned value, response status, rendered content, access decision, form behavior, browser interaction, or performance regression. Avoid relying on a developer’s local site state.

  1. Identify the behavior and the narrowest layer that can exercise it.
  2. Declare the modules and services the test needs.
  3. Create required users, roles, permissions, configuration, and content within the test.
  4. Perform the action under test and assert the observable outcome.
  5. Review skipped and incomplete tests and any emitted output as well as the final process status.

A BrowserTestBase test installs a fresh Drupal instance. Supply any non-default modules and all required accounts, permissions, configuration, and content. This makes prerequisites explicit and tests more reproducible. Drupal’s guide to functional tests with a simulated browser describes this model.

4. Use Kernel HTTP tests within their limits

Kernel tests can make useful lightweight HTTP assertions for selected routes or responses. They are a good fit when a full browser test is unnecessary and the behavior can be checked with the smaller bootstrapped environment. They do not provide normal form-submission support or ordinary page-request session semantics, so choose a functional test when those are part of the behavior being tested.

See Drupal’s documentation for programmatic HTTP requests in Kernel tests before relying on them for request behavior.

5. Run JavaScript tests only with a working browser driver

Use FunctionalJavascript tests when the result depends on JavaScript or AJAX: for example, an interaction that must execute in a browser to produce the behavior under test. Do not make every page assertion a browser test; the real browser adds tooling requirements and execution time.

  1. Configure PHPUnit according to the Drupal project and test environment.
  2. Make sure Chrome or Chromium and a compatible, reachable ChromeDriver/WebDriver service are available.
  3. Run the test with the project’s PHPUnit binary, using the documented JavaScript test setup.
  4. Inspect browser output when a test fails and confirm the driver actually started and was reachable.

Drupal warns that core/scripts/run-tests.sh can report JavaScript tests as passed when ChromeDriver is not running and those tests did not execute. A green summary is not evidence that browser behavior ran: confirm the driver and inspect test output. Follow Drupal’s guides for creating real-browser FunctionalJavascript tests and running JavaScript tests.

6. Add performance assertions where regressions matter

Performance tests can assert basic metrics such as database query counts or cache requests. They are useful for protecting a performance fix from regression; set expectations around the behavior and environment being tested instead of inventing universal thresholds. Drupal’s described Gander support extends FunctionalJavascript tests and requires Drupal Core 10.2 or later. Check the project’s current compatibility before adopting it. See the Drupal performance testing guidance.

7. Troubleshoot common failures

Symptom Likely cause What to check or change
A functional test cannot find content, a role, or configuration. The test expected state from a developer’s site or omitted a prerequisite. Create the content, account, permissions, configuration, and required modules in the test setup.
A Kernel HTTP assertion behaves differently from a normal browser request. Kernel HTTP tests do not offer normal form submission or ordinary page-request session semantics. Keep the assertion within the supported request behavior or move it to a functional browser test.
A JavaScript test appears to pass but browser behavior was not exercised. ChromeDriver/WebDriver was unavailable, or the chosen runner skipped execution. Run through PHPUnit with the documented setup, verify the browser driver is running and reachable, and inspect output. Do not rely on core/scripts/run-tests.sh for JavaScript tests.
A browser test fails before reaching its assertion. The browser or driver service is missing, incompatible, or unreachable. Check Chrome/Chromium and the matching driver, service availability, and browser test output.
A test passes locally but is unreliable in another environment. It depends on undeclared local configuration, modules, fixtures, or services. Make prerequisites explicit and use the project’s configured test environment.
The suite runs slowly. Tests use a heavier layer than their behavior requires, or real-browser tests cover behavior that does not depend on JavaScript. Use unit or Kernel tests for suitable isolated behavior; reserve browser tests for behaviors that need them.

8. Keep the suite useful over time

  • Prefer a small test at the cheapest layer that faithfully exercises the behavior.
  • Use functional tests for workflows that depend on a real Drupal site setup, permissions, forms, or rendered pages.
  • Use real-browser tests for JavaScript and AJAX behavior, and verify that the browser actually ran.
  • Make setup reproducible and assertions specific to user-visible or operational outcomes.
  • For performance fixes, add an appropriate regression assertion and account for the project’s Drupal version and environment.
  • Check compatibility before copying commands or configuration across Drupal, PHP, PHPUnit, and browser-driver versions.

9. Capture rendered Drupal pages for visual review

Automated behavior tests answer whether a route, permission, or interaction works. A screenshot can help review the rendered page’s layout and visible content, but it does not replace assertions about application behavior. For a repeatable capture, use the same URL, viewport, authentication state, and page readiness conditions across runs.

Or skip the browser setup

For a screenshot of a Drupal page, call ScreenshotNeo’s API with one GET request. See the ScreenshotNeo API documentation for options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Replace the example URL with a page you are authorized to capture. ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Learn about ScreenshotNeo or sign up for 1,000 free screenshots a month with no card.

Frequently asked questions

Should every Drupal project use all four PHPUnit test types?

No. Choose layers based on the behavior and setup needed; the official guidance describes the options, not a requirement to use every one.

Can a Kernel test prove that a form works for a user?

Not when the behavior depends on normal form submission or ordinary session semantics. Use a functional test for that workflow.

When should I add a real-browser test?

When JavaScript or AJAX is part of the behavior and needs to execute in a browser to validate the result.

Does a passing test command prove the JavaScript test ran?

No. Confirm that the browser driver was available and inspect the test output; Drupal documents a runner caveat when ChromeDriver is not running.

What does a screenshot tell me that a functional test does not?

It gives a visual artifact for reviewing rendered appearance. It does not establish that permissions, forms, or other application behavior work correctly.