Sorry, you need to enable JavaScript to visit this website.

GitHub Actions

Behat Screenshot, a picture of the page your test failed on

The failure message that leaves out the page: a failed browser test

If your platform has automated browser tests, you have read this message. A check looked for something on the page and did not find it. That is the whole report.

What you actually want to know is what the page was showing at that moment:

  • whether it rendered at all
  • whether it sent the visitor somewhere unexpected
  • whether a form came back carrying an error nobody has seen before

Without that, fixing it is guesswork. The loop goes like this:

  • change something
  • push
  • wait for the pipeline
  • read a message that still does not describe what happened

Each of those loops costs a real slice of the day, and after 2 or 3 of them the temptation to write the test off as unreliable and move on gets harder to resist. That is the moment a test suite starts losing the team's trust, and a suite nobody trusts stops protecting the platform.

Capture the page, not just the verdict: HTML and an image on failure

behat-screenshot(Opens in a new tab/window) is a Behat extension we wrote for exactly that moment. Behat is the testing tool that drives a real browser through the journeys a platform cannot afford to break:

  • signing in
  • searching
  • submitting a form
  • completing a purchase

When one of those journeys fails, the extension captures the page as evidence.

It saves 2 things, because either one alone leaves a question open. The rendered HTML is what you need to see what the page really contained, down to the element that was missing. The image is what you need to see what a person would have seen, which is where layout and rendering problems show up and where markup alone tells you nothing.

Together they usually answer the question in seconds, rather than in another pipeline run.

There are 2 ways to trigger a capture. It happens automatically when a scenario fails, and a test author can also capture deliberately, part-way through a scenario, from a single step. That second mode earns its keep when a journey passes but something along the route looks wrong.

Turned on in configuration, not in your test code: adoption without a rewrite

How much a tool costs to adopt decides whether it spreads. This one is switched on in a project's Behat configuration file rather than composed into the test code, so a team can enable it without touching a line of its own suite, and turn it off just as easily.

Small decision, large consequence: it is why the extension ends up switched on across whole platforms rather than on the single project whose developer went looking for it.

The evidence has to leave the build: publishing screenshots as artifacts

A capture nobody can open is not evidence. The extension writes where a build pipeline collects and publishes artifacts, so whoever picks up a failed run opens the page straight from the build interface instead of trying to reproduce the failure locally and hoping it happens again. That closes the loop from "the test failed in CI" to "here is the page it failed on".

For a client, that reads as a shorter path from a red build to a working site: the team spends the afternoon fixing the fault rather than reproducing it.

We took the same idea a step further in Vortex(Opens in a new tab/window), our open-source Drupal(Opens in a new tab/window) project template, which publishes captured screenshots somewhere a reviewer will actually look. Evidence that arrives where the conversation is already happening gets used. Evidence sitting in a container does not.

In service since 2017: shipped with Vortex and used beyond our own work

The extension was already part of our testing stack by 2017, documented alongside the rest of our Behat tooling on a long-running clinical trial platform, and it has been maintained ever since. It ships with Vortex, and it is loaded by the test configuration of every repository in the Victorian Government's central content platform.

The part we value most is the part we did not write. Developers with no connection to our practice and no connection to our clients have contributed patches to it, which is a signal no self-authored package list can manufacture. People send fixes to tools they depend on.

In our testing stack since 2017, shipped with Vortex, and carrying patches from developers outside the practice.

For a client, that is the quiet part of the value: the tooling under your test suite is maintained by the same people who maintain your platform.

It sits in a small family we maintain and use daily:

Used together, they mean a failed run explains itself.

For teams whose test failures do not explain themselves

Everything on this page is 2 of our services pointed at our own tooling: the development of the extension, and the ongoing support and maintenance that has kept it current since 2017. The same standard sits behind our AI-assisted delivery: however a change gets written, a failing test still has to show you the page before anyone calls it safe.

If you run a platform whose browser tests the team has started ignoring, or a pipeline whose failures nobody can diagnose without re-running them locally, this is the shape the engagement takes: the critical journeys get covered by tests, every failure captures the page behind it, and the evidence lands where the person picking up the build will see it.

If your team has started skipping a red test because nobody can tell what it means, show us the failing run.

Environment Detector, one reliable answer to where your site is running

The question a site answers before anything else: which environment is this?

