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 withbeforeSuite(),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 listedCreate a new Gauge JavaScript project:
mkdir my-gauge-project
cd my-gauge-project
gauge init jsThis generates:
my-gauge-project/
├── specs/
│ └── example.spec
├── tests/
│ └── step_implementation.js
├── env/
│ └── default/
│ └── default.properties
├── manifest.json
└── package.jsonInstall dependencies:
npm installWriting 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 chromiumSet 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.jsThe 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=trueRun 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.specThen 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.