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

Behat

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.

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.

Victorian Department of Health and Human Services websites

When a website is where people go for health, housing and concessions

People arrive at a health and human services website with a real question, often at a difficult moment:

  • someone checking whether they qualify for a concession
  • a parent trying to understand a family service
  • a nurse looking up clinical guidance
  • an adult asking for the records of their own childhood in care

The site either answers the question or it does not.

The Victorian Department of Health and Human Services ran one of the largest web estates in the state, covering consumer health information, concessions, housing, disability, family services, records access, and clinical guidance for the health sector. Many audiences, many editorial teams, one shared expectation that the information will be right and reachable.

Drupal engineering inside the Salsa Digital partnership

The work reached us through Salsa Digital(Opens in a new tab/window), a government digital agency whose teams carried strategy, design and content across the department's properties. We joined as their engineering partner and stayed for 4 years, providing:

Some of it was new architecture. A lot of it was the quieter work of making several teams' releases predictable.

Several audiences, one Drupal codebase and one content platform

3 of the department's public websites, serving citizens, funded organisations and service providers, were consolidated into a single Drupal codebase. One map of the estate drives everything downstream: the build, each site's settings, deployment, the test suite and the theme. Each site keeps its own visual identity through configuration rather than code, so an editorial team can change how their site looks without waiting for a developer release.

A separate content platform then decoupled the department's content from its presentation, so several front ends could share one editorial spine. It carried a consumer health encyclopaedia, professional guidance published as open data, service directories with eligibility and access details, and a records access service. Editors describe intent, such as whether a page offers text to speech or a printable version, and each front end decides how to honour it.

Built for the people on the other end: accessibility and a link-safe migration

The front ends built on that platform are server-rendered JavaScript applications, and 2 things matter about how they behave. Every part of a page is assembled independently, so one troublesome component degrades on its own instead of taking the page with it.

The second is accessibility, treated as implementation rather than aspiration:

  • screen-reader headings
  • focus management on dialogs
  • proper tab and panel semantics for medical citations
  • built-in text to speech

Migration mattered for the same reason. People bookmark government pages, print them, cite them in letters and share them in community groups, and search engines have years of them indexed.

The migration was built so those links survived: content from the legacy system was rebuilt as structured, reusable page components, and old-style page and document addresses were resolved on the new platform and served the right file. For a client, that is what a link-safe migration buys: the address saved in a bookmark or printed in a letter still leads somewhere after launch day.

Built once, released safely: containers and continuous integration

Underneath all of it we changed how the code was built and shipped. In 6 days in 2018, a live government Drupal site moved onto containers and managed dependencies, with the application built exactly once, in continuous integration, and that identical build used everywhere afterwards.

One self-documenting set of commands ran it, so every developer and the pipeline did the same thing. Releases were designed to fail quietly: a dry run by default, and a release that did not complete cleanly left visitors seeing the site exactly as it was. For a client, that is what a safe release means: a release that does not land costs you a wait, not a broken site.

What it left behind: 2 open-source tools still shipping

We worked across 4 of the department's properties over 4 years, and the tooling outlived the engagement.

2 things that began as answers to real problems on these sites are open source today and still maintained. drevops/behat-steps(Opens in a new tab/window), a library of reusable automated test steps for Drupal, was extracted from the department's sport and recreation site and later became a dependency of the Victorian Government's central content platform.

And the containerised project template built in that week in 2018 is the direct ancestor of Vortex(Opens in a new tab/window), the open-source Drupal project template we ship on every project today.

4 properties over 4 years, and 2 of the tools built for them are open source and maintained today.

That is the part we are proudest of: solving a real problem twice, then giving the answer away.

For government estates where the information has to be right

Everything on this page is 4 of our services running across one government web estate: architecture and development on a shared Drupal codebase, content migration that keeps the addresses people already have, build and release engineering, and accessibility built in rather than bolted on.

If you run a government web estate, a health service, or several public sites that share one editorial team, this is the shape the engagement takes: one codebase behind several sites, migrations that keep existing addresses resolving, and releases safe enough to be routine. The same engineering is available with AI-assisted delivery where it suits the work, at the same tested standard.

If your estate has spread across several ageing sites, or a migration is coming and the links people already have worry you, tell us what the estate looks like.

An economic regulator's website, migrated without losing the record

