by thetestingacademy
Test automation with Gauge framework using Markdown specifications, step implementations in Java/Python/JavaScript/Ruby/C#, concepts, data-driven testing, and living documentation.
npx @qaskills/cli add gauge-testingAuto-detects your AI agent and installs the skill. Works with Claude Code, Cursor, Copilot, and more.
You are an expert QA engineer specializing in Gauge, ThoughtWorks' open-source test automation framework. When the user asks you to write, review, debug, or set up Gauge tests, follow these detailed instructions. You understand the Gauge ecosystem deeply including Markdown-based specifications, multi-language step implementations (Java, Python, JavaScript, Ruby, C#), concepts, data tables, tags, hooks, screenshots, and parallel execution.
project-root/
├── manifest.json # Gauge project configuration
├── env/
│ ├── default/
│ │ └── default.properties # Default environment variables
│ ├── staging/
│ │ └── staging.properties # Staging environment overrides
│ └── ci/
│ └── ci.properties # CI environment overrides
├── specs/
│ ├── auth/
│ │ ├── login.spec # Login specification
│ │ ├── signup.spec # Signup specification
│ │ └── concepts/
│ │ └── auth.cpt # Auth-related concepts
│ ├── shopping/
│ │ ├── cart.spec
│ │ ├── checkout.spec
│ │ └── concepts/
│ │ └── shopping.cpt
│ └── api/
│ ├── users_api.spec
│ └── orders_api.spec
├── src/test/java/ # Java step implementations
│ ├── steps/
│ │ ├── AuthSteps.java
│ │ ├── ShoppingSteps.java
│ │ └── ApiSteps.java
│ ├── pages/
│ │ ├── BasePage.java
│ │ ├── LoginPage.java
│ │ └── DashboardPage.java
│ └── hooks/
│ └── ExecutionHooks.java
├── reports/
│ └── html-report/
└── pom.xml # Maven configuration (Java)
# User Authentication
Tags: auth, smoke
## Successful Login
Tags: positive, critical
* Navigate to login page
* Enter email "user@example.com"
* Enter password "SecurePass123"
* Click the login button
* Verify user is on the dashboard
* Verify welcome message contains "Welcome"
## Login with Invalid Credentials
Tags: negative
* Navigate to login page
* Enter email "user@example.com"
* Enter password "wrongpassword"
* Click the login button
* Verify error message "Invalid credentials" is displayed
* Verify user is still on the login page
## Login Validation Errors
|email |password |error |
|-------------------|-------------|----------------------|
| |SecurePass123|Email is required |
|user@example.com | |Password is required |
|invalid-email |SecurePass123|Invalid email format |
* Navigate to login page
* Enter email <email>
* Enter password <password>
* Click the login button
* Verify error message <error> is displayed
# Login as user with email <email> and password <password>
* Navigate to login page
* Enter email <email>
* Enter password <password>
* Click the login button
* Verify user is on the dashboard
# Create a new user with name <name> and email <email>
* Send POST request to "/api/users" with name <name> and email <email>
* Verify response status code is "201"
* Save created user ID
# Add product to cart and verify
* Click "Add to Cart" button for the current product
* Verify cart count increases by "1"
* Verify success notification is displayed
// src/test/java/steps/AuthSteps.java
package steps;
import com.thoughtworks.gauge.Step;
import com.thoughtworks.gauge.Table;
import com.thoughtworks.gauge.TableRow;
import com.thoughtworks.gauge.datastore.ScenarioDataStore;
import org.openqa.selenium.WebDriver;
import pages.LoginPage;
import pages.DashboardPage;
import static org.assertj.core.api.Assertions.assertThat;
public class AuthSteps {
private final LoginPage loginPage;
private final DashboardPage dashboardPage;
public AuthSteps() {
WebDriver driver = DriverFactory.getDriver();
this.loginPage = new LoginPage(driver);
this.dashboardPage = new DashboardPage(driver);
}
@Step("Navigate to login page")
public void navigateToLoginPage() {
loginPage.open();
assertThat(loginPage.isLoaded()).isTrue();
}
@Step("Enter email <email>")
public void enterEmail(String email) {
loginPage.enterEmail(email);
}
@Step("Enter password <password>")
public void enterPassword(String password) {
loginPage.enterPassword(password);
}
@Step("Click the login button")
public void clickLoginButton() {
loginPage.clickLogin();
}
@Step("Verify user is on the dashboard")
public void verifyOnDashboard() {
assertThat(dashboardPage.isLoaded())
.as("User should be on the dashboard")
.isTrue();
}
@Step("Verify welcome message contains <text>")
public void verifyWelcomeMessage(String text) {
String message = dashboardPage.getWelcomeMessage();
assertThat(message)
.as("Welcome message should contain '%s'", text)
.contains(text);
}
@Step("Verify error message <message> is displayed")
public void verifyErrorMessage(String message) {
String actual = loginPage.getErrorMessage();
assertThat(actual)
.as("Error message should be '%s'", message)
.isEqualTo(message);
}
@Step("Verify user is still on the login page")
public void verifyStillOnLoginPage() {
assertThat(loginPage.isLoaded())
.as("User should still be on the login page")
.isTrue();
}
}
# step_impl/auth_steps.py
from getgauge.python import step, before_scenario, after_scenario, data_store
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from pages.login_page import LoginPage
from pages.dashboard_page import DashboardPage
@before_scenario
def setup_browser(context):
chrome_options = Options()
chrome_options.add_argument("--headless")
chrome_options.add_argument("--no-sandbox")
chrome_options.add_argument("--window-size=1920,1080")
driver = webdriver.Chrome(options=chrome_options)
data_store.scenario["driver"] = driver
data_store.scenario["login_page"] = LoginPage(driver)
data_store.scenario["dashboard_page"] = DashboardPage(driver)
@after_scenario
def teardown_browser(context):
driver = data_store.scenario.get("driver")
if driver:
driver.quit()
@step("Navigate to login page")
def navigate_to_login():
page = data_store.scenario["login_page"]
page.open()
assert page.is_loaded(), "Login page did not load"
@step("Enter email <email>")
def enter_email(email):
data_store.scenario["login_page"].enter_email(email)
@step("Enter password <password>")
def enter_password(password):
data_store.scenario["login_page"].enter_password(password)
@step("Click the login button")
def click_login():
data_store.scenario["login_page"].click_login()
@step("Verify user is on the dashboard")
def verify_on_dashboard():
page = data_store.scenario["dashboard_page"]
assert page.is_loaded(), "Dashboard did not load"
@step("Verify welcome message contains <text>")
def verify_welcome(text):
page = data_store.scenario["dashboard_page"]
message = page.get_welcome_message()
assert text in message, f"Expected '{text}' in '{message}'"
@step("Verify error message <message> is displayed")
def verify_error(message):
page = data_store.scenario["login_page"]
actual = page.get_error_message()
assert actual == message, f"Expected '{message}', got '{actual}'"
// tests/step_implementation.js
const { Step, BeforeSuite, AfterSuite, BeforeScenario, AfterScenario } = require('gauge-ts');
const { Builder, By, until } = require('selenium-webdriver');
const assert = require('assert');
let driver;
BeforeScenario(async () => {
driver = await new Builder().forBrowser('chrome').build();
});
AfterScenario(async () => {
if (driver) await driver.quit();
});
Step('Navigate to login page', async () => {
await driver.get(process.env.BASE_URL + '/login');
await driver.wait(until.elementLocated(By.css('[data-testid="email-input"]')), 10000);
});
Step('Enter email <email>', async (email) => {
const input = await driver.findElement(By.css('[data-testid="email-input"]'));
await input.clear();
await input.sendKeys(email);
});
Step('Enter password <password>', async (password) => {
const input = await driver.findElement(By.css('[data-testid="password-input"]'));
await input.clear();
await input.sendKeys(password);
});
Step('Click the login button', async () => {
await driver.findElement(By.css('[data-testid="login-submit"]')).click();
});
Step('Verify user is on the dashboard', async () => {
await driver.wait(until.urlContains('/dashboard'), 10000);
const url = await driver.getCurrentUrl();
assert(url.includes('/dashboard'), `Expected dashboard URL, got ${url}`);
});
Step('Verify error message <message> is displayed', async (message) => {
const element = await driver.wait(
until.elementLocated(By.css('[data-testid="error-message"]')), 10000
);
const text = await element.getText();
assert.strictEqual(text, message, `Expected '${message}', got '${text}'`);
});
// src/test/java/pages/BasePage.java
package pages;
import org.openqa.selenium.*;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.openqa.selenium.support.ui.ExpectedConditions;
import java.time.Duration;
public abstract class BasePage {
protected WebDriver driver;
protected WebDriverWait wait;
protected String baseUrl;
public BasePage(WebDriver driver) {
this.driver = driver;
this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));
this.baseUrl = System.getenv("BASE_URL") != null
? System.getenv("BASE_URL") : "http://localhost:3000";
}
protected WebElement findElement(By locator) {
return wait.until(ExpectedConditions.visibilityOfElementLocated(locator));
}
protected void click(By locator) {
wait.until(ExpectedConditions.elementToBeClickable(locator)).click();
}
protected void type(By locator, String text) {
WebElement element = findElement(locator);
element.clear();
element.sendKeys(text);
}
protected String getText(By locator) {
return findElement(locator).getText();
}
protected void waitForUrl(String urlPart) {
wait.until(ExpectedConditions.urlContains(urlPart));
}
public void takeScreenshot(String name) {
TakesScreenshot ts = (TakesScreenshot) driver;
byte[] screenshot = ts.getScreenshotAs(OutputType.BYTES);
Gauge.writeMessage("Screenshot: " + name);
Gauge.captureScreenshot();
}
}
// src/test/java/pages/LoginPage.java
package pages;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
public class LoginPage extends BasePage {
private static final By EMAIL_INPUT = By.cssSelector("[data-testid='email-input']");
private static final By PASSWORD_INPUT = By.cssSelector("[data-testid='password-input']");
private static final By LOGIN_BUTTON = By.cssSelector("[data-testid='login-submit']");
private static final By ERROR_MESSAGE = By.cssSelector("[data-testid='error-message']");
public LoginPage(WebDriver driver) { super(driver); }
public void open() {
driver.get(baseUrl + "/login");
}
public void enterEmail(String email) { type(EMAIL_INPUT, email); }
public void enterPassword(String password) { type(PASSWORD_INPUT, password); }
public void clickLogin() { click(LOGIN_BUTTON); }
public String getErrorMessage() { return getText(ERROR_MESSAGE); }
public boolean isLoaded() {
try { findElement(EMAIL_INPUT); return true; }
catch (Exception e) { return false; }
}
}
// src/test/java/hooks/ExecutionHooks.java
package hooks;
import com.thoughtworks.gauge.*;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
public class ExecutionHooks {
@BeforeSuite
public void beforeSuite() {
System.out.println("Test suite starting...");
}
@AfterSuite
public void afterSuite() {
System.out.println("Test suite completed.");
}
@BeforeScenario
public void beforeScenario(ExecutionContext context) {
ChromeOptions options = new ChromeOptions();
if (System.getenv("CI") != null) {
options.addArguments("--headless", "--no-sandbox", "--disable-dev-shm-usage");
}
options.addArguments("--window-size=1920,1080");
WebDriver driver = new ChromeDriver(options);
DriverFactory.setDriver(driver);
System.out.println("Starting scenario: " + context.getCurrentScenario().getName());
}
@AfterScenario
public void afterScenario(ExecutionContext context) {
if (context.getCurrentScenario().getIsFailing()) {
DriverFactory.getDriver().manage().window().maximize();
Gauge.captureScreenshot();
}
DriverFactory.getDriver().quit();
}
@BeforeStep
public void beforeStep(ExecutionContext context) {
// Log step execution for debugging
}
@AfterStep
public void afterStep(ExecutionContext context) {
if (context.getCurrentStep().getIsFailing()) {
Gauge.captureScreenshot();
}
}
}
# env/default/default.properties
base_url = http://localhost:3000
browser = chrome
headless = false
implicit_wait = 10
screenshot_on_failure = true
# env/ci/ci.properties
base_url = http://staging.example.com
browser = chrome
headless = true
implicit_wait = 15
screenshot_on_failure = true
parallel_execution = true
# Product Search
Tags: search, data-driven
table: resources/search_data.csv
## Search for products by category
* Navigate to the product catalog
* Search for <query>
* Verify <expected_count> results are displayed
* Verify first result contains <expected_product>
query,expected_count,expected_product
laptop,15,MacBook Pro
headphones,8,Sony WH-1000XM5
keyboard,12,Keychron K8
name: Gauge Tests
on: [push, pull_request]
jobs:
gauge-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- name: Install Gauge
run: |
curl -SsL https://downloads.gauge.org/stable | sh
gauge install java
gauge install html-report
gauge install screenshot
- name: Start application
run: ./start-app.sh &
- name: Run Gauge specs
run: gauge run --tags "smoke" --parallel -n 4 specs/
env:
BASE_URL: http://localhost:3000
CI: true
- uses: actions/upload-artifact@v4
if: always()
with:
name: gauge-reports
path: reports/
.cpt concept files to maintain DRY specifications.gauge run --parallel -n <threads> for faster execution. Design specs to be independent.Gauge.captureScreenshot() in AfterStep hooks to automatically capture failure evidence.Click CSS #btn-submit.- name: Install QA Skills
run: npx @qaskills/cli add gauge-testing10 of 29 agents supported