Enable and disable tests directly from a Google Spreadsheet — no code changes required.
Skipper integrates with your existing test suite via a minimal config. Each test is identified by its file path + title. Set a disabledUntil date in the spreadsheet to skip a test until that date; leave it empty (or set a past date) to run it normally.
| Package | Framework |
|---|---|
@get-skipper/playwright |
Playwright |
@get-skipper/jest |
Jest |
@get-skipper/vitest |
Vitest |
@get-skipper/cypress |
Cypress |
@get-skipper/nightwatch |
Nightwatch.js |
read-onlymode (default): Skipper reads the spreadsheet at the start of the test run and skips any test whosedisabledUntildate is in the future.syncmode (SKIPPER_MODE=sync): Same as read-only, plus after the run Skipper reconciles the spreadsheet — adding rows for new tests and, whenSKIPPER_SYNC_ALLOW_DELETE=true, removing rows for tests no longer in the suite.
Create a Google Spreadsheet with the following columns in the first row (header):
testId |
disabledUntil |
notes |
|---|---|---|
tests/auth/login.spec.ts > login > should log in |
||
tests/checkout/payment.spec.ts > payment > stripe |
2026-04-01 |
Flaky until fix |
tests/payments/refund.spec.ts > refund |
2099-12-31 |
Disabled indefinitely |
testId—{relative file path} > {describe blocks} > {test name}, automatically generated by the plugin.disabledUntil— ISO 8601 date (e.g.2026-04-01). Empty or past date = test runs. Future date = test is skipped.notes— optional free-text field, ignored by the plugin.
All Skipper plugins accept the same SkipperConfig object:
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
spreadsheetId |
string |
✅ | — | The Google Spreadsheet ID (from the URL) |
credentials |
object | ✅ | — | Service account credentials — see below |
sheetName |
string |
first tab | Name of the sheet tab to read/write | |
referenceSheets |
string[] |
[] |
Additional sheet tabs to read (read-only); merged with the primary sheet | |
testIdColumn |
string |
"testId" |
Column header for the test identifier | |
disabledUntilColumn |
string |
"disabledUntil" |
Column header for the disable date |
// Base64-encoded JSON (recommended for CI)
credentials: { credentialsBase64: process.env.GOOGLE_CREDS_B64! }
// Path to a local JSON file (recommended for local dev)
credentials: { credentialsFile: './service-account.json' }
// Raw service account object
credentials: { client_email: '...', private_key: '...' }By default Skipper uses the first tab of the spreadsheet. Use sheetName to target a specific tab:
{
spreadsheetId: '...',
credentials: { credentialsBase64: '...' },
sheetName: 'E2E Tests', // reads and writes to this tab only
}Additional sheet tabs to read (never written to). Useful for sharing a common list of disabled tests across multiple suites. When the same testId appears in more than one sheet, the most restrictive (latest future) disabledUntil wins:
{
spreadsheetId: '...',
credentials: { credentialsBase64: '...' },
sheetName: 'E2E Tests', // primary sheet (read + write in sync mode)
referenceSheets: ['Shared'], // additional sheets (read-only)
}- Go to Google Cloud Console
- Create a new project (or select an existing one)
- Navigate to APIs & Services → Library
- Search for Google Sheets API and enable it
- Navigate to APIs & Services → Credentials
- Click Create Credentials → Service Account
- Give it a name (e.g.
skipper-bot) and click Done - Click on the service account → Keys tab → Add Key → Create new key → JSON
- Download the JSON file — keep it secret
Open your Google Spreadsheet and share it with the service account email (found in the JSON file as client_email):
- Viewer role for
read-onlymode - Editor role for
syncmode
Convert the JSON file to a base64 string for use as a CI secret:
base64 -i service-account.json | tr -d '\n'Save the output as a secret named GOOGLE_CREDS_B64 in your CI environment (e.g. GitHub Actions secrets).
For local development, keep the JSON file and reference it via credentialsFile:
credentials: { credentialsFile: './service-account.json' }Run your test suite in sync mode once to auto-populate all test rows:
SKIPPER_MODE=sync SKIPPER_SPREADSHEET_ID=<your-spreadsheet-id> pnpm testAll tests will be added to the spreadsheet with an empty disabledUntil (enabled by default). You can then set dates in the spreadsheet to disable specific tests.
| Variable | Default | Description |
|---|---|---|
SKIPPER_MODE |
read-only |
Set to sync to enable spreadsheet reconciliation after the test run |
SKIPPER_FAIL_OPEN |
true |
When true, runs all tests if the API is unreachable and no valid cache exists. Set to false to crash instead |
SKIPPER_CACHE_TTL |
300 |
Seconds a local .skipper-cache.json is considered valid. Skipper writes this file after each successful fetch and reads it as a fallback on API failure |
SKIPPER_SYNC_ALLOW_DELETE |
false |
When false, orphaned rows (tests removed from the suite) are only warned about. Set to true to delete them |
SKIPPER_DEBUG |
— | Set to any truthy value to enable verbose logging |
No env var needed. Tests with a future disabledUntil are skipped.
SKIPPER_SPREADSHEET_ID=<id> pnpm testSKIPPER_MODE=sync SKIPPER_SPREADSHEET_ID=<id> pnpm testAfter the run:
- New tests → added to the spreadsheet with empty
disabledUntil - Removed tests → warned (set
SKIPPER_SYNC_ALLOW_DELETE=trueto also delete them)
# .github/workflows/test.yml
name: Test
on:
pull_request:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install
- name: Test (read-only on PR)
if: github.event_name == 'pull_request'
env:
SKIPPER_SPREADSHEET_ID: ${{ secrets.SKIPPER_SPREADSHEET_ID }}
GOOGLE_CREDS_B64: ${{ secrets.GOOGLE_CREDS_B64 }}
run: pnpm test
- name: Test + sync (on merge to main)
if: github.ref == 'refs/heads/main'
env:
SKIPPER_MODE: sync
SKIPPER_SPREADSHEET_ID: ${{ secrets.SKIPPER_SPREADSHEET_ID }}
GOOGLE_CREDS_B64: ${{ secrets.GOOGLE_CREDS_B64 }}
run: pnpm testMIT — see LICENSE.