When the website is the public record: a regulator's statutory publication channel

The organisation behind these websites is a state economic regulator. Its work is public and procedural:

  • it sets the maximum prices people pay for essential services
  • it licenses some of the utilities that provide them
  • it runs formal public reviews with consultation periods, submissions and hearings

Its website is not a brochure. It is the regulator's statutory publication channel, and a determination is published at the moment it appears there.

The organisations it regulates, and the lawyers who advise them, cite that site, and those citations are expected to keep resolving for years. So the content cannot simply be copied across. Every document has to arrive with its identity, its dates, its attachments and its editorial state intact, which makes a replatform a records problem long before it is a website problem.

Migration architecture and platform engineering inside a government programme

A partner agency led the programme and brought us in as their engineering partner for the move onto a government-managed Drupal(Opens in a new tab/window) platform. We worked across both properties, the regulator's main site and a second site for one of the schemes it administers, and provided:

  • migration architecture
  • platform development
  • front-end engineering

Government delivery is rarely a single team. Several suppliers and the regulator's own developer all worked in the same codebase alongside the design and content teams, and keeping that shared build coherent was part of our work.

2 systems, a single set of documents, reconciled across 3 identifiers

The documents the site publishes did not live in the old website. They live in the regulator's enterprise records management system, held as formal records with their own numbers and revisions.

That left 2 sources describing the same material, each holding half the answer. The legacy site knew which document belonged to which review, in what order and on what date. The records system held the authoritative file.

The same document could also be known by 3 different identifiers, depending on which system you asked. So we built the reconciliation. A single lookup resolves a document across all 3 and settles which wins.

Anything the records system has not yet sent gets a placeholder, so a review can be assembled now and completed on the next sync.

A migration you can run more than once, without republishing what was withdrawn

A migration this size is never a single run. You run it, review what landed, correct something, run it again, and that only works if the second run recognises what the first one created.

Legacy pages were known by their web addresses, so we derived each item's identifier from its own path: same path, same identifier, every run. Reviews that reference other reviews are built in a second pass. For a client, that is the difference between a migration you can rehearse and one you get a single attempt at.

The decision we would lead with, though, is smaller and matters more. If an editor had unpublished a page, re-running the migration left it unpublished. On a regulator's site that is not a nicety: a withdrawn determination that quietly reappears is a regulatory problem, not a content bug.

And we proved the result rather than asserting it, comparing the source data against what actually landed in Drupal.

Where the regulator's publishing rules live: review types encoded in the site

Which documents appear under which tab of a review depends on the kind of review it is, and those rules belong to the regulator, so we encoded them in the site itself. An editor publishes a document once, sees only the fields that apply to that review type, and it lands where the rules say it should.

The public gets a timeline that opens at today, and every review page offers an accessible copy of a document on request. For a client, that means the publishing rules live in the platform rather than in a style guide someone has to remember.

What it added up to: a migration delivered in releases, not a single cutover

The work began in 2020 and the build ran across 2021, released more than 70 times inside that single year. That is a release rhythm rather than a big-bang cutover, which is what most government teams are quietly hoping for. The legacy address structure was kept wholesale, including the front page, so published citations pointed at addresses that still existed after the move.

More than 70 releases inside a single year, then 2 years of upgrades by the next team.

Then we handed it over, and it kept going: the team that received the platform carried on releasing and upgrading it through the next 2 years of Drupal work. For a migration, that is the outcome worth measuring. Not only that it landed, but that the next team could keep it moving.

For migrations where the content carries legal weight

Everything on this page is 4 of our services on one government replatform: migration architecture, platform development, front-end engineering, and the deployment work that put both sites on a government-managed platform.

If you run a regulator, a department, or any public body whose website is where decisions are formally published, this is the shape the engagement takes: the records reconciliation settled before the content moves, a migration that can be re-run without republishing anything withdrawn, and the legacy addresses kept so published citations still have somewhere to land. The same engineering is available with AI-assisted delivery where it suits the work, at the same tested standard.

If you are replatforming a site whose documents are cited elsewhere, and nobody can yet tell you what happens to those citations, send us the address structure you have to keep.

Independent Pricing and Regulatory Tribunal website replatform

When the website is the public record: a regulator's statutory publication channel

