Skip to content

get-skipper/skipper-dotnet

Repository files navigation

skipper-dotnet

Test-gating for .NET — skip tests dynamically via a Google Spreadsheet, without touching your test code.

NuGet

What is Skipper?

Skipper reads a Google Spreadsheet to decide which tests to skip. Each row has:

testId disabledUntil notes
Tests/Unit/AuthTests.cs > AuthTests > CanLogin 2026-06-01 Flaky on CI
Tests/E2E/LoginTests.cs > LoginTests > UserCanLogin
  • disabledUntil is in the future → test is skipped automatically
  • disabledUntil is empty or in the past → test runs normally
  • Test not in spreadsheet → test runs normally (opt-out model)

No code changes required in your tests. Configure once, gate everywhere.


Packages

Package Description NuGet
GetSkipper.Core Google Sheets client, resolver, writer NuGet
GetSkipper.XUnit xUnit v2/v3 integration NuGet
GetSkipper.NUnit NUnit 3/4 integration NuGet
GetSkipper.MSTest MSTest v3 integration NuGet
GetSkipper.Playwright Playwright for .NET NuGet
GetSkipper.SpecFlow Reqnroll (SpecFlow) BDD NuGet

Quick Start

xUnit (zero changes to your tests)

// AssemblyInfo.cs — add once to your test project
using GetSkipper.XUnit;

[assembly: TestFramework("GetSkipper.XUnit.SkipperTestFramework", "GetSkipper.XUnit")]
[assembly: SkipperConfig(
    SpreadsheetIdEnvVar = "SKIPPER_SPREADSHEET_ID",
    CredentialsFile = "service-account.json")]

// Your tests remain unchanged:
public class AuthTests
{
    [Fact]
    public void CanLogin() { /* ... */ }
}

NUnit (zero changes to your tests)

// AssemblyInfo.cs
using GetSkipper.NUnit;

[assembly: SkipperConfig(
    SpreadsheetId = "1abc...",
    CredentialsFile = "service-account.json")]

// Your tests remain unchanged:
[TestFixture]
public class AuthTests
{
    [Test]
    public void CanLogin() { /* ... */ }
}

MSTest (zero changes to your tests)

// SkipperSetup.cs — add once to your test project
using GetSkipper.Core;
using GetSkipper.Core.Credentials;
using GetSkipper.MSTest;
using Microsoft.VisualStudio.TestTools.UnitTesting;

public static class SkipperSetup
{
    [GlobalTestInitialize]
    public static async Task InitAsync()
        => await SkipperGlobalHooks.ConfigureAsync(new SkipperConfig
        {
            SpreadsheetId = "1abc...",
            Credentials = new FileCredentials("service-account.json"),
        });

    [GlobalTestCleanup]
    public static Task CleanupAsync() => SkipperGlobalHooks.AfterAllTestsAsync();
}

// Intercept each test (call from a second [GlobalTestInitialize] or use one method):
public static class SkipperTestGate
{
    [GlobalTestInitialize]
    public static void BeforeTest(TestContext ctx) => SkipperGlobalHooks.BeforeTest(ctx);
}

Playwright (change base class only)

using GetSkipper.NUnit;
using GetSkipper.Playwright;

[assembly: SkipperConfig(SpreadsheetId = "1abc...", CredentialsFile = "service-account.json")]

[TestFixture]
public class LoginTests : SkipperPageTest  // was: PageTest
{
    [Test]
    public async Task UserCanLogin()
    {
        await Page.GotoAsync("/login");
        // automatic skip is handled before this line
    }
}

Reqnroll / SpecFlow (zero changes to step definitions)

// reqnroll.json
{
  "bindingAssemblies": [
    { "assembly": "GetSkipper.SpecFlow" }
  ]
}
SKIPPER_SPREADSHEET_ID=1abc... SKIPPER_CREDENTIALS_FILE=./service-account.json dotnet test

Spreadsheet Format

Column Required Description
testId Yes Canonical test identifier
disabledUntil No ISO-8601 date (2026-06-01). Empty = enabled
notes No Free text

Test ID formats

Framework Format
xUnit / NUnit / MSTest Tests/Unit/AuthTests.cs > AuthTests > CanLogin
Playwright Tests/E2E/LoginTests.cs > LoginTests > UserCanLogin
Reqnroll Features/Auth/Login.feature > User authentication > User can log in

Sync Mode

Reconcile the spreadsheet with your test suite (append new tests, remove deleted ones):

SKIPPER_MODE=sync dotnet test

Typically run on main after all tests pass:

# .github/workflows/ci.yml
- name: Run tests in sync mode
  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: dotnet test

Credentials

Three ways to supply the Google service account:

// 1. JSON file (local dev)
Credentials = new FileCredentials("./service-account.json")

// 2. Base-64 encoded JSON (CI secrets)
Credentials = new Base64Credentials(Environment.GetEnvironmentVariable("GOOGLE_CREDS_B64")!)

// 3. Inline object
Credentials = new ServiceAccountCredentials(
    clientEmail: "skipper@project.iam.gserviceaccount.com",
    privateKey: "-----BEGIN RSA PRIVATE KEY-----\n...")

Environment Variables

Variable Default Description
SKIPPER_MODE read-only Set to sync to reconcile spreadsheet
SKIPPER_SPREADSHEET_ID Spreadsheet ID (used with SpreadsheetIdEnvVar)
GOOGLE_CREDS_B64 Base-64 credentials (used with CredentialsEnvVar)
SKIPPER_DEBUG Set to any value for verbose logging

License

MIT — see LICENSE.

About

Test-gating for .NET — skip tests dynamically via a Google Spreadsheet, without touching your test code.

Resources

License

Contributing

Stars

Watchers

Forks

Contributors

Languages