Using Gauge with JavaScript: A Complete gauge-js Guide

Using Gauge with JavaScript: A Complete gauge-js Guide

gauge-js is the official JavaScript plugin for the Gauge test framework. It lets you write step implementations in Node.js, supports async/await natively, and works well with Playwright, Puppeteer, or any other Node.js testing library. If your team is JavaScript-first and you want Gauge's readable spec format without switching to Java or Python, gauge-js is the right choice.

Key Takeaways

  • gauge-js runs step implementations as a Node.js process alongside the Gauge runner
  • Steps are defined with step(), hooks with beforeSuite(), beforeScenario(), etc.
  • Full async/await support — return a Promise or use async functions in any step
  • The DataStore API (gauge.dataStore) is available for sharing state between steps
  • Playwright is the recommended browser automation library for gauge-js projects in 2024+
  • Use gauge.message() to add custom messages visible in the HTML report

Why gauge-js?

Most Gauge documentation and examples default to Java. The Java plugin is mature and well-documented, but it requires the JVM, Maven or Gradle, and a build step before tests run. For JavaScript teams — especially those already using Playwright or Puppeteer for browser automation — gauge-js removes that friction.

With gauge-js, your entire test stack is Node.js: step implementations, helper utilities, fixtures, and browser automation all live in the same ecosystem. You get npm install, async/await, and access to the entire npm registry without any polyglot complexity.

Installation

Prerequisites: Node.js 18+ and Gauge CLI installed.

# Install Gauge CLI (macOS)
brew install gauge

# Install the JavaScript plugin
gauge install js

# Verify
gauge --version
# Should show js plugin listed

Create a new Gauge JavaScript project:

mkdir my-gauge-project
cd my-gauge-project
gauge init js

This generates:

my-gauge-project/
├── specs/
│   └── example.spec
├── tests/
│   └── step_implementation.js
├── env/
│   └── default/
│       └── default.properties
├── manifest.json
└── package.json

Install dependencies:

npm install

Writing Step Implementations

Step implementations live in any .js file inside the tests/ directory (configurable in manifest.json). Gauge scans all JS files in that directory at startup.

// tests/step_implementation.js
const { step, beforeSuite, afterSuite } = require('gauge-js-api');

step('Open the home page', async () => {
    // your browser automation here
    await page.goto(process.env.BASE_URL || 'http://localhost:3000');
});

step('Click the <buttonName> button', async (buttonName) => {
    await page.click(`button:has-text("${buttonName}")`);
});

step('Verify the page title is <expectedTitle>', async (expectedTitle) => {
    const title = await page.title();
    if (title !== expectedTitle) {
        throw new Error(`Expected title "${expectedTitle}", got "${title}"`);
    }
});

The step() function takes two arguments: the step text (matching the spec exactly, with <param> placeholders) and a callback. The callback receives parameters as positional arguments in the order they appear in the step text.

Async Steps

All steps can be async. gauge-js awaits any returned Promise:

step('Fetch user data for <userId>', async (userId) => {
    const response = await fetch(`${process.env.API_URL}/users/${userId}`);
    if (!response.ok) {
        throw new Error(`API returned ${response.status}`);
    }
    const user = await response.json();
    gauge.dataStore.scenarioStore.put('currentUser', user);
});

step('Verify user email is <expectedEmail>', async (expectedEmail) => {
    const user = gauge.dataStore.scenarioStore.get('currentUser');
    if (user.email !== expectedEmail) {
        throw new Error(`Expected email ${expectedEmail}, got ${user.email}`);
    }
});

If a step throws or rejects, Gauge marks the scenario as failed and records the error message and stack trace.

Hooks

gauge-js provides hooks at four levels:

const { 
    beforeSuite, afterSuite,
    beforeSpec, afterSpec,
    beforeScenario, afterScenario,
    beforeStep, afterStep
} = require('gauge-js-api');

beforeSuite(async () => {
    // Runs once before the entire test suite
    // Good place to start a browser, connect to a database
    global.browser = await chromium.launch({ headless: true });
    global.context = await browser.newContext();
    global.page = await context.newPage();
});

afterSuite(async () => {
    // Runs once after all tests complete
    await browser.close();
});

beforeScenario(async () => {
    // Runs before each scenario
    // Good for resetting application state
    await page.goto(process.env.BASE_URL);
});

afterScenario(async () => {
    // Runs after each scenario — even if the scenario failed
    await page.evaluate(() => localStorage.clear());
});