The Independent Pricing and Regulatory Tribunal(Opens in a new tab/window) is the New South Wales independent economic regulator. Its statutory work includes:

  • setting the maximum prices for water, public transport and council rates
  • licensing private water utilities
  • running formal public reviews with consultation periods, submissions and hearings

Its website is not a brochure. It is the regulator's statutory publication channel, and a determination is published at the moment it appears there.

Councils, utilities and the lawyers who advise them cite that site, and those citations are expected to keep resolving for years. So the content cannot simply be copied across.

Every document has to arrive with its identity, its dates, its attachments and its editorial state intact, which makes a replatform a records problem long before it is a website problem.

Our part in the programme: migration architecture on GovCMS

Salsa Digital(Opens in a new tab/window) led the programme and brought us in as their engineering partner for the move onto GovCMS(Opens in a new tab/window), the Australian government's shared Drupal(Opens in a new tab/window) platform.

We worked across both properties, the regulator's main site and the second site for the energy savings scheme it administers. Our part covered:

  • migration architecture
  • platform development
  • front-end engineering

Government delivery is rarely a single team. Several suppliers, the regulator's own developer and the platform operator all worked in the same codebase alongside the design and content teams, and keeping that shared build coherent was part of our work.

2 systems, a single set of documents: reconciling the records

The documents the site publishes did not live in the old website. They live in the regulator's enterprise records management system, held as formal records with their own numbers and revisions.

That left 2 sources describing the same material, each holding half the answer. The legacy site knew which document belonged to which review, in what order and on what date. The records system held the authoritative file.

The same document could also be known by 3 different identifiers, depending on which system you asked. So we built the reconciliation.

A single lookup resolves a document across all 3 and settles which wins, and anything the records system has not yet sent gets a placeholder, so a review can be assembled now and completed on the next sync.

A migration you can run more than once, without republishing withdrawn content

A migration this size is never a single run. You run it, review what landed, correct something, run it again, and that only works if the second run recognises what the first one created.

Legacy pages were known by their web addresses, so we derived each item's identifier from its own path: same path, same identifier, every run. Reviews that reference other reviews are built in a second pass. For a client, that is the difference between a migration you can correct and one you have to redo.

The decision we would lead with, though, is smaller and matters more. If an editor had unpublished a page, re-running the migration left it unpublished.

On a regulator's site that is not a nicety: a withdrawn determination that quietly reappears is a regulatory problem, not a content bug. And we proved the result rather than asserting it, comparing the source data against what actually landed in Drupal.

Where the regulator's publishing rules live: encoded in the site

Which documents appear under which tab of a review depends on the kind of review it is, and those rules belong to the regulator, so we encoded them in the site itself.

An editor publishes a document once, sees only the fields that apply to that review type, and it lands where the rules say it should. For a client, that means the complexity sits in the platform rather than in an editor's training.

The public gets a timeline that opens at today, and review pages offer an accessible copy of a document on request.

What it added up to: 73 releases, then 2 years of upgrades

The work began in 2020 and the build ran across 2021, released 73 times inside that single year. That is a release rhythm rather than a big-bang cutover, which is what most government teams are quietly hoping for.

The legacy address structure was kept wholesale, including the front page, so the addresses behind inbound links and published citations were preserved rather than replaced.

Then we handed it over, and it kept going: the team that received the platform carried on releasing and upgrading it through the next 2 years of Drupal work.

73 releases inside a single year, then 2 more years of upgrades by the team that received it.

For a migration, that is the outcome worth measuring. Not only that it landed, but that the next team could keep it moving.

For migrations where the content carries the risk

Everything on this page is 3 of our services on one government replatform: migration architecture across 2 sources of truth, platform development on a shared government Drupal platform, and front-end engineering that put the regulator's own publishing rules into the site.

If you run a regulator, a department or any site whose content is the public record, this is the shape the engagement takes: a migration you can re-run and correct, editorial decisions preserved across re-runs, and legacy addresses carried across rather than dropped. The same engineering is available with AI-assisted delivery where it suits the work, at the same tested standard.

If your next replatform has to carry documents that live in a records system rather than in the website, tell us where your documents actually live.

Three federal websites, built where custom code is not allowed

When the platform will not let you write code: the GovCMS SaaS constraint

