Guides

Test Contao packages in a monorepo

Use this guide when several Composer packages live in one repository and should be tested together in an isolated Contao installation. The root installs the test tooling, while MonorepoProject adds selected local packages to the Managed Edition.

For a monorepo containing another kind of web application, follow Test a web application. Your repository layout does not require a Managed Edition.

Prerequisites and layout

Install the supported PHP and Node.js versions, Composer and Git. Start Docker with Linux containers, or configure an administrative database connection. The tooling starts Contao's web server automatically.

This example assumes:

composer.json
packages/
    example-bundle/composer.json
    shared-bundle/composer.json
tests/E2e/MonorepoSmokeTest.php
phpunit.xml.dist

Each package manifest must have a name. The shared local version must satisfy constraints between the packages. For example, add this root Composer setting when local packages require each other with ^1.0:

{
    "extra": {
        "branch-alias": {
            "dev-main": "1.0.x-dev"
        }
    }
}

Merge that setting into your existing root manifest. An explicit root version takes precedence over the alias. Without either setting, discovery uses dev-main. It reads package names from the selected local manifests.

Install and configure

Run from the monorepo root:

composer require --dev contao/e2e-testing
vendor/bin/playwright-install --browsers

Add .contao-e2e/ to the root .gitignore. Put this in tests/E2e/MonorepoSmokeTest.php:

<?php

declare(strict_types=1);

use Contao\E2eTesting\Composer\MonorepoProject;
use Contao\E2eTesting\ManagedEdition\AbstractManagedEditionTestCase;
use Contao\E2eTesting\ManagedEdition\ManagedEditionConfig;
use Contao\InstallationRecipe\Composer\ComposerConfig;
use Contao\InstallationRecipe\Recipe\InstallationRecipe;

final class MonorepoSmokeTest extends AbstractManagedEditionTestCase
{
    protected static function createApplicationConfig(): ManagedEditionConfig
    {
        $root = dirname(__DIR__, 2);
        $project = MonorepoProject::discover($root);
        $composer = $project->configureComposer(
            ComposerConfig::managedEdition('^5.7'),
            'packages/example-bundle',
            'packages/shared-bundle',
        );

        return ManagedEditionConfig::create(InstallationRecipe::create($composer), $root);
    }

    public function testBackendLoginPage(): void
    {
        self::managedEdition()->createBackendBrowser()->visit('/contao/login');
        $this->assertSelectorExists('input[name="username"]');
    }
}

Replace the package directories and choose a Contao constraint supported by them. Add this root PHPUnit configuration, or merge its test suite into your existing configuration:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php" cacheDirectory=".contao-e2e/cache/phpunit">
  <testsuites>
    <testsuite name="e2e">
      <directory>tests/E2e</directory>
    </testsuite>
  </testsuites>
</phpunit>

Run the test

vendor/bin/phpunit --testsuite=e2e

PHPUnit should pass after loading the backend login page with both local packages installed. Each test class chooses its packages and recipe. Share that configuration through a project-specific abstract test class when several classes need it, rather than repeating the package list.

Local packages are linked from their working trees. Changes to package manifests select a fresh dependency installation. Other source changes refresh application setup on the next process. See caching and isolation, Docker warm-up and CI caches.

Improve this page