| Install | |
|---|---|
composer require get-skipper/skipper-php |
|
| Latest Version: | v1.0.0 |
| PHP: | >=8.2 |
Test-gating for PHP via Google Spreadsheet. Enable or disable tests without changing code — just update a date in a Google Sheet.
A PHP port of get-skipper/skipper, supporting PHPUnit, Pest, Behat, Codeception, PHPSpec, and Kahlan.
A Google Spreadsheet stores test IDs with optional disabledUntil dates:
| testId | disabledUntil | notes |
|---|---|---|
tests/Feature/AuthTest.php > AuthTest > testItCanLogin |
||
tests/Feature/PaymentTest.php > PaymentTest > testCheckout |
2099-12-31 |
Flaky on CI |
features/auth.feature > Auth > User can log in |
2026-06-01 |
Under investigation |
disabledUntil → test runs normallyTests not listed in the spreadsheet always run (opt-out model).
composer require get-skipper/skipper-php
Install your test framework if not already present:
# PHPUnit / Pest
composer require --dev phpunit/phpunit
# Behat
composer require --dev behat/behat
# Codeception
composer require --dev codeception/codeception
# PHPSpec
composer require --dev phpspec/phpspec
# Kahlan
composer require --dev kahlan/kahlan
Create a Google Spreadsheet with the following columns in row 1:
testIddisabledUntilnotes (optional)Create a Google Cloud service account and download the JSON key file.
Share the spreadsheet with the service account's email address (client_email in the JSON).
Note the spreadsheet ID from the URL:
https://docs.google.com/spreadsheets/d/YOUR_SPREADSHEET_ID/edit
Three formats are accepted for all integrations:
| Format | Parameter | Use case |
|---|---|---|
| File path | credentialsFile: './service-account.json' |
Local development |
| Base64 string | credentialsBase64: 'eyJ0eX...' |
CI/CD inline secret |
| Environment variable | credentialsEnvVar: 'GOOGLE_CREDS_B64' |
CI/CD env var (base64) |
To encode your credentials file for CI:
base64 -i service-account.json
Add to phpunit.xml:
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php">
<testsuites>
<testsuite name="Tests">
<directory>tests</directory>
</testsuite>
</testsuites>
<extensions>
<bootstrap class="GetSkipper\PHPUnit\SkipperExtension">
<parameter name="spreadsheetId" value="YOUR_SPREADSHEET_ID"/>
<!-- Choose one credentials option: -->
<parameter name="credentialsFile" value="./service-account.json"/>
<!-- <parameter name="credentialsBase64" value="eyJ0eXBlIjoic2VydmljZV9hY2NvdW50Ii4uLn0="/> -->
<!-- <parameter name="credentialsEnvVar" value="GOOGLE_CREDS_B64"/> -->
<parameter name="sheetName" value="MySheet"/> <!-- optional -->
</bootstrap>
</extensions>
</phpunit>
Test ID format:
tests/Unit/AuthTest.php > AuthTest > testItCanLogin
tests/Unit/AuthTest.php > AuthTest > testWithDataProvider with data set "valid"
Pest runs on top of PHPUnit — use the same phpunit.xml configuration above. The extension auto-detects Pest-generated classes (P\ namespace prefix) and applies the correct ID format automatically.
Test ID format:
tests/Feature/auth.php > can login
tests/Feature/auth.php > Auth > can login ← with describe() block
Alternative: hook-based setup via tests/Pest.php
Use this if you prefer Pest-native configuration instead of phpunit.xml:
<?php
// tests/Pest.php
use GetSkipper\Core\Config\SkipperConfig;
use GetSkipper\Core\Credentials\Base64Credentials;
use GetSkipper\Core\Credentials\FileCredentials;
use GetSkipper\Pest\Plugin;
// Choose one credentials option:
Plugin::skipperSetup(new SkipperConfig(
spreadsheetId: 'YOUR_SPREADSHEET_ID',
credentials: new FileCredentials('./service-account.json'),
// credentials: new Base64Credentials('eyJ0eXBlIjoic2VydmljZV9hY2NvdW50Ii4uLn0='),
// credentials: new Base64Credentials((string) getenv('GOOGLE_CREDS_B64')),
sheetName: 'MySheet', // optional
));
Add to behat.yml:
default:
extensions:
GetSkipper\Behat\SkipperExtension:
spreadsheetId: 'YOUR_SPREADSHEET_ID'
# Choose one credentials option:
credentialsFile: './service-account.json'
# credentialsBase64: 'eyJ0eXBlIjoic2VydmljZV9hY2NvdW50Ii4uLn0='
# credentialsEnvVar: 'GOOGLE_CREDS_B64'
sheetName: 'MySheet' # optional
suites:
default:
contexts:
- GetSkipper\Behat\SkipperContext
# Add your other contexts here
- App\Context\FeatureContext
Disabled scenarios are marked as Pending (yellow).
Test ID format:
features/auth/login.feature > User authentication > User can log in
Add to codeception.yml:
extensions:
enabled:
- GetSkipper\Codeception\SkipperExtension
config:
GetSkipper\Codeception\SkipperExtension:
spreadsheetId: 'YOUR_SPREADSHEET_ID'
# Choose one credentials option:
credentialsFile: './service-account.json'
# credentialsBase64: 'eyJ0eXBlIjoic2VydmljZV9hY2NvdW50Ii4uLn0='
# credentialsEnvVar: 'GOOGLE_CREDS_B64'
sheetName: 'MySheet' # optional
Test ID format:
tests/Acceptance/AuthCest.php > AuthCest > tryToLogin
tests/Unit/AuthTest.php > AuthTest > testItCanLogin
Add to phpspec.yml:
extensions:
GetSkipper\PHPSpec\SkipperExtension:
spreadsheetId: 'YOUR_SPREADSHEET_ID'
# Choose one credentials option:
credentialsFile: './service-account.json'
# credentialsBase64: 'eyJ0eXBlIjoic2VydmljZV9hY2NvdW50Ii4uLn0='
# credentialsEnvVar: 'GOOGLE_CREDS_B64'
sheetName: 'MySheet' # optional
Disabled specs are marked as Skipped.
Test ID format:
spec/Auth/LoginSpec.php > LoginSpec > it login with valid credentials
spec/Auth/LoginSpec.php > LoginSpec > it reject invalid passwords
In kahlan-config.php:
<?php
// kahlan-config.php
use GetSkipper\Core\Config\SkipperConfig;
use GetSkipper\Core\Credentials\Base64Credentials;
use GetSkipper\Core\Credentials\FileCredentials;
use GetSkipper\Kahlan\SkipperPlugin;
$config->beforeAll(function () {
// Choose one credentials option:
SkipperPlugin::setup(new SkipperConfig(
spreadsheetId: 'YOUR_SPREADSHEET_ID',
credentials: new FileCredentials('./service-account.json'),
// credentials: new Base64Credentials('eyJ0eXBlIjoic2VydmljZV9hY2NvdW50Ii4uLn0='),
// credentials: new Base64Credentials((string) getenv('GOOGLE_CREDS_B64')),
sheetName: 'MySheet', // optional
));
});
// Check tests globally (all specs in all directories):
$config->scope()->beforeEach(function () {
SkipperPlugin::checkTest($this);
});
Test ID format:
spec/Auth/LoginSpec.php > Auth > Login > can login with valid credentials
In sync mode, the spreadsheet is automatically reconciled with your test suite:
disabledUntil)Enable with the SKIPPER_MODE environment variable:
# PHPUnit / Pest
SKIPPER_MODE=sync vendor/bin/phpunit
# Behat
SKIPPER_MODE=sync vendor/bin/behat
# Codeception
SKIPPER_MODE=sync vendor/bin/codecept run
# PHPSpec
SKIPPER_MODE=sync vendor/bin/phpspec run
# Kahlan
SKIPPER_MODE=sync vendor/bin/kahlan
Note: Sync only writes to the primary sheet. Reference sheets are never modified.
The bundled workflow (.github/workflows/tests.yml) includes a sync job that runs automatically after every push to main, once all tests have passed:
sync:
name: Sync spreadsheet
needs: tests
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
steps:
- run: composer test
env:
SKIPPER_MODE: sync
GOOGLE_CREDS_B64: ${{ secrets.GOOGLE_CREDS_B64 }}
This ensures the spreadsheet is always up to date with the current test suite on main. The sync job is skipped on pull requests and on branches other than main.
You can merge test entries from multiple sheets. When the same test ID appears in multiple sheets, the most restrictive (latest) disabledUntil wins.
Configure additional sheets with referenceSheets:
<!-- phpunit.xml -->
<parameter name="sheetName" value="Main"/>
<parameter name="referenceSheets" value='["SharedDisabled", "QA"]'/>
# behat.yml / phpspec.yml / codeception.yml
sheetName: 'Main'
referenceSheets: ['SharedDisabled', 'QA']
| Variable | Default | Description |
|---|---|---|
SKIPPER_MODE |
read-only |
Set to sync to enable spreadsheet reconciliation |
SKIPPER_CACHE_FILE |
(auto) | Path to the resolver cache file (set by the main process) |
SKIPPER_DISCOVERED_DIR |
(auto) | Directory for collecting discovered test IDs across workers |
SKIPPER_DEBUG |
(unset) | Set to any non-empty value to enable verbose logging |
| Framework | Format example |
|---|---|
| PHPUnit | tests/Unit/AuthTest.php > AuthTest > testItCanLogin |
| Pest | tests/Feature/auth.php > Auth > can login |
| Behat | features/auth.feature > User authentication > User can log in |
| Codeception | tests/Acceptance/AuthCest.php > AuthCest > tryToLogin |
| PHPSpec | spec/Auth/LoginSpec.php > LoginSpec > it login with valid credentials |
| Kahlan | spec/Auth/LoginSpec.php > Auth > Login > can login |
All test IDs are case-insensitive and whitespace-collapsed for comparison.