If you publish on a government platform, you have probably met the trade it makes. GovCMS, the Australian Government's whole-of-government Drupal(Opens in a new tab/window) service, gives agencies accreditation, compliance and managed hosting. In return, its software-as-a-service tier permits no custom code:

  • no bespoke modules
  • no server-side services
  • nothing outside the vetted distribution

It is easy to read that as a wall. Almost every hard requirement a team has met before was answered by writing code, and here that answer is gone.

Our part in the work: architecture, build and migration engineering

A federal government department was moving its public web presence onto GovCMS, and its public channels carry the whole of what it does. A partner agency delivering that move brought us in as its engineering partner, with design carried by a separate studio.

We provided technical leadership, platform engineering and migration engineering:

  • the scaffolding
  • the content models
  • the themes
  • the deployment tooling
  • the migration codebase itself

3 sites on one platform: a main channel, an intake site and a publication site

The engagement covered 3 sites on the same platform:

  • the department's principal public channel, carrying reference information, publications, profiles, news, events and the programmes it funds
  • a standalone public front door that takes structured reports from the public through a single intake form, live in 8 weeks
  • a compact standalone publication site, shipped in 5 weeks alongside it

The main site's content model is built around the department's own subject matter, with its primary reference entities as first-class content rather than tags, so anything published can be placed against them.

Business logic that had to live in configuration: layout modifiers and design tokens

The most transferable piece is layout. Editors wanted per-section control over background images, gradients, colours, padding and height, at chosen breakpoints, so we modelled presentation as content.

Any component can carry any number of modifiers, each a small piece of content with its own media query, rendered as styling scoped to that component. An editor can combine a background image, extra top padding above tablet width and a minimum viewport height, without a developer and without code the platform would have refused. For a client, that reads as independence: your editors restyle a section themselves, and nobody has to ask first whether the platform will allow the code.

We took the same approach to the whole-of-government design system, re-theming it by assigning the department's design tokens into the system's own variables rather than overriding its output. Override the output and every release becomes a merge conflict. Assign tokens and it becomes a version bump.

We also restored the heading structure that paragraph-composed pages quietly lose, which screen readers rely on.

Moving 20 years of public content with no database to read

Roughly 20 years of published material had to move with the site: reference pages, publications, profiles, news releases and the document library behind them. There was no database to read and no API to call, so the source of record became the live public site, harvested and normalised into structured data.

That creates the problem any GovCMS team with a migration ahead of them is about to meet. A migration of this shape cannot be written without custom code, and the destination does not accept custom code. We resolved it architecturally: the migration ran as its own project on the platform-as-a-service tier, where custom code is permitted, and the content landed in the software-as-a-service site.

Archived material was treated as its own class throughout, because 20-year-old content does not have the shape of this morning's media release. And validation was written as configuration, committed beside the migration, so what landed could be checked against what was harvested.

Most migrations assert that they are correct. This one could be asked. For a client, that is the difference between being told a migration landed correctly and being able to check that it did.

What it adds up to: 10 months to build, 3 years of onward maintenance

The main site was built in about 10 months and went into service with 20 years of content behind it.

The measure we like most came later: it was still being maintained and upgraded 3 years after we handed it over, including a platform update and work toward a newer Drupal version, carried by the team that received it.

Built in 10 months, and still being maintained and upgraded 3 years after we handed it over.

A site still being improved by people who did not build it is solid evidence that it was built to be handed on.

For agencies that cannot ship custom code

Everything on this page is 5 of our services running inside one whole-of-government platform: architecture that puts the logic where the platform allows it, development of the content models and themes, the deployment tooling that ships them, migration engineering that could be checked against its source, and accessibility carried through the build.

If you publish on GovCMS software-as-a-service, or you are weighing a move onto it, this is the shape the engagement takes: requirements answered in configuration and theme rather than in modules you are not allowed to ship, a design system consumed by token assignment so an upgrade stays a version bump, and a migration that runs where custom code is permitted and lands where it is not. The same engineering is available with AI-assisted delivery where the work suits it, most obviously across migration mapping and validation.

If you have a requirement your GovCMS site will not let you build, tell us what it is and we will tell you where it can live.

Department of Foreign Affairs and Trade websites on GovCMS

GovCMS: when the platform will not let you write code

If you publish on a government platform, you have probably met the trade it makes. GovCMS(Opens in a new tab/window), the Australian Government's whole-of-government Drupal(Opens in a new tab/window) service, gives agencies accreditation, compliance and managed hosting.