Hooks can be conditional using tags. Run a hook only for scenarios tagged with a specific tag:

beforeScenario({ tags: ['authenticated'] }, async () => {
    // Only runs for scenarios tagged with @authenticated
    await page.goto('/login');
    await page.fill('#email', process.env.TEST_EMAIL);
    await page.fill('#password', process.env.TEST_PASSWORD);
    await page.click('button[type=submit]');
    await page.waitForURL('/dashboard');
});

The DataStore API

Gauge's DataStore lets you pass data between steps without global variables. There are three store scopes:

Store Lifecycle
scenarioStore Reset after each scenario
specStore Reset after each spec file
suiteStore Persists for the entire suite run
// Store data in a step
step('Create a new order with product <productId>', async (productId) => {
    const response = await fetch(`${API}/orders`, {
        method: 'POST',
        body: JSON.stringify({ productId, qty: 1 }),
        headers: { 'Content-Type': 'application/json' }
    });
    const order = await response.json();
    
    // Save for later steps in this scenario
    gauge.dataStore.scenarioStore.put('orderId', order.id);
    gauge.dataStore.scenarioStore.put('orderTotal', order.total);
});

// Retrieve data in a later step
step('Verify order total matches <expectedTotal>', async (expectedTotal) => {
    const total = gauge.dataStore.scenarioStore.get('orderTotal');
    const expected = parseFloat(expectedTotal);
    const actual = parseFloat(total);
    
    if (Math.abs(actual - expected) > 0.01) {
        throw new Error(`Order total mismatch: expected ${expected}, got ${actual}`);
    }
});

Working with Playwright

Playwright is the best choice for browser automation with gauge-js. It is async-native, has excellent debugging tools, and generates its own traces and screenshots.

Install Playwright:

npm install playwright
npx playwright install chromium

Set up the browser in a beforeSuite hook and share it via the DataStore:

// tests/browser-setup.js
const { chromium } = require('playwright');
const { beforeSuite, afterSuite, beforeScenario } = require('gauge-js-api');

beforeSuite(async () => {
    const headless = process.env.HEADLESS !== 'false';
    
    gauge.dataStore.suiteStore.put('browser', 
        await chromium.launch({ headless })
    );
});

afterSuite(async () => {
    const browser = gauge.dataStore.suiteStore.get('browser');
    await browser.close();
});

beforeScenario(async () => {
    const browser = gauge.dataStore.suiteStore.get('browser');
    const context = await browser.newContext({
        viewport: { width: 1280, height: 720 },
        recordVideo: process.env.RECORD_VIDEO === 'true' 
            ? { dir: 'test-results/videos' } 
            : undefined
    });
    const page = await context.newPage();
    
    gauge.dataStore.scenarioStore.put('page', page);
    gauge.dataStore.scenarioStore.put('context', context);
});

afterScenario(async () => {
    const context = gauge.dataStore.scenarioStore.get('context');
    await context.close();
});

Step implementations then retrieve the page object:

// tests/navigation-steps.js
const { step } = require('gauge-js-api');

function getPage() {
    return gauge.dataStore.scenarioStore.get('page');
}

step('Navigate to <path>', async (path) => {
    const page = getPage();
    const base = process.env.BASE_URL || 'http://localhost:3000';
    await page.goto(`${base}${path}`);
    await page.waitForLoadState('networkidle');
});

step('Click <selector>', async (selector) => {
    const page = getPage();
    await page.click(selector);
});

step('Fill <selector> with <value>', async (selector, value) => {
    const page = getPage();
    await page.fill(selector, value);
});

step('Verify text <expected> is visible', async (expected) => {
    const page = getPage();
    const element = page.getByText(expected);
    const isVisible = await element.isVisible();
    if (!isVisible) {
        // Attach a screenshot to the Gauge report
        const screenshot = await page.screenshot();
        gauge.screenshotFn = async () => screenshot.toString('base64');
        throw new Error(`Text "${expected}" is not visible on the page`);
    }
});

Custom Messages in Reports

Use gauge.message() to add informational text to the Gauge HTML report. This is useful for debugging — log the API response body, the current URL, or any computed value:

step('Submit the checkout form', async () => {
    const page = getPage();
    
    // Intercept the API response for logging
    let apiResponse;
    page.on('response', async (response) => {
        if (response.url().includes('/api/checkout')) {
            apiResponse = await response.json();
        }
    });
    
    await page.click('button#checkout-submit');
    await page.waitForResponse(r => r.url().includes('/api/checkout'));
    
    gauge.message(`Checkout API response: ${JSON.stringify(apiResponse, null, 2)}`);
    
    if (!apiResponse?.success) {
        throw new Error(`Checkout failed: ${apiResponse?.error}`);
    }
});

Messages appear inline in the step output in the HTML report, making it easy to see what was happening at the time of a failure without reading through console logs.

Project Structure for Larger Suites

As the project grows, organize step implementations by domain area:

tests/
├── setup/
│   ├── browser.js         # Browser lifecycle hooks
│   └── auth.js            # Authentication hooks (beforeScenario for @authenticated)
├── steps/
│   ├── navigation.js      # Navigate to, click, fill steps
│   ├── assertions.js      # Verify steps
│   ├── api.js             # Direct API call steps
│   └── forms.js           # Form interaction steps
├── helpers/
│   ├── api-client.js      # Shared fetch wrapper
│   └── test-data.js       # Test data generators
└── page-objects/
    ├── LoginPage.js
    ├── DashboardPage.js
    └── CheckoutPage.js

The page object pattern works naturally with gauge-js:

// tests/page-objects/LoginPage.js
class LoginPage {
    constructor(page) {
        this.page = page;
    }
    
    async goto() {
        await this.page.goto('/login');
    }
    
    async login(email, password) {
        await this.page.fill('#email', email);
        await this.page.fill('#password', password);
        await this.page.click('button[type=submit]');
        await this.page.waitForURL('/dashboard');
    }
    
    async getErrorMessage() {
        return this.page.locator('.error-message').textContent();
    }
}

module.exports = LoginPage;
// tests/steps/auth-steps.js
const { step } = require('gauge-js-api');
const LoginPage = require('../page-objects/LoginPage');

step('Login as <email> with password <password>', async (email, password) => {
    const page = gauge.dataStore.scenarioStore.get('page');
    const loginPage = new LoginPage(page);
    await loginPage.goto();
    await loginPage.login(email, password);
});

Environment Configuration

Use the env/ directory for environment-specific properties and read them in your steps:

# env/default/default.properties
BASE_URL=http://localhost:3000
API_URL=http://localhost:3001
HEADLESS=true
TEST_EMAIL=testuser@example.com
TEST_PASSWORD=testpass123
# env/ci/default.properties
BASE_URL=https://staging.example.com
API_URL=https://api.staging.example.com
HEADLESS=true

Run against the CI environment:

gauge run --env ci specs/

Running Tests

# Run all specs
gauge run specs/

# Run a specific spec
gauge run specs/checkout.spec

# Run specs with a specific tag
gauge run --tags smoke specs/

# Run in parallel (4 streams)
gauge run --parallel --n 4 specs/

# Run with verbose output
gauge run --verbose specs/

# Run only previously failed specs
gauge run --only-failures specs/

Debugging

gauge-js supports the Node.js inspector protocol. Set the GAUGE_DEBUG environment variable to pause execution and attach a debugger:

# Start with debugger waiting for connection
GAUGE_NODE_DEBUG=true gauge run specs/my-spec.spec

Then in your IDE, attach a Node.js remote debugger to localhost:9229. You can set breakpoints in your step implementation files and inspect variables at each step.

For quick debugging without an IDE, use console.log — output appears in the terminal during the run. For output that should appear in the HTML report, use gauge.message() instead.

Common Pitfalls

Missing await on async operations. If you forget await on a Playwright call, the step completes before the browser action finishes, causing the next step to run against the wrong UI state. Always use async/await consistently.

Shared state via globals. Using global.page or module-level variables instead of the DataStore causes subtle failures in parallel runs. Stick to the DataStore for anything accessed across files.

Not closing the browser context after scenarios. Each scenario should get a fresh browser context. If you reuse the same context, cookies and localStorage from one scenario bleed into the next.

Step text mismatch. Gauge matches step text exactly, including whitespace and capitalization. If your spec says Click the "Submit" button and your step is defined as Click the <name> button, the match fails. Use gauge validate specs/ to catch these before running.

Summary

gauge-js makes Gauge a first-class option for JavaScript teams. The API is clean, async/await works throughout, and integration with Playwright gives you a powerful, modern browser automation stack. The main trade-off compared to Java is ecosystem maturity — the Java plugin has more community examples — but for new projects in JavaScript-heavy organizations, gauge-js is entirely production-capable.

Read more

Start now free