A surprising amount of what a website does depends on knowing where it is running:

  • debug output belongs on a developer's machine and nowhere near the public site
  • caching should be aggressive in production and out of the way while someone is working
  • sample content and test users are useful in a throwaway preview build and unwelcome anywhere else
  • emails should only reach real customers from the real site

So every project asks the same question early in its startup: what kind of environment is this?

The awkward part is that each hosting platform answers it differently, with its own signals and its own vocabulary for its tiers, so the environment a team integrates in goes by a different name on each one.

The result is familiar. Each project grows its own small block of detection code, written fresh, a little different from the last one.

It holds up until a platform adds a tier or a project gains an environment that block was never told about. Then the site behaves as though it were somewhere else, in the one place where being wrong is expensive.

One call, nothing to configure: 6 environment types

environment-detector(Opens in a new tab/window) answers the question in a single call, with no setup. It resolves the environment to one of 6 types:

  • local
  • CI (an automated build)
  • development
  • preview
  • stage
  • production

Everything else in the codebase simply branches on the answer.

It recognises where that code is running:

  • the hosting platforms teams actually run on: Acquia, Lagoon, Pantheon, Platform.sh, Skpr and Tugboat
  • the automated build systems where the same code also runs: GitHub Actions, GitLab CI and CircleCI
  • what it is sitting in underneath: a plain machine, a container, or a local development tool such as DDEV or Lando

Telling a preview apart from development is worth more than it sounds. A short-lived environment built for a single change is not the shared space a team integrates in, and once the difference is visible in code, the settings that follow can finally be right.

For a client, that reads as a preview link that behaves like a preview, and a production site that behaves like production.

Built so a wrong answer is loud: it stops instead of guessing

A detector is only worth having if you can trust it, so the design prefers certainty over cleverness.

A single hosting platform can be active at a time. If 2 announce themselves at once, that is a genuine misconfiguration rather than a riddle to solve, so the library stops instead of guessing.

When a platform is active but cannot place an environment in a tier it recognises, the fallback leans the safe way, towards a shared development environment, and it does not downgrade a platform already known to be production.

There are 2 failures worth engineering against: local settings reaching the live site, and production settings reaching a laptop. The design guards against both rather than relying on everyone remembering.

For a client, that reads as one less way for a site to misbehave after a deploy: debug output, caching and test data follow the environment the code landed in.

One line in a Drupal settings file, and a plain call anywhere in PHP

For Drupal(Opens in a new tab/window) the integration is a single line in the site's settings file. That line works out the environment and applies the settings that belong with it, so trusted hosts, file paths and caching are correct wherever the code has landed, with no per-project block to maintain.

Outside Drupal it works through a plain call in any PHP application. It is also built to be extended, with the same pieces the built-in platforms and frameworks use.

Drupal is the framework integration that ships today, and the model underneath it was designed to be framework-agnostic from the start.

Why we keep it public: shipped in Vortex, free to adopt

Solving this once is the entire argument. It ships as part of Vortex(Opens in a new tab/window), our open-source Drupal project template, so every project we start already knows where it is running, and supporting a new host becomes a package update rather than an edit in every codebase.

One call, 6 environment types, and the same answer on every supported host.

It is open source and free to use, published on GitHub(Opens in a new tab/window), so any team can adopt it without ever speaking to us. That is deliberate.

This is unglamorous plumbing, it is the same in every project, and no budget should be paying to write it a second time.

When we take on a platform, that habit comes with us: the generic parts are already solved, so the effort goes where your platform is actually different.

For teams whose environment logic is hand-written in every project

Everything on this page is 2 of our services pointed at our own tooling: the development of the library itself, and the ongoing support and maintenance that keeps it current as hosting platforms change. The same standard applies to our AI-assisted delivery: whatever writes the change, the environment it lands in is decided by one tested library rather than by a block of code copied between projects.

If you run a Drupal or PHP platform whose settings file carries its own environment logic, or a team working across several hosts that each name their tiers differently, this is the shape the engagement takes. The detection moves into one tested library, the per-environment settings follow from it, and adding a host later is a version bump instead of an edit in every repository.

If nobody on your team can say for certain what your production site would do on the day it decided it was staging, send us your settings file.

BATS Helpers, assertions for testing shell scripts

The code that runs closest to production: deployment scripts

Think about what actually happens when your website is deployed. A script does the work:

  • copies a database
  • runs updates
  • clears caches
  • imports configuration
  • sends a notification
  • hands the environment over

