typo3-testing — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited typo3-testing (Plugin) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
<!-- SPDX-License-Identifier: CC-BY-SA-4.0 --> <!-- SPDX-FileCopyrightText: Netresearch DTT GmbH -->
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:
Skills are portable packages of procedural knowledge that work across any AI agent supporting the Agent Skills specification.
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.
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.
cd your-extension
~/.claude/skills/typo3-testing/skills/typo3-testing/scripts/setup-testing.sh ~/.claude/skills/typo3-testing/skills/typo3-testing/scripts/generate-test.sh unit MyService Build/Scripts/runTests.sh -s unit
composer ci:testFast, 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.
#### Parallel Test Execution
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:
#### Separate Unit and Functional Configurations
The tea extension maintains separate PHPUnit configurations:
Unit Tests (Build/phpunit/UnitTests.xml):
Functional Tests (Build/phpunit/FunctionalTests.xml):
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>#### Coverage Thresholds
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:
The tea extension demonstrates an elegant CSV-based pattern for functional test fixtures, significantly improving test readability and maintainability.
#### Problem Statement
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');
}#### CSV Fixture Pattern
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);
}
}#### CSV Assertion Pattern
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');
}#### Benefits
#### Best Practices
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 deletionThe tea extension demonstrates comprehensive multi-database testing across SQLite, MariaDB, MySQL, and PostgreSQL, ensuring compatibility across all TYPO3-supported database systems.
#### Why Multi-Database Testing Matters
Different databases have subtle behavioral differences:
Extensions using advanced SQL features (e.g., JSON columns, full-text search, stored procedures) must test across all target databases.
#### runTests.sh Pattern
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:
#### CI Matrix Configuration
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:
#### Database-Specific Considerations
SQLite Advantages:
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))
);#### Local Multi-Database Testing
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 postgres#### Docker Compose Alternative
For 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.
#### Core Features
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 dependencies#### Implementation Structure
Key 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}"#### Benefits
#### Integration with Composer Scripts
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.
#### Example Usage Workflows
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:postgresThis skill stands on the shoulders of the TYPO3 community's exceptional work. We gratefully acknowledge:
typo3/core-testing-* Docker images, and establishing the testing patterns that make this skill possible.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: missing composer.json, a fresh extension (Composer calls, directories, copied configurations), existing files and dependencies left alone, -a, rejected options. composer and vendor/bin/codecept are stubs that record their arguments.skills/typo3-testing/scripts/generate-test.sh: unit, functional and acceptance tests (checked with php -l), the fixture file, rejected test types and class names, a missing Tests/ 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).skills/typo3-testing/checkpoints.yaml, run as the assessment runner runs them (including a minMsi with whitespace before the colon, a config that declares only minCoveredMsi, the higher of the two thresholds being judged, and commented-out thresholds being ignored), and a guard that no checkpoint uses the expected: field, which the runner does not read.Build/Scripts/validate-skill.sh through the Build/hooks/pre-commit hook, Build/Scripts/check-plugin-version.sh through the Build/hooks/pre-push hook, and scripts/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.
grep; setup-testing.sh calls composer, generate-test.sh calls php, and validate-setup.sh looks for docker. The templates under skills/typo3-testing/assets/ declare the packages they need (for example assets/Build/playwright/package.json); those are installed in the user's extension, not here.composer.json requires netresearch/composer-agent-skill-plugin for installation through Composer. No composer.lock is committed; Build/Scripts/validate-skill.sh fails when one exists.rev: in .pre-commit-config.yaml. CI tools come from the shared workflows in netresearch/.github, netresearch/skill-repo-skill and netresearch/typo3-ci-workflows, which this repository calls at @main and which pin every action by commit SHA.renovate.json, config:recommended with the pre-commit manager enabled) opens update pull requests. For pull requests opened by Renovate or Dependabot, auto-merge-deps.yml approves them and enables auto-merge through the shared workflow, unless they carry the label deps-major or deps-no-automerge.This repository follows the Netresearch organisation policies:
PROJECT_APP_ID and PROJECT_APP_PRIVATE_KEY, and pass them only to the auto-merge workflow; releases sign through OIDC (id-token: write) without a stored key.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:
lint.yml: skill structure, manifest sync, markdownlint, yamllint, actionlint, JSON syntax, ShellCheck at severity error, 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 to main that are not drafts, also the CodeRabbit review and the Copilot code review that the repository ruleset requests.main: security.yml with 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 as the codeql job of security.yml (Actions, JavaScript/TypeScript and Python, security-and-quality query suite).security.yml on pull requests to main and pushes to main, with the default rule set and the allowlist in .gitleaks.toml (the file references/synthetic-secret-fixtures.md and one fake Stripe key), and GitHub secret scanning with push protection, which is enabled for this repository.This project uses split licensing:
skills/typo3-testing/assets/Build/Scripts/runTests.sh is derived from TYPO3 Core's Build/Scripts/runTests.sh and 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](https://www.netresearch.de/)
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.