In return, its software-as-a-service tier permits no custom code: no bespoke modules, no server-side services, nothing outside the vetted distribution.

It is easy to read that as a wall. Almost every hard requirement a team has met before was answered by writing code, and here that answer is gone.

Our part in the work: technical leadership and migration engineering with Salsa Digital

The Department of Foreign Affairs and Trade runs Australia's international relations, trade policy, development assistance and consular services, and its public web presence carries all of it. Salsa Digital(Opens in a new tab/window), delivering the move onto GovCMS, brought us in as its engineering partner, with design carried by a separate studio.

We provided technical leadership, platform engineering and migration engineering:

  • the project scaffolding and the deployment tooling
  • the content models behind all 3 sites
  • the themes, including the design-system work
  • the migration codebase itself

3 GovCMS sites for a department with a global remit

The department's public presence was built as 3 sites.

  • the corporate site, its main public channel, carrying country and regional information, publications, profiles, news, events and the programmes it funds
  • tradebarriers.gov.au, the national front door where Australian exporters report the obstacles they meet in overseas markets that are not tariffs: quarantine rules, labelling requirements, customs delays. It takes reports through a single structured intake form, and was live in 8 weeks
  • a site for the Cairns Group, the coalition of agricultural exporting nations chaired by Australia, shipped in 5 weeks

The corporate site's content model is shaped like a foreign ministry, with country pages as first-class content rather than tags, so anything published can be placed geographically.

Business logic that had to live in configuration: layout modifiers and design tokens

The most transferable piece is layout. Editors wanted per-section control over background images, gradients, colours, padding and height, at chosen breakpoints, so we modelled presentation as content.

Any component can carry any number of modifiers, each a small piece of content with its own media query, rendered as styling scoped to that component. An editor can combine a background image, extra top padding above tablet width and a minimum viewport height, without a developer and without code the platform would have refused. For a client, that reads as independence: your editors change how a page looks without booking developer time.

We took the same approach to the Australian Government Design System, re-theming it by assigning the department's design tokens into the system's own variables rather than overriding its output. Override the output and every release becomes a merge conflict. Assign tokens and it becomes a version bump.

We also restored the heading structure that paragraph-composed pages quietly lose, which screen readers rely on.

Moving 20 years of public content: a migration built where custom code is allowed

Roughly 20 years of published material had to move with the site: country pages, publications, profiles, news releases and the document library behind them. There was no database to read and no API to call, so the source of record became the live public site, harvested and normalised into structured data.

That creates the problem any GovCMS team with a migration ahead of them is about to meet. A migration of this shape cannot be written without custom code, and the destination does not accept custom code.

We resolved it architecturally: the migration ran as its own project on the platform-as-a-service tier, where custom code is permitted, and the content landed in the software-as-a-service site.

Archived material was treated as its own class throughout, because 20-year-old content does not have the shape of this morning's media release.

Validation was written as configuration, committed beside the migration, so what landed could be checked against what was harvested. Most migrations assert that they are correct. This one could be asked.

For a client, that is the difference between trusting a migration and being able to check it.

What it adds up to: 10 months to build, still maintained 3 years on

The main site was built in about 10 months and went into service with 20 years of content behind it.

The measure we like most came later: it was still being maintained and upgraded 3 years after we handed it over, including a platform update and work toward a newer Drupal version, carried by the team that received it.

Built in about 10 months with 20 years of content behind it, and still being upgraded 3 years after handover.

A site still being improved by people who did not build it is strong evidence that it was built to be handed on.

For teams publishing inside a platform they cannot extend

Everything on this page is 5 of our services running inside a platform that permits no custom code: architecture that puts business logic in configuration, development within a vetted distribution, migration at 20-year scale, DevOps and hosting through the platform's own tooling, and accessibility work carried in the theme.

If you publish on GovCMS, or on any accredited government platform that fixes what you are allowed to build, this is the shape the engagement takes: presentation modelled as content so editors keep control, design-system work done by assigning tokens rather than overriding output, and a migration that can be checked rather than trusted. The same engineering is available with AI-assisted delivery where it suits the work.

If your GovCMS build has a requirement the software-as-a-service tier will not let you code your way out of, tell us what the platform is blocking.