It runs on a developer's laptop, again in the build pipeline, and again on production hosting, usually holding credentials that reach real data.

Now think about how that script is tested. In most teams the application code has a test suite, a coverage report and a review process. The deployment scripts have none of that, and they are the ones running in the highest-consequence environment in the system.

That gap is not carelessness. It is that testing shell has always been awkward.

Why shell testing stops before it starts: no assertion vocabulary

There is a perfectly good test runner for Bash. BATS, the Bash Automated Testing System, will run your test files and report pass or fail.

What it does not give you is the vocabulary that makes tests worth reading: a way to assert that a command printed the right thing, to stand in for a command that would otherwise call a live service, to build a throwaway directory of fixtures, or to run the same check across a list of cases.

Without those pieces, each project ends up inventing its own version of them, the tests get written once, and shell testing quietly stops. We wrote the missing layer and published it instead.

What the library provides: assertions, command mocking and fixtures

bats-helpers(Opens in a new tab/window) sits on top of BATS and supplies the assertions and helpers a real test suite needs:

  • assertions for command output, strings, files, directories and Git repositories, so a check says what it means in one readable line
  • command mocking, with per-call output, exit status and side effects, so a script that would otherwise call a hosting API or a live database can be exercised safely
  • a step runner for asserting a sequence of calls in the order they should happen
  • a data provider for running a function across many cases
  • fixture and file utilities for building and restoring a sandbox directory
  • a helper for driving interactive prompts with scripted answers

It installs from npm next to bats-core, and it is developed in the open under an open-source licence. Its own suite runs on every change through GitHub Actions, with dependencies kept current automatically.

Where it earns its keep: the Bash underneath Vortex

The library exists because of Vortex(Opens in a new tab/window), our open-source Drupal(Opens in a new tab/window) project template. Underneath Vortex is a substantial amount of Bash: a provisioning script written to behave identically on a laptop, in the build pipeline and on hosting, plus the tooling that deploys, notifies, and downloads and exports databases.

Those are the scripts where a surprise is most expensive. Because bats-helpers exists, they are covered by tests that run on every change, which means we can improve them without wondering what we might have broken.

Tested deployment scripts, checked on every change, inherited by every project built on Vortex.

Every project built on Vortex inherits that safety, whether or not anyone on the team ever opens a Bash file. For a client, that reads as a quieter deployment: the scripts that touch your database and your live environment are covered by tests that run before the change does.

The standard, not the package: infrastructure scripts are code

The library itself is small. The habit behind it is the interesting part.

Infrastructure scripts are code. They run where mistakes are most expensive, and they deserve the same treatment as everything else:

  • version control
  • review
  • tests that run automatically
  • a failure that shows up in a pipeline rather than in front of your audience

Testing deployment scripts is still uncommon in our industry, which is precisely why we treat it as a baseline rather than a nice-to-have. For a client, that is the difference between trusting a deployment script and knowing what it does.

When we take on a platform, that discipline comes with us.

For teams whose deployment scripts are untested

Everything on this page is 3 of our services pointed at our own tooling: development of the library, the DevOps engineering it exists to protect, and the ongoing support and maintenance that keeps both current. The same standard applies to our AI-assisted delivery: whatever writes the change, the test suite is what decides it is safe to ship.

If you run a platform whose deployments are hand-run scripts nobody has tested, or a build pipeline whose shell nobody wants to touch, this is the shape the engagement takes. The infrastructure code gets read, covered by tests, and moved into the pipeline, so the failure you would have found in production shows up in a build instead.

If nobody on your team can say with confidence what your deployment script does on its bad day, show us the script.

CivicTheme, an open-source design system for government

Everyone builds the same components, separately: navigation, cards and accessible forms

Every public-sector website needs the same handful of things:

  • a navigation people can actually use
  • a card, a button, a form that works with a screen reader
  • pages that read unmistakably as official

Most organisations pay to build all of it again, from scratch, on every project.

The cost is not only the first build. It shows up again when a component changes and nobody can say which of your sites carries which version, and again when accessibility arrives late as a remediation ticket rather than early as a default. And the sites drift apart until services meant to feel like one government no longer do.

A shared design system converts that repeated spend into a single maintained asset. Designing one is the easy half. The hard half is maintaining it so that the teams downstream can take an update without fear.

What CivicTheme is: a UI Kit, a Drupal theme, modules and a Figma source

