A comprehensive Claude Code skill for creating and managing TYPO3 extension tests.
This is an Agent Skill following the open standard originally developed by Anthropic and released for cross-platform use.
Supported Platforms:
- ✅ Claude Code (Anthropic)
- ✅ Cursor
- ✅ GitHub Copilot
- ✅ Other skills-compatible AI agents
Skills are portable packages of procedural knowledge that work across any AI agent supporting the Agent Skills specification.
- Test Creation: Generate Unit, Functional, and E2E tests
- E2E Testing: Playwright-based browser automation (TYPO3 Core standard)
- Accessibility Testing: axe-core integration for WCAG compliance
- Infrastructure Setup: Automated testing infrastructure installation
- CI/CD Integration: GitHub Actions and GitLab CI templates
- Quality Tools: PHPStan, Rector, php-cs-fixer integration
- Fixture Management: Database fixture templates and tooling
- Test Orchestration: runTests.sh script pattern from TYPO3 best practices
Add the Netresearch marketplace once, then browse and install skills:
# Claude Code
/plugin marketplace add netresearch/claude-code-marketplace
/plugin install typo3-testing@netresearch-claude-code-marketplaceSince Claude Code 2.1.157 a plugin directory under your personal skills directory loads on its own, including the agents this repo ships:
mkdir -p ~/.claude/skills
git clone https://github.com/netresearch/typo3-testing-skill.git \
~/.claude/skills/typo3-testingIt loads as typo3-testing@skills-dir on the next session. Update with git -C ~/.claude/skills/typo3-testing pull and start a new session; remove it by deleting the directory. This route has no claude plugin update.
npx (skills.sh)
Install with any Agent Skills-compatible agent:
npx skills add https://github.com/netresearch/typo3-testing-skill --skill typo3-testingLimitation:
npx skillsinstallsSKILL.md-based skills only. This repo also shipsagents, which it does not install — use the marketplace or the skills directory for those.
Download the latest release and extract to your agent's skills directory.
git clone https://github.com/netresearch/typo3-testing-skill.gitcomposer require netresearch/typo3-testing-skillRequires netresearch/composer-agent-skill-plugin.
The paths below assume the clone from Without a marketplace. The scripts work on the current directory, so run them from the extension root.
-
Setup testing infrastructure:
cd your-extension ~/.claude/skills/typo3-testing/skills/typo3-testing/scripts/setup-testing.sh
-
Generate a test:
~/.claude/skills/typo3-testing/skills/typo3-testing/scripts/generate-test.sh unit MyService -
Run tests:
Build/Scripts/runTests.sh -s unit composer ci:test
Fast, isolated tests without external dependencies. Perfect for testing services, utilities, and domain logic.
Tests with database and full TYPO3 instance. Use for repositories, controllers, and integration scenarios.
Browser-based end-to-end tests using Playwright (TYPO3 Core standard). For testing complete user workflows, backend modules, and accessibility compliance with axe-core.
The tea extension demonstrates production-grade PHPUnit configuration with parallel execution, strict mode, and comprehensive coverage analysis.
PHPUnit 10+ supports parallel test execution for significant performance improvements:
Configuration (Build/phpunit/UnitTests.xml):
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../.Build/vendor/phpunit/phpunit/phpunit.xsd"
executionOrder="random"
failOnRisky="true"
failOnWarning="true"
stopOnFailure="false"
beStrictAboutTestsThatDoNotTestAnything="true"
colors="true"
cacheDirectory=".Build/.phpunit.cache">
<testsuites>
<testsuite name="Unit Tests">
<directory>../../Tests/Unit/</directory>
</testsuite>
</testsuites>
<coverage includeUncoveredFiles="true">
<report>
<clover outputFile=".Build/coverage/clover.xml"/>
<html outputDirectory=".Build/coverage/html"/>
<text outputFile="php://stdout" showUncoveredFiles="false"/>
</report>
</coverage>
</phpunit>Key Features:
executionOrder="random": Detects hidden test dependencies by randomizing test orderfailOnRisky="true": Treats risky tests as failures (tests without assertions)failOnWarning="true": Fails on warnings like deprecated function usagebeStrictAboutTestsThatDoNotTestAnything="true": Ensures every test has assertions
The tea extension maintains separate PHPUnit configurations:
Unit Tests (Build/phpunit/UnitTests.xml):
- No database bootstrap
- Fast execution (milliseconds per test)
- Strict mode enabled
- Code coverage analysis
Functional Tests (Build/phpunit/FunctionalTests.xml):
- Database bootstrap included
- TYPO3 testing framework integration
- SQLite for fast in-memory testing
- Test doubles for external services
Example functional test configuration:
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../.Build/vendor/phpunit/phpunit/phpunit.xsd"
stopOnFailure="false"
colors="true"
cacheDirectory=".Build/.phpunit.cache">
<testsuites>
<testsuite name="Functional Tests">
<directory>../../Tests/Functional/</directory>
</testsuite>
</testsuites>
<php>
<ini name="display_errors" value="1"/>
<env name="TYPO3_CONTEXT" value="Testing"/>
</php>
</phpunit>Enforce minimum coverage requirements via composer scripts:
{
"scripts": {
"ci:coverage:check": [
"@ci:tests:unit",
"phpunit --configuration Build/phpunit/UnitTests.xml --coverage-text --coverage-clover=.Build/coverage/clover.xml",
"phpunit-coverage-check .Build/coverage/clover.xml 70"
]
}
}Progressive Coverage Targets:
- MVP Extensions: 50% minimum
- Production Extensions: 70% minimum
- Reference Extensions: 80%+ target
The tea extension demonstrates an elegant CSV-based pattern for functional test fixtures, significantly improving test readability and maintainability.
Traditional fixture loading in TYPO3 functional tests uses SQL files or PHP arrays:
// ❌ Traditional approach: Verbose and hard to read
protected function setUp(): void
{
parent::setUp();
$this->importCSVDataSet(__DIR__ . '/Fixtures/Database/pages.csv');
$this->importCSVDataSet(__DIR__ . '/Fixtures/Database/tt_content.csv');
$this->importCSVDataSet(__DIR__ . '/Fixtures/Database/tx_tea_domain_model_product_tea.csv');
}Fixture File (Tests/Functional/Fixtures/Database/tea.csv):
tx_tea_domain_model_product_tea
uid,pid,title,description,owner
1,1,"Earl Grey","Classic black tea",1
2,1,"Green Tea","Organic green tea",1
3,2,"Oolong Tea","Traditional oolong",2Loading in Test:
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
final class TeaRepositoryTest extends FunctionalTestCase
{
protected array $testExtensionsToLoad = [
'typo3conf/ext/tea',
];
protected function setUp(): void
{
parent::setUp();
$this->importCSVDataSet(__DIR__ . '/Fixtures/Database/tea.csv');
}
/**
* @test
*/
public function findAllReturnsAllRecords(): void
{
$result = $this->subject->findAll();
self::assertCount(3, $result);
}
}Even More Powerful: Assert database state using CSV format:
Expected State File (Tests/Functional/Fixtures/Database/AssertTeaAfterCreate.csv):
tx_tea_domain_model_product_tea
uid,pid,title,description,owner
1,1,"Earl Grey","Classic black tea",1
2,1,"Green Tea","Organic green tea",1
3,2,"Oolong Tea","Traditional oolong",2
4,1,"New Tea","Newly created tea",1Assertion in Test:
/**
* @test
*/
public function createPersistsNewTea(): void
{
$newTea = new Tea();
$newTea->setTitle('New Tea');
$newTea->setDescription('Newly created tea');
$this->subject->add($newTea);
$this->persistenceManager->persistAll();
// Assert entire database state matches expected CSV
$this->assertCSVDataSet(__DIR__ . '/Fixtures/Database/AssertTeaAfterCreate.csv');
}- Readability: CSV format is human-readable and version control friendly
- Maintainability: Easy to modify fixtures without PHP syntax knowledge
- Comprehensive Assertions: Assert entire table state in single call
- Change Detection: Diff tools show fixture changes clearly
- Cross-Test Reuse: Same CSV fixtures reusable across multiple tests
Minimal Fixtures: Include only necessary columns for the test:
tx_tea_domain_model_product_tea
uid,title
1,"Earl Grey"
2,"Green Tea"Named Test Data: Use descriptive titles to make test intent clear:
tx_tea_domain_model_product_tea
uid,title,deleted
1,"Active Tea",0
2,"Deleted Tea",1Fixture Organization:
Tests/Functional/
├── Fixtures/
│ └── Database/
│ ├── tea_initial.csv # Initial state
│ ├── tea_after_create.csv # Expected after creation
│ ├── tea_after_update.csv # Expected after update
│ └── tea_after_delete.csv # Expected after deletion
The tea extension demonstrates comprehensive multi-database testing across SQLite, MariaDB, MySQL, and PostgreSQL, ensuring compatibility across all TYPO3-supported database systems.
Different databases have subtle behavioral differences:
- SQLite: Case-insensitive LIKE, limited ALTER TABLE support
- MySQL: Case sensitivity varies by OS and configuration
- MariaDB: Different optimizer behavior, JSON handling differences
- PostgreSQL: Strict type casting, different string comparison semantics
Extensions using advanced SQL features (e.g., JSON columns, full-text search, stored procedures) must test across all target databases.
The tea extension uses Build/Scripts/runTests.sh for orchestrated multi-database testing:
#!/usr/bin/env bash
# Run functional tests against SQLite (default, fast)
./Build/Scripts/runTests.sh -s functional
# Run functional tests against MariaDB 11.8
./Build/Scripts/runTests.sh -s functional -d mariadb -i 11.8
# Run functional tests against MySQL 8.0
./Build/Scripts/runTests.sh -s functional -d mysql -i 8.0
# Run functional tests against PostgreSQL 16
./Build/Scripts/runTests.sh -s functional -d postgres -i 16Script Responsibilities:
- Docker container orchestration
- Database initialization and schema setup
- Test execution with proper environment variables
- Cleanup and teardown
GitHub Actions (.github/workflows/ci.yml):
name: CI
on: [push, pull_request]
jobs:
functional-tests:
name: Functional Tests
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: ['8.2', '8.3', '8.4']
typo3: ['12.4', '13.0']
database:
- type: 'sqlite'
- type: 'mariadb'
version: '11.8'
- type: 'mysql'
version: '8.0'
- type: 'postgres'
version: '16'
steps:
- uses: actions/checkout@v4
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: pdo_sqlite, pdo_mysql, pdo_pgsql
- name: Composer Install
run: composer install --no-progress
- name: Functional Tests
run: |
if [ "${{ matrix.database.type }}" = "sqlite" ]; then
./Build/Scripts/runTests.sh -s functional
else
./Build/Scripts/runTests.sh -s functional -d ${{ matrix.database.type }} -i ${{ matrix.database.version }}
fiThis matrix runs tests across:
- 3 PHP versions × 2 TYPO3 versions × 4 databases = 24 test combinations
SQLite Advantages:
- Fast (in-memory execution)
- No external dependencies
- Ideal for local development
SQLite Limitations:
// ❌ Won't work on SQLite (lacks ALTER TABLE support)
$connection->executeUpdate('ALTER TABLE tt_content ADD COLUMN new_field VARCHAR(255)');
// ✅ Use TYPO3 API instead (cross-database compatible)
$schemaManager = $connection->getSchemaManager();
$column = new Column('new_field', Type::getType('string'), ['length' => 255]);
$schemaManager->addColumn('tt_content', $column);PostgreSQL Strict Typing:
// ❌ MySQL/MariaDB allow implicit conversion, PostgreSQL doesn't
$queryBuilder->where(
$queryBuilder->expr()->eq('uid', '123') // String '123' vs INT uid
);
// ✅ Explicit type casting works everywhere
$queryBuilder->where(
$queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter(123, \PDO::PARAM_INT))
);Developers can run multi-database tests locally:
# Quick SQLite test during development
composer ci:tests:functional
# Comprehensive multi-DB test before pushing
./Build/Scripts/runTests.sh -s functional -d mariadb
./Build/Scripts/runTests.sh -s functional -d mysql
./Build/Scripts/runTests.sh -s functional -d postgresFor complex scenarios, use docker-compose.yml:
version: '3.8'
services:
mariadb:
image: mariadb:11.8
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: test
ports:
- "3306:3306"
postgres:
image: postgres:16
environment:
POSTGRES_PASSWORD: root
POSTGRES_DB: test
ports:
- "5432:5432"
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: test
ports:
- "3307:3306"The runTests.sh script from the tea extension provides comprehensive test orchestration with Docker-based isolation.
1. Test Suite Selection:
./Build/Scripts/runTests.sh -s unit # Unit tests only
./Build/Scripts/runTests.sh -s functional # Functional tests
./Build/Scripts/runTests.sh -s acceptance # Acceptance tests
./Build/Scripts/runTests.sh -s lint # PHP linting
./Build/Scripts/runTests.sh -s phpstan # Static analysis2. Database Selection:
./Build/Scripts/runTests.sh -s functional -d sqlite # Default
./Build/Scripts/runTests.sh -s functional -d mariadb # MariaDB
./Build/Scripts/runTests.sh -s functional -d mysql # MySQL
./Build/Scripts/runTests.sh -s functional -d postgres # PostgreSQL3. Version Control:
./Build/Scripts/runTests.sh -s functional -d mariadb -i 11.8
./Build/Scripts/runTests.sh -s functional -d postgres -i 16
./Build/Scripts/runTests.sh -p 8.3 # PHP version4. Cleanup and Maintenance:
./Build/Scripts/runTests.sh -s clean # Remove containers
./Build/Scripts/runTests.sh -s composer update # Update dependenciesKey Components:
#!/usr/bin/env bash
# Parse command line arguments
while getopts "s:d:i:p:h" option; do
case ${option} in
s) TEST_SUITE=${OPTARG} ;;
d) DATABASE=${OPTARG} ;;
i) DATABASE_VERSION=${OPTARG} ;;
p) PHP_VERSION=${OPTARG} ;;
h) showHelp; exit 0 ;;
esac
done
# Set defaults
DATABASE=${DATABASE:-sqlite}
PHP_VERSION=${PHP_VERSION:-8.2}
# Container configuration
CONTAINER_NAME="typo3-testing-${DATABASE}"
DOCKER_IMAGE="typo3/core-testing-${DATABASE}:${DATABASE_VERSION}"
# Execute test suite in container
docker run \
--name ${CONTAINER_NAME} \
--rm \
-v $(pwd):/app \
-w /app \
${DOCKER_IMAGE} \
/bin/bash -c "composer ci:tests:${TEST_SUITE}"- Isolation: Each test run in clean container environment
- Reproducibility: Same environment locally and in CI
- Version Flexibility: Test against multiple PHP/TYPO3/DB versions
- Developer Convenience: Single command for all test types
- CI Integration: Same script used locally and in CI
Composer scripts delegate to runTests.sh:
{
"scripts": {
"ci:tests:unit": "Build/Scripts/runTests.sh -s unit",
"ci:tests:functional": "Build/Scripts/runTests.sh -s functional",
"ci:tests:functional:mariadb": "Build/Scripts/runTests.sh -s functional -d mariadb",
"ci:tests:functional:postgres": "Build/Scripts/runTests.sh -s functional -d postgres",
"ci:tests": [
"@ci:tests:unit",
"@ci:tests:functional"
]
}
}This maintains the local-CI parity principle: developers and CI use identical commands.
Development Workflow:
# Quick unit test during coding
composer ci:tests:unit
# Functional test before commit
composer ci:tests:functional
# Full test suite before push
composer ci:testsPre-Release Workflow:
# Test against all databases
./Build/Scripts/runTests.sh -s functional -d sqlite
./Build/Scripts/runTests.sh -s functional -d mariadb -i 11.8
./Build/Scripts/runTests.sh -s functional -d mysql -i 8.0
./Build/Scripts/runTests.sh -s functional -d postgres -i 16
# Test against multiple PHP versions
./Build/Scripts/runTests.sh -s unit -p 8.2
./Build/Scripts/runTests.sh -s unit -p 8.3
./Build/Scripts/runTests.sh -s unit -p 8.4CI/CD Workflow:
# .github/workflows/ci.yml
- name: Unit Tests
run: composer ci:tests:unit
- name: Functional Tests (SQLite)
run: composer ci:tests:functional
- name: Functional Tests (MariaDB)
run: composer ci:tests:functional:mariadb
- name: Functional Tests (PostgreSQL)
run: composer ci:tests:functional:postgres- SKILL.md - Main workflow guide with decision trees
- references/ - Detailed testing documentation
- assets/ - PHPUnit configs, AGENTS.md, examples
- PHP 8.1+
- Composer
- Docker (for functional tests)
- Node.js 22.18+ (for E2E tests)
- TYPO3 v12 or v13
- TYPO3 Testing Framework
- TYPO3 Best Practices: tea extension
- TYPO3 community best practices
This skill stands on the shoulders of the TYPO3 community's exceptional work. We gratefully acknowledge:
-
TYPO3 Core Development Team — For creating and maintaining the TYPO3 Testing Framework, the
typo3/core-testing-*Docker images, and establishing the testing patterns that make this skill possible. -
TYPO3 Best Practices Team — For the exemplary Tea Extension that serves as the reference implementation for testing infrastructure, demonstrating production-grade PHPUnit configuration, multi-database testing, and runTests.sh orchestration.
-
TYPO3 Documentation Team — For the comprehensive testing documentation that guides extension developers toward quality practices.
Changes are proposed as pull requests against main. New or changed behaviour of a script, a hook or a script checkpoint needs a test in tests/test_scripts.py in the same pull request.
tests/test_scripts.py holds the behaviour tests. It uses the Python standard library only and needs python3, bash, git and php (for generate-test.sh):
python3 tests/test_scripts.pyWhat the tests cover:
skills/typo3-testing/scripts/setup-testing.sh: missingcomposer.json, a fresh extension (Composer calls, directories, copied configurations), existing files and dependencies left alone,-a, rejected options.composerandvendor/bin/codeceptare stubs that record their arguments.skills/typo3-testing/scripts/generate-test.sh: unit, functional and acceptance tests (checked withphp -l), the fixture file, rejected test types and class names, a missingTests/directory or namespace, no overwrite of an existing test.skills/typo3-testing/scripts/validate-setup.sh: errors, warnings and the exit code for an empty, an incomplete and a complete setup, with and without Docker (a stub).- Checkpoints TT-105 and TT-106 in
skills/typo3-testing/checkpoints.yaml, run as the assessment runner runs them (including aminMsiwith whitespace before the colon, a config that declares onlyminCoveredMsi, the higher of the two thresholds being judged, and commented-out thresholds being ignored), and a guard that no checkpoint uses theexpected:field, which the runner does not read. Build/Scripts/validate-skill.shthrough theBuild/hooks/pre-commithook,Build/Scripts/check-plugin-version.shthrough theBuild/hooks/pre-pushhook, andscripts/verify-harness.sh.
The templates under skills/typo3-testing/assets/ have no tests here; they run in the extensions they are copied into.
A passing run prints a dot per test and ends with OK and exit code 0. A failure prints FAIL: or ERROR: with the test class and name and the assertion that failed (most assertions add the script's output), and the exit code is 1. CI runs the same file in the Skill Tests workflow (.github/workflows/tests.yml) on every pull request and every push to main.
- Skill: the instructions and references have no runtime dependency. The helper scripts need Bash and
grep;setup-testing.shcallscomposer,generate-test.shcallsphp, andvalidate-setup.shlooks fordocker. The templates underskills/typo3-testing/assets/declare the packages they need (for exampleassets/Build/playwright/package.json); those are installed in the user's extension, not here. - Composer:
composer.jsonrequiresnetresearch/composer-agent-skill-pluginfor installation through Composer. Nocomposer.lockis committed;Build/Scripts/validate-skill.shfails when one exists. - Development and CI tools: pre-commit hooks are pinned by
rev:in.pre-commit-config.yaml. CI tools come from the shared workflows innetresearch/.github,netresearch/skill-repo-skillandnetresearch/typo3-ci-workflows, which this repository calls at@mainand which pin every action by commit SHA. - Updates: Renovate (
renovate.json,config:recommendedwith thepre-commitmanager enabled) opens update pull requests. For pull requests opened by Renovate or Dependabot,auto-merge-deps.ymlapproves them and enables auto-merge through the shared workflow, unless they carry the labeldeps-majorordeps-no-automerge. - Checks: dependency review and Composer Audit (see below) check dependency changes. New dependencies follow the licence and vulnerability rules of the organisation's handling of dependency and code analysis findings.
This repository follows the Netresearch organisation policies:
- Governance: ownership, roles, and how decisions are made and disputes resolved.
- Roadmap: planned and explicitly excluded work for the coming year.
- Handling of dependency and code analysis findings: thresholds, deadlines and the exception process for dependency (SCA) and static analysis (SAST) findings.
- Secret management: how CI and release credentials are stored, accessed and rotated. The workflows here reference two secrets,
PROJECT_APP_IDandPROJECT_APP_PRIVATE_KEY, and pass them only to the auto-merge workflow; releases sign through OIDC (id-token: write) without a stored key. - Access roster: who holds administrative access to this repository and the organisation.
The security assurance case for this skill (threat model, trust boundaries, countermeasures and limits) is in docs/SECURITY-ASSURANCE.md; components and data flow are in docs/ARCHITECTURE.md.
Checks that run on pull requests in this repository:
- Every pull request: Skill Validation (
lint.yml: skill structure, manifest sync, markdownlint, yamllint, actionlint, JSON syntax, ShellCheck at severityerror, Ruff, checkpoint schema), Eval Validation (eval-validate.yml), Skill Tests (tests.yml:tests/test_scripts.py), PR Quality Gates (pr-quality.yml), the Labeler (labeler.yml) and Auto-merge dependency PRs (auto-merge-deps.yml, which acts only on Renovate and Dependabot pull requests); configured outside the workflows: SonarCloud and the DCO sign-off check; on pull requests tomainthat are not drafts, also the CodeRabbit review and the Copilot code review that the repository ruleset requests. - Pull requests to
main:security.ymlwith Betterleaks (secret scanning), zizmor (workflow static analysis), dependency review (fails on vulnerabilities of severity high or above), Composer Audit and Opengrep SAST (--config auto; which findings fail the check is set by the organisation rule); Harness Verification (harness-verify.yml) and Template Drift (check-template-drift.yml); CodeQL through GitHub's default setup (Actions and JavaScript/TypeScript, extended query suite). - Secret detection: Betterleaks in
security.ymlon pull requests tomainand pushes tomain, with the default rule set and the allowlist in.gitleaks.toml(the filereferences/synthetic-secret-fixtures.mdand one fake Stripe key), and GitHub secret scanning with push protection, which is enabled for this repository.
This project uses split licensing:
- Code (scripts, workflows, configs): MIT
- Content (skill definitions, documentation, references): CC-BY-SA-4.0
- Exception: the runner template
skills/typo3-testing/assets/Build/Scripts/runTests.shis derived from TYPO3 Core'sBuild/Scripts/runTests.shand stays under the core's licence, GPL-2.0-or-later; the file says so in its header.
See the individual license files for full terms.
Netresearch DTT GmbH, Leipzig
Made with ❤️ for Open Source by Netresearch