Qaskills Gauge Testing
Test automation with Gauge framework using Markdown specifications, step implementations in Java/Python/JavaScript/Ruby/C#, concepts, data-driven testing, and living documentation.
install
source · Clone the upstream repo
git clone https://github.com/PramodDutta/qaskills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/PramodDutta/qaskills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/seed-skills/gauge-testing" ~/.claude/skills/pramoddutta-qaskills-gauge-testing && rm -rf "$T"
manifest:
seed-skills/gauge-testing/SKILL.mdsource content
Gauge Testing
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.
Core Principles
- Readable Specifications — Write specifications in plain Markdown that anyone on the team can read and understand. Specifications are living documentation, not just tests.
- Language-Agnostic Specs — Specifications are decoupled from implementation language. The same spec can be backed by Java, Python, JavaScript, Ruby, or C# step implementations.
- Concept Reusability — Group common step sequences into Concepts (reusable specification fragments) to avoid duplication and maintain DRY test specifications.
- Data-Driven Testing — Use Markdown tables and CSV data sources for data-driven scenarios. Parameterize specifications rather than duplicating them.
- Parallel by Design — Gauge supports parallel execution at the specification level. Design tests for isolation from the start.
- Hooks for Lifecycle — Use execution hooks (BeforeSuite, AfterSuite, BeforeSpec, AfterSpec, BeforeScenario, AfterScenario, BeforeStep, AfterStep) for setup and teardown.
- Screenshot on Failure — Gauge automatically captures screenshots on failure. Configure custom screenshot strategies for non-browser tests.
Project Structure
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)
Detailed Code Examples
Specification File (Markdown)
# 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
Concepts (Reusable Step Groups)
# 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
Step Implementations (Java)
// 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 Implementations (Python)
# 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}'"
Step Implementations (JavaScript)
// 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}'`); });
Page Object (Java)
// 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; } } }
Execution Hooks
// 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(); } } }
Environment Configuration
# 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
Data-Driven Testing with CSV
# 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
CI/CD Integration (GitHub Actions)
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/
Best Practices
- Write specifications in business language — Gauge specs are Markdown, making them natural documentation. Write them so product managers can review and understand.
- Use concepts for reusable sequences — Extract common step patterns into
concept files to maintain DRY specifications..cpt - Organize specs by feature area — Group related specifications in directories. Use tags for cross-cutting concerns (smoke, regression).
- Use data tables for parameterized scenarios — Inline tables or CSV files make data-driven testing clean and easy to extend.
- Use data stores appropriately — ScenarioDataStore for scenario scope, SpecDataStore for specification scope, SuiteDataStore for global data.
- Implement proper Page Objects — Keep step implementations thin. Business logic and browser interactions belong in page objects.
- Configure environment-specific properties — Use Gauge's env directory to manage configuration for different environments.
- Enable parallel execution — Use
for faster execution. Design specs to be independent.gauge run --parallel -n <threads> - Capture screenshots on failure — Use
in AfterStep hooks to automatically capture failure evidence.Gauge.captureScreenshot() - Generate HTML reports — Use gauge's built-in HTML report plugin for comprehensive test results with screenshots and step details.
Anti-Patterns to Avoid
- Avoid implementation details in specs — Specifications should describe behavior, not browser interactions like
.Click CSS #btn-submit - Avoid monolithic step files — Split step implementations by domain (auth, shopping, API) for maintainability.
- Avoid coupling between scenarios — Each scenario must be independently executable. Never depend on previous scenario outcomes.
- Avoid hardcoded data — Use environment properties and data tables. Hardcoded URLs and credentials break portability.
- Avoid long scenarios — Keep scenarios focused on one behavior. If a scenario exceeds 10 steps, extract concepts or decompose.
- Avoid ignoring Gauge's data stores — Use ScenarioDataStore instead of global variables. Data stores are properly scoped and thread-safe.
- Avoid skipping hooks — Always implement AfterScenario to clean up resources (browsers, database records, temp files).
- Avoid duplicate steps — If multiple step files define the same step, Gauge raises ambiguity errors. Centralize shared steps.
- Avoid testing external services directly — Mock external APIs. Gauge tests should verify your application's behavior, not third-party services.
- Avoid missing tags — Tag every specification and scenario. Tags enable selective execution, reporting, and hook targeting.