CivicTheme(Opens in a new tab/window) is an open-source, component-based design system published by Salsa Digital(Opens in a new tab/window). Its own documentation puts the purpose plainly: it was created "so governments and corporations can rapidly assemble modern, consistent and compliant digital experiences".

4 parts ship, and they stay in step:

  • a CMS-agnostic UI Kit of components built with HTML, CSS and JavaScript, browsable in Storybook
  • a Drupal(Opens in a new tab/window) theme that connects the content model to those components
  • supporting modules, including one that adapts the theme to the federal GovCMS(Opens in a new tab/window) platform
  • a Figma design source that carries the same version number as the code, so a designer and a developer are provably looking at the same release

Accessibility sits in the baseline rather than in each agency's budget. The documentation is explicit: "The components have been built and assessed to comply with WCAG accessibility standards 2.1 AA out-of-the-box."

Our part in the work: architecture, release engineering and maintenance since 2021

Salsa Digital publishes CivicTheme, hosts it and runs its community. We wrote its first commit in September 2021 and have provided the engineering behind it ever since:

  • architecture of the component system
  • development of the components, the theme and the supporting modules
  • release engineering, to drupal.org and to npm
  • ongoing maintenance as Drupal, PHP and GovCMS move forward

Everything is developed in one repository and published out to the separate ones people install from. Develop together, ship separately: that one decision lets a small team keep a theme, a component library, companion modules and a documentation site moving in step.

Underneath it, the build and continuous integration run on our own open-source project tooling, the same tooling we bring to client platforms. For a client, that reads as inheritance: the build and test discipline behind a public design system arrives with us on your own platform.

Making an update safe to take: visual regression and schema update tests

Components exist in 2 forms, Twig templates and Drupal Single Directory Components, so that Drupal consumers and non-Drupal consumers each get the implementation they need. Keeping 2 versions of every component honest by hand would be untenable, so parity is machine-enforced: tooling synchronises them, and a check command fails the build the moment they drift.

The primary defence is visual. A unit test cannot tell you that a button moved 4 pixels, and teams downstream notice.

So every pull request captures screenshots of the components, publishes a side-by-side comparison and posts the link back into the review conversation where someone will actually read it. The same pipeline does double duty: one comparison catches a visual regression against the released version, the other catches drift between the 2 component implementations.

Then there is the question every organisation actually asks: what happens to our site when we take the update? Because the system ships configuration for content types, fields and site settings, updates are rehearsed against committed database snapshots, both schema-only and schema-with-content, before they reach anyone.

For a team downstream, that is what makes an update routine rather than a project: the change has been rehearsed against a database shaped like yours before it reaches you.

Release rules follow the same instinct: on drupal.org, where a release cannot be deleted, only the development branch is ever pushed.

Where it is now: Drupal 10 and 11, GovCMS, and 4 content profiles

Live and in service. Published on drupal.org and on npm, documented at docs.civictheme.io(Opens in a new tab/window), with the component library and a live demonstration site online.

4 industry content profiles ship alongside a default:

  • Corporate
  • Government
  • Higher Education
  • Health

Each is a complete worked example rather than a starter shell, so a team can watch the system do the job before committing to it.

It tracks the platform forward: Drupal 10 and Drupal 11, current PHP, GovCMS support kept current. A steady release cadence has carried it across 4 years, with a pre-release channel so teams can test a release candidate before they take it.

A single maintained design system, 4 industry content profiles, and 4 years of steady releases.

4 years on, we are still the engineering behind it.

For teams rebuilding the same components on every site

Everything on this page is 4 of our services running on one public open-source design system: the architecture of the component system, the development of the components and modules, the DevOps and release engineering that ships them, and the ongoing support and maintenance that keeps all of it current. The same standard applies to our AI-assisted delivery: whatever writes a component, the visual comparison and the schema update tests are what decide it is safe to release.

If you run a government agency, a university, or any organisation with a family of sites that keep drifting apart, this is the shape the engagement takes: a component library that is genuinely shared, accessibility in the baseline rather than in a remediation ticket, and updates rehearsed before they reach your site.

If you are weighing up CivicTheme for your next site, or you already run it and want taking an update to be routine, tell us which sites you are trying to keep in step.

Git Artifact: safe build deployments to git-based hosting

What you push is what production runs: deploying from a git branch