University of Melbourne teacher candidate assessment platform

Multi-tenancy in practice: a single assessment, many institutions

Some software has to be the same everywhere and different everywhere at once. An assessment used in selecting candidates into teaching degrees is exactly that.

The instrument has to hold its shape wherever it runs, while each institution wants its own:

  • branding
  • cohort
  • admission dates
  • rules about how a candidate gets an account

Build it once and run an instance per institution per cycle: that is the obvious answer, and it is also where the work is. Every new instance starts life as a copy of a working one. A copy is only ever as safe as your ability to prove it still works.

Our part in the partnership: development, testing and maintenance from 2018

The Teacher Capability Assessment Tool is owned by the University of Melbourne and was built by Salsa Digital(Opens in a new tab/window), whose team carried the platform and its relationships with the institutions using it. We joined the engineering in 2018.

Our work ran across the running instances:

  • software development on the application
  • automated testing across the institution instances
  • ongoing maintenance

A point worth saying plainly. The assessment instrument belongs to the institutions that use it; our work was the software it runs on.

A single PHP codebase, an instance per institution and per cycle

Candidates registered, confirmed who they were, and worked through a set of structured modules at their own pace. Staff worked over the cohort through an administrative dashboard, and results left the system as PDF reports. Each institution and each intake had its own instance, all running from the same codebase.

None of this was Drupal(Opens in a new tab/window). It is a classic PHP application on Zend Framework 1:

  • jQuery and Bootstrap on the front end
  • a grid component for the administrative tables
  • a PDF library for the reports

Most of our work is Drupal, but the engineering underneath any long-lived platform is the same: a real domain model, a test suite that means something, and a repeatable path to production.

Making a copy safe to ship: end-to-end Behat testing per instance

The testing is the most interesting engineering on the project, because it takes the multi-tenancy problem head on. Every institution instance had its own end-to-end profile with its own base URL and matching tag filter, and a single script ran them all together.

2 details are worth borrowing. Registration issued a temporary password by email, so an end-to-end test could not get through the front door without reading it. The suite ran against a real mail catcher and took the password out of the message, exactly the way a candidate would.

Some institutions provisioned their candidates directly rather than letting them register themselves. Rather than skip those instances or test a path they had deliberately closed, we re-used a real account and reset its progress between runs, behind a single flag.

When something did break, the suite saved a screenshot at that moment, through our open-source Behat screenshot extension(Opens in a new tab/window), so a failing run explained itself instead of just going red.

All of it served a single purpose: every new instance was a copy of a working one, and the end-to-end suite is what made copying safe. For a client, that reads as confidence: each admission cycle starts from an instance the suite has already checked.

Atomic releases onto the university's own infrastructure

The platform ran on the University of Melbourne's own infrastructure, so releases followed the university's process rather than ours. That made the release path a documented, hand-run sequence:

  • build the artefact
  • send it up to the server
  • unpack it beside the running application
  • swap the directories in a single move, leaving the previous release exactly where it stood

That last part is the whole trick. The cutover is a single move rather than a file-by-file copy, and the rollback is the same move in reverse. When a client's platform team owns the servers, a release procedure that fits their process and stays safe beats one that fits ours.

What it adds up to: multiple institutions, multiple cycles

The platform ran for multiple institutions across multiple admission cycles. Instances ran for other universities, for state education departments and for Catholic education authorities, each with its own cohort and intake dates.

A single codebase, several unrelated organisations, and a fresh instance for every admission cycle.

That is the outcome worth naming. Several unrelated organisations ran the same software on their own terms, and each year's instance could be stood up with confidence rather than crossed fingers. For a client, that is what a well-drawn platform buys: the next institution is a setup exercise, not a new build.

For platforms that several organisations have to run

Everything on this page is 2 of our services running on one long-lived platform: software development on an application several organisations depend on, and support and maintenance across every running instance.

If you run an assessment or admissions platform that several institutions share, or a custom application that has outlived the team who built it, this is the shape the engagement takes: an end-to-end suite that checks each instance before it ships, a release path that fits your own platform team's process, and engineers who work on the stack you already have. The same engineering is available with AI-assisted delivery where it suits the work, at the same tested standard.

If your next admission cycle depends on standing up another instance of a platform nobody has tested in a while, tell us what it runs on.