Gauge Test Automation: Advanced Concepts and Real-World Patterns

Gauge Test Automation: Advanced Concepts and Real-World Patterns

Gauge looks deceptively simple on the surface — Markdown specs, step definitions, a CLI. But the framework hides substantial depth: scoped data stores, a plugin system that replaces what Cucumber handles with hooks, and parallel execution that scales without test pollution. This post covers the patterns that separate Gauge projects that scale from ones that collapse under their own weight.

Data Stores: Scoped State Without Global Variables

The most underused Gauge feature is its three-tier data store system. Instead of reaching for static variables or thread-local storage, Gauge gives you contextual stores that are automatically scoped and cleared.

ScenarioStore — lives for one scenario, then cleared. Use this for anything that should not bleed between scenarios: tokens, created resource IDs, temporary file paths.

SpecStore — lives for one specification file. Useful when multiple scenarios in the same spec file share setup (e.g., a user created in a ## Before Spec step that all scenarios in the file use).

SuiteStore — lives for the entire test run. Reserve this for expensive resources: a seeded database state, a started server, a browser session pool.

// Java example — storing and retrieving across steps
import com.thoughtworks.gauge.datastore.*;

@Step("Create user with email <email>")
public void createUser(String email) {
    String userId = userService.create(email);
    ScenarioDataStore.put("userId", userId);
}

@Step("Verify user exists in the system")
public void verifyUserExists() {
    String userId = (String) ScenarioDataStore.get("userId");
    assertThat(userService.find(userId)).isNotNull();
}
// JavaScript equivalent
const { ScenarioStore } = require('gauge-ts');

step('Create user with email <email>', async (email) => {
    const userId = await userService.create(email);
    ScenarioStore.put('userId', userId);
});

step('Verify user exists in the system', async () => {
    const userId = ScenarioStore.get('userId');
    const user = await userService.find(userId);
    expect(user).toBeDefined();
});

The mistake most teams make is stuffing everything into SuiteStore for convenience and then debugging mysteriously stateful test runs for hours.

Parallel Execution

Gauge supports parallel execution at the spec level. Each spec file runs in its own process — which means steps in parallel specs cannot share in-process state. This forces good isolation by default.

Enable parallel execution from the CLI:

gauge run --parallel specs/
gauge run --parallel -n 4 specs/   # 4 streams
gauge run --parallel --group 2 specs/  # run specific group (for CI matrix)

For CI matrix builds (GitHub Actions, GitLab CI), the --group flag combined with --parallel-nodes is the right approach:

# .github/workflows/test.yml
strategy:
  matrix:
    group: [1, 2, 3, 4]
steps:
  - run: gauge run --parallel -n 4 --group ${{ matrix.group }} specs/

This splits the spec suite evenly across CI workers without any coordination overhead.

SuiteStore in parallel runs: When running in parallel, each process gets its own SuiteStore. If you need truly shared state (e.g., a shared test database URL), use environment variables or external config, not SuiteStore.

Plugin Ecosystem

Gauge's plugin system handles what other frameworks solve with hooks and third-party libraries.

HTML Report (installed by default):

gauge install html-report

Generates a navigable report at reports/html-report/index.html. Failures include step-level screenshots if the Screengrabber plugin is also installed.

XML Report (for CI systems that consume JUnit XML):

gauge install xml-report

Outputs JUnit-compatible XML to reports/xml-report/. Most CI systems (Jenkins, CircleCI, TeamCity) parse this natively.

Screengrabber (browser test screenshots on failure):

gauge install screenshot

After installing, screenshots are automatically embedded in the HTML report for failed steps — no extra code in step definitions.

Spectacle — converts Gauge specs to a static HTML documentation site. Worth running as part of your CI pipeline to publish living documentation.

gauge install spectacle
gauge docs spectacle specs/

Step Implementations: Java vs JavaScript

The step implementation style differs significantly between language runners.

Java encourages grouping related steps in classes. Gauge scans all classes on the classpath — no registration needed.

public class CheckoutSteps {
    private CartPage cartPage;

    @BeforeScenario
    public void setUp() {
        cartPage = new CartPage(driver);
    }

    @Step("Add <quantity> units of <product> to cart")
    public void addToCart(int quantity, String product) {
        cartPage.addItem(product, quantity);
        // Gauge converts parameter types automatically
    }

    @Step("Cart total should be <amount>")
    public void verifyTotal(double amount) {
        assertThat(cartPage.getTotal()).isEqualTo(amount, offset(0.01));
    }
}

JavaScript (via @getgauge/gauge-js or the TypeScript runner) uses a flat registration model:

const { step, beforeScenario } = require('@getgauge/gauge-js');

let cartPage;

beforeScenario(async () => {
    cartPage = new CartPage(page);
});

step('Add <quantity> units of <product> to cart', async (quantity, product) => {
    await cartPage.addItem(product, parseInt(quantity));
});

step('Cart total should be <amount>', async (amount) => {
    const total = await cartPage.getTotal();
    expect(total).toBeCloseTo(parseFloat(amount), 2);
});

Note that Gauge passes all parameters as strings in JavaScript — type conversion is your responsibility.

Handling Async Steps

In JavaScript, async steps are fully supported. Gauge awaits the returned Promise before moving to the next step:

step('Wait for order <orderId> to be processed', async (orderId) => {
    await waitUntil(
        () => orderService.getStatus(orderId) === 'processed',
        { timeout: 10000, interval: 500 }
    );
});

In Java, Gauge steps are synchronous by default. For async operations, use CompletableFuture or block explicitly:

@Step("Wait for order <orderId> to be processed")
public void waitForOrder(String orderId) throws Exception {
    Awaitility.await()
        .atMost(10, SECONDS)
        .pollInterval(500, MILLISECONDS)
        .until(() -> orderService.getStatus(orderId).equals("processed"));
}

Environment Management

Gauge's environment system allows environment-specific configuration without code changes. Environment files live in env/<name>/ directories:

env/
  default/
    default.properties
  staging/
    staging.properties
  production/
    production.properties

Run against a specific environment:

gauge run --env staging specs/

Properties are available in step implementations via System.getenv() (Java) or process.env (JavaScript). Gauge merges default with the specified environment, so you only override what differs.

# env/staging/staging.properties
BASE_URL = https://staging.example.com
API_TIMEOUT = 5000
HEADLESS = true

Scaling a Gauge Test Suite

A few patterns that matter once a suite grows past a few dozen specs:

Tag-based execution — tag specs and steps for targeted runs:

# Checkout Flow
tags: checkout, critical, smoke

## Guest checkout with credit card
tags: credit-card
gauge run --tags "smoke" specs/           # only smoke tests
gauge run --tags "critical && !flaky" specs/  # critical but not flaky

Concept extraction — Gauge Concepts (.cpt files) let you name a sequence of steps as a single reusable step. This is not a step definition — it is a spec-level abstraction:

# Login as admin
* Navigate to "/admin/login"
* Enter "admin@example.com" in "Email"
* Enter "secret" in "Password"
* Click "Sign In"
* Wait for dashboard to load

Reference it in any spec as * Login as admin. When the login flow changes, one file changes — not 40 specs.

Spec organization — keep specs in domain directories, not by type. specs/checkout/, specs/inventory/, specs/auth/ scales better than specs/happy-paths/, specs/error-cases/.

Complementing Gauge with Continuous Monitoring

Gauge covers acceptance-level scenarios triggered by commits or scheduled runs. What it does not cover is production behavior between deployments. A checkout flow that passes in staging can degrade in production due to third-party API latency, database load, or config drift.

HelpMeTest runs your critical user journeys against production continuously — no code required, plain English test definitions. When a Gauge suite defines the acceptance criteria, HelpMeTest validates that production keeps meeting it. The two complement each other: Gauge catches regressions before release, HelpMeTest catches degradations after release.

Read more

Start now free