A good deal of hosting still works the simplest way there is. You push to a git branch, the platform notices, and it deploys whatever it finds there. It is a clean model, and it puts an awkward requirement on your repository.

Your repository is not what production needs. It carries the parts a team builds with:

  • development tooling
  • test suites
  • source assets
  • build configuration

What it does not carry is the compiled CSS, installed dependencies and vendor code the running site depends on. Something has to bridge that gap, and for most teams that something is a deployment script written once, under time pressure, and then trusted forever.

That step is worth thinking about carefully, because of everything in a pipeline it is the one that can fail quietly.

A build that breaks stops the pipeline and tells you. A deployment that publishes the wrong contents raises nothing at all. It simply goes live.

A deployment step designed to refuse: dry runs, no-change guards and tag collisions

git-artifact(Opens in a new tab/window) builds the deployable artifact inside CI and commits only that to the separate repository your host watches. Development tooling stays out of the deployed package, which keeps the running site lean and narrows what is exposed there.

The part we care about most is what it does when something is not right. It refuses in 3 independent ways, each covering a different way a deployment can go wrong without anyone noticing:

  • pushing is a dry run unless you explicitly ask for a deploy, so the default invocation prints what it would do and writes nothing to the remote
  • if the artifact it has just built is identical to what is already deployed and no new tags are involved, it stops rather than adding noise to the history
  • if a tag would collide with one already on the destination, it stops there too

For a client, that reads as a deployment that stops rather than guesses: when something does not look right, the pipeline says so instead of publishing quietly.

Underneath those refusals sits the ordinary work of getting an artifact right:

  • a manifest controls exactly what goes in
  • a watermark in each commit message lets the tool find where it left off on a repository it does not own, which is what makes incremental artifact builds possible at all
  • tags are carried across with collision detection
  • symlinks survive the copy instead of being flattened into broken files

All of it was built test first, against a suite that provisions real throwaway git repositories rather than mocking them. Testing a deployment tool that way is slow and awkward. It is also the only way to know the safety rails actually fire.

Where it came from: a 2017 client audit, extracted as open source

git-artifact started inside client work. In 2017 we were auditing a dual-sector university's TAFE website platform, and we rebuilt its deployment step from the ground up: written test first in PHP, with the refusals above and a test suite that exercised every one of them.

2 years later, we did the part that mattered more. We lifted it out of the client's codebase and published it as a standalone open-source package, so every project after it inherited the same tested deployment step at no cost to anyone.

That is a habit rather than a one-off. When client work produces a piece of engineering that is better than that one project strictly needed, we take it out, publish it and maintain it. git-artifact is the earliest example and still the clearest one.

For a client, that habit reads as inherited engineering: the deployment step on your platform arrives already built, already tested and already maintained, rather than written fresh under time pressure.

Still deploying, 9 years on: Vortex, Acquia Cloud and Lagoon

git-artifact is maintained and in service 9 years after the first version and 7 after it became a package. It ships with Vortex(Opens in a new tab/window), our open-source Drupal(Opens in a new tab/window) project template, and it is the deployment mechanism for Vortex-based projects on git-deployed hosting, including Acquia Cloud and Lagoon.

9 years in service, 7 of them as an open-source package other projects inherit.

For a tool whose whole job is to say no at the right moments, longevity is the measure that counts. It has been doing the same careful thing in other people's pipelines for the better part of a decade, and the teams using it rarely have to think about it.

For pipelines whose deploy step nobody has read

Everything on this page is 4 of our services pointed at our own tooling: the architecture of a deployment step, the development of the package that implements it, the DevOps engineering it exists to protect, and the ongoing support and maintenance that has kept it current since 2017. It is the same rule we apply to AI-assisted delivery: however quickly a change is written, it reaches production through a deployment step that has been tested.

If you run a site deployed from a git branch, on hosting like Acquia Cloud or Lagoon, and the step that gets your build there is a script somebody wrote once, this is the shape the engagement takes. The deploy step becomes a tested, maintained package: a dry run by default, a refusal when something does not look right, and only the built site in front of your visitors.

If nobody on your team can say what your pipeline actually pushes to production, show us your deploy step.

Behat Steps, ready-made Behat test steps for Drupal

The tests every Drupal project writes twice: logins, content, email, logs

Automated tests for a website are usually written in near-English, a sentence per action. "Given I am logged in as a user with the administrator role." "Then an email should be sent to the applicant."

That is deliberate: the people who decide what a site must do can read what is being checked.

Behind each of those sentences sits real code. Someone has to implement, in PHP, what "logged in as an administrator" means, what counts as an email being sent, and how a test knows a file actually downloaded.

And the sentences repeat from project to project:

  • sign in as a role
  • create content
  • check an email went out
  • confirm nothing landed in the site's error log

Writing them the first time costs a few days. Keeping them is the part nobody budgets for.

They live in your repository, and they need attention every time Drupal(Opens in a new tab/window) moves. They are never what your project is actually about, and when they break, testing quietly stops.

A library you take a piece at a time: Behat steps as PHP traits

behat-steps(Opens in a new tab/window) is that set of steps, written once, tested and maintained. It ships as PHP traits, so a project imports the pieces it wants and nothing else.

Take the email assertions without inheriting anything you did not ask for, and keep every step your team has already written sitting alongside them.

Small parts rather than a single base class is what makes the library safe to adopt gradually. You add a piece, nothing you had before changes, and you decide about the rest later.

It covers the common ground of Drupal testing:

  • authentication and roles
  • content
  • email
  • file downloads
  • media
  • error-log checks

For a client, that reads as a shorter start: the test suite on your project begins with the common ground already written and already maintained.

Where it came from: a Drupal test context cut from 300 lines to 46

It started on a real project in 2018, a Victorian government health department website reached through a government digital agency.

That project had built itself a thorough set of test steps:

  • email assertions
  • file downloads
  • media handling
  • error-log checks

All of it solid work, and none of it specific to a health department.

So we pulled the generic parts out into a package we would maintain. The project's own test class went from roughly 300 lines of bespoke step definitions to 46 lines that simply compose the imported ones, with every check it had before still in place.

The department kept its coverage and stopped owning the plumbing underneath it. Every project we have worked on since has had those steps for free.

The decisions that make it safe to rely on: strict error-log checks

A library like this earns trust in the details. Checking Drupal's error log after every scenario is the classic source of false alarms, because some scenarios are supposed to trigger an error.

The easy answer is to soften the check for everyone. Instead, a test can declare that it expects an error, so the check stays strict everywhere else.

Sometimes the behaviour that needs adjusting sits further upstream, in the shared Drupal testing driver the library builds on. When that is the right home for a fix, it goes there rather than being worked around on every project, and we have been contributing to that driver for years.

Where it ended up: whole-of-government Victoria and community radio

A library extracted from a single health department project went on to underpin the Victorian Government's Single Digital Presence, the shared platform behind vic.gov.au and the agency sites around it. Years later it was doing the same job on a community radio station's rebuild.

It ships as part of Vortex(Opens in a new tab/window), our open-source Drupal project template, so every project we start inherits it.

It has been maintained since 2018, across Drupal 7 through to Drupal 11, which is a long time for a package to stay useful in a platform that changes as much as Drupal does.

300 lines of test glue became 46 lines of composition, and a package maintained since 2018 across Drupal 7 to Drupal 11.

It also carries contributions from developers with no connection to DrevOps. People do not send patches to code they are not depending on.

For a client, that track record is the point: the steps your suite depends on have outlived the projects that introduced them.

What it adds up to: Drupal testing that starts already built

The library is public, so any Drupal team can use it without ever talking to us. Testing gets skipped when it is expensive to start, and the quickest way to make it cheap is to stop asking every project to rebuild the same foundations.

When we take on a platform, that groundwork is already done and maintained. The effort goes into the parts that are actually yours.

For teams rewriting the same Drupal test steps

Everything on this page is 2 of our services pointed at our own tooling: development of the library, and the ongoing support and maintenance that has kept it current from Drupal 7 through to Drupal 11. The same standard applies to our AI-assisted delivery: however fast the code arrives, the automated checks are what decide it is safe to ship.

If you run a Drupal site whose test suite nobody wants to open, or a team writing the same step definitions on every new project, this is the shape the engagement takes. The generic steps come from a maintained library, your own steps sit beside them, and the only checks you own are the ones specific to your business.

If your last Drupal upgrade was verified by hand because nobody trusts the automated suite, tell us what it covers today.

Vortex, the Drupal project template

The 2 costs nobody puts on the budget: setup and drift

Ask a team that runs more than 1 Drupal(Opens in a new tab/window) site where its time goes, and 2 answers come back.

The first is setup. Every new project spends weeks on the same groundwork before anyone builds a feature:

  • local environments
  • CI pipelines
  • hosting wiring
  • code quality tooling
  • a test harness

The second is drift. Once a project is running it grows its own arrangement of tooling, so a year later moving a developer between 2 of your own sites means learning 2 different machines.

Neither cost appears on a budget line, and both are paid again on every project.

Installed once and kept current: local environment, CI pipelines and hosting

Vortex(Opens in a new tab/window) is a Drupal project template: a tested foundation you install once, pick features from, and then keep current for the life of the project.

The template is the pre-configured Drupal project itself, wired together and working on day 1:

  • a containerised local environment
  • CI pipelines
  • hosting integrations
  • code quality tooling
  • a testing harness

Documentation ships with it, including an onboarding checklist for new team members. An installer adds only the features you chose, from hosting and CI provider through to theme and AI agent instructions. On the current line that tool is the installer; the next major moves the job into a dedicated Vortex CLI.

That upgrade path is what separates Vortex from a starter kit, which is a one-time copy frozen at whatever it got right in its year. Vortex keeps the path open, so a project set up in its first year can adopt improvements made in its third through the same tool that created it.

For a client, that reads as continuity: the foundation under your site keeps getting the improvements made after your project started.

Whichever features you pick, local, CI and hosting all run the same provisioning path. That single decision is where "works on my machine" stops being a recurring argument.

The quality gates arrive switched on: secret scanning, static analysis, 90 per cent coverage

Plenty of templates leave quality tooling as an exercise for the reader. Vortex ships the gates configured and running across the whole codebase:

  • secret scanning
  • dependency auditing
  • container linting
  • static analysis
  • coding standards

A 90 per cent code coverage threshold fails the build by default, with the result posted back onto the pull request. Visual regression waits behind an opt-in label, so an expensive check runs only on the changes that warrant it. Third-party CI actions are pinned by commit hash, a ready answer to a supply-chain question many teams are now being asked.

Every gate can be switched off with a single variable, and that matters more than it sounds. A team can adopt the pipeline on day 1 without being blocked by it, then tighten it as they go. It is the difference between a pipeline people keep and one they quietly delete.

The documentation is held to the same standard: linted, tested, and gated against drifting out of step with the code.

9 years in and busier than ever: 111 releases across Drupal 7 to 11

The usual failure mode of a project template is quiet abandonment: excellent for 18 months, then attention moves elsewhere and the repository goes still.

Vortex began in July 2017 and has shipped 111 releases since. It has tracked Drupal 7, 8, 9, 10 and 11 across their lives, and today it targets Drupal 11.

Releases go out on a stated monthly cadence, borne out across the recent run. A second major version is already published alongside the first, with the release plumbing built so that promoting a new major to the default is a single configuration change.

It is being developed more actively today than in any year of its life.

111 releases since 2017, across Drupal 7 to Drupal 11, and busier today than in any year of its life.

For a client, that history reads as low risk: the foundation under your project is maintained by the team you are already working with.

Open source under GPL-3.0, and used on the work we are paid for

Vortex is open source under GPL-3.0 and developed in public: repository, issue tracker, releases and documentation are all open. Community support runs through Drupal Slack and GitHub issues, and paid support is available for teams that want a commercial contact behind it.

We use it ourselves, which is the part that keeps it honest. Our own website runs on Vortex and is offered openly as a reference for what a long-lived Vortex project looks like, and the client platforms we support run on it too. The template meets real delivery pressure, not only its own test suite.

The flow runs both ways. Tools that began inside client work ship with Vortex, including our Behat step library(Opens in a new tab/window), CI runner image(Opens in a new tab/window) and artifact deployer(Opens in a new tab/window). Pieces that outgrow it are published in their own right.

For teams paying the setup cost on every Drupal project

Everything on this page is 4 of our services pointed at our own product: the architecture of the template, its development, the DevOps and hosting wiring it ships with, and the support and maintenance that has kept it current since 2017. The same template ships the AI agent instructions behind our AI-assisted delivery, and the gates decide what is allowed to merge.

If you run a portfolio of Drupal sites, or a team that starts a new project every few months, this is the shape the engagement takes: your sites build, test and deploy the same way, new developers onboard against a stack they have seen before, and improvements arrive through the same tool that set the project up.

If your Drupal projects have each grown their own build and nobody can move between them, tell us what your setup looks like today.