Skip to main content

Quick start

Start with the path that matches your repository. New projects should be generated, existing projects should be upgraded, MCP should be connected only after the project runs, and manual setup is the fallback for constrained repos.

Choose your path

Workflow map

Three-minute useful test

The fastest useful evaluation is: generate a project, run the included sample, open the evidence, then decide whether SHAFT removed enough setup work to keep.

  1. Create a generated project from Installation.
  2. Run the project from its root:
mvn test
  1. Confirm Maven reports passing tests.
  2. Open the generated evidence under allure-results and the generated Allure report under the project target output.
  3. If the sample test, screenshots, logs, and report match what you expected, Star SHAFT on GitHub so you can find the project again and track releases.

For a browser-first smoke run, keep the generated defaults or use this minimal configuration:

src/main/resources/properties/custom.properties
targetBrowserName=CHROME
headlessExecution=true
createAnimatedGif=true
allure.singleFile=true

New project generation

Use this when you are creating a SHAFT project from scratch.

Default path

The SHAFT Project Generator is the recommended way to create a new project.

  1. Open the Project Generator.
  2. Choose the test runner: TestNG, JUnit, or Cucumber.
  3. Choose the test surface: Web, Mobile, or API.
  4. Download and extract the generated project.
  5. Run the first build from the project root:
mvn test

Generated projects include matching sample tests, reporting setup, optional GitHub Actions and Dependabot files, and shared IntelliJ IDEA run templates configured with @argFiles.

Existing project upgrade

Use this when you already have Selenium, Appium, REST Assured, Cucumber, legacy SHAFT, or modular SHAFT code.

Safe migration path

The automated upgrade tool updates Maven dependencies, validates the result, and rolls changes back if compilation fails.

Open the automated upgrade and rollback guide from the existing project and run its canonical upgrade command. That page owns the script download, OS-neutral invocation, flags, rollback behavior, and optional repair flow.

MCP integration

Use this after a generated, upgraded, or manual project already compiles. MCP is for agent operations around a working SHAFT project, not a replacement for project setup.

  1. Connect your client from the SHAFT MCP guide.
  2. Keep model credentials in the MCP client or provider configuration, not in source files.
  3. Use Capture and Doctor output as evidence, then review generated code before committing it.

Use the MCP command reference for Capture, Doctor, and remote-server commands. Use the manual configuration section only when the installer cannot update your selected client.

Manual creation

Use this only when the Project Generator or upgrader cannot be used, such as a locked enterprise repository where you must edit files by hand.

Add SHAFT dependencies

Add the BOM and required engine dependency to pom.xml:

<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.github.shafthq</groupId>
<artifactId>shaft-bom</artifactId>
<version>10.2.20260628</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>io.github.shafthq</groupId>
<artifactId>shaft-engine</artifactId>
</dependency>
</dependencies>

Add optional modules only when a test uses that capability. For example, reference-image assertions need shaft-visual; ordinary browser, mobile, API, database, CLI, screenshots, reporting, and test data workflows use shaft-engine.

Create the test skeleton

  1. Create src/test/java/testPackage.
  2. Create src/test/resources/testDataFiles/simpleJSON.json.
  3. Copy this test data:
{
"searchQuery": "SHAFT_Engine",
"expectedTitle": "DuckDuckGo",
"unexpectedInFirstResult": "Nope"
}
  1. Pick one runner sample below.
  2. Run the selected test from your IDE or with Maven.

On the first test run, SHAFT creates src/main/resources/properties, generates default properties, runs in minimalistic test run mode, and self-configures listeners under src/test/resources/META-INF/services. On later runs, the Allure execution report opens automatically in your default browser.

IDE run templates

Project Generator projects already include shared IntelliJ IDEA run templates. For older or manually created projects, open Run > Edit Configurations > Edit configuration templates and set Shorten command line to @argFiles (Java 9+) for the Java, JUnit, TestNG, and Cucumber Java templates you use. Delete stale temporary runs created before changing the template.

TestNG sample

Create src/test/java/testPackage/TestClass.java:

package testPackage;

import com.shaft.driver.SHAFT;
import com.shaft.gui.internal.locator.Locator;
import org.openqa.selenium.By;
import org.openqa.selenium.Keys;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

public class TestClass {
SHAFT.GUI.WebDriver driver;
SHAFT.TestData.JSON testData;

String targetUrl = "https://duckduckgo.com/";

By logo = By.xpath("//div[contains(@class,'container_fullWidth__1H_L8')]//img");
By searchBox = Locator.hasAnyTagName().hasAttribute("name", "q").build();
By firstSearchResult = Locator.hasTagName("article").isFirst().build();

@Test
public void navigateToDuckDuckGoAndAssertBrowserTitleIsDisplayedCorrectly() {
driver.browser().navigateToURL(targetUrl)
.and().assertThat().title().contains(testData.get("expectedTitle"));
}

@Test
public void navigateToDuckDuckGoAndAssertLogoIsDisplayedCorrectly() {
driver.browser().navigateToURL(targetUrl)
.and().element().assertThat(logo).isVisible();
}

@Test
public void searchForQueryAndAssert() {
driver.browser().navigateToURL(targetUrl)
.and().element().type(searchBox, testData.get("searchQuery") + Keys.ENTER)
.and().assertThat(firstSearchResult).text().doesNotEqual(testData.get("unexpectedInFirstResult"));
}

@BeforeClass
public void beforeClass() {
testData = new SHAFT.TestData.JSON("simpleJSON.json");
}

@BeforeMethod
public void beforeMethod() {
driver = new SHAFT.GUI.WebDriver();
}

@AfterMethod
public void afterMethod() {
driver.quit();
}
}
JUnit sample

Generated JUnit projects enable the SHAFT listener and extension through src/test/resources/junit-platform.properties. Existing projects should keep junit.jupiter.extensions.autodetection.enabled=true in that file.

Create src/test/java/testPackage/TestClass.java:

package testPackage;

import com.shaft.driver.SHAFT;
import com.shaft.gui.internal.locator.Locator;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.Keys;

public class TestClass {
private SHAFT.GUI.WebDriver driver;
private static SHAFT.TestData.JSON testData;

String targetUrl = "https://duckduckgo.com/";

By logo = By.xpath("//div[contains(@class,'container_fullWidth__1H_L8')]//img");
By searchBox = Locator.hasAnyTagName().hasAttribute("name", "q").build();
By firstSearchResult = Locator.hasTagName("article").isFirst().build();

@Test
public void navigateToDuckDuckGoAndAssertBrowserTitleIsDisplayedCorrectly() {
driver.browser().navigateToURL(targetUrl)
.and().assertThat().title().contains(testData.get("expectedTitle"));
}

@Test
public void navigateToDuckDuckGoAndAssertLogoIsDisplayedCorrectly() {
driver.browser().navigateToURL(targetUrl)
.and().element().assertThat(logo).isVisible();
}

@Test
public void searchForQueryAndAssert() {
driver.browser().navigateToURL(targetUrl)
.and().element().type(searchBox, testData.get("searchQuery") + Keys.ENTER)
.and().assertThat(firstSearchResult).text().doesNotEqual(testData.get("unexpectedInFirstResult"));
}

@BeforeAll
public static void beforeAll() {
testData = new SHAFT.TestData.JSON("simpleJSON.json");
}

@BeforeEach
public void beforeEach() {
driver = new SHAFT.GUI.WebDriver();
}

@AfterEach
public void afterEach() {
driver.quit();
}
}
Cucumber sample

Create src/test/java/cucumberTestRunner/CucumberTests.java:

package cucumberTestRunner;

import io.cucumber.testng.AbstractTestNGCucumberTests;
import org.testng.annotations.Listeners;

@Listeners({com.shaft.listeners.TestNGListener.class})
public class CucumberTests extends AbstractTestNGCucumberTests {
}

Create src/test/java/customCucumberSteps/StepDefinitions.java:

package customCucumberSteps;

import com.shaft.driver.SHAFT;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import org.openqa.selenium.By;
import org.openqa.selenium.Keys;

public class StepDefinitions {
private SHAFT.GUI.WebDriver driver;
private SHAFT.TestData.JSON testData;

@Given("I open the target browser")
public void i_open_the_target_browser() {
driver = new SHAFT.GUI.WebDriver();
testData = new SHAFT.TestData.JSON("simpleJSON.json");
}

@When("I navigate to {string}")
public void i_navigate_to(String url) {
driver.browser().navigateToURL(url);
}

@When("I search for {string}")
public void i_search_for(String query) {
By searchBox = By.name("q");
driver.element().type(searchBox, query + Keys.ENTER);
}

@Then("I should see the page title contains {string}")
public void i_should_see_the_page_title_contains(String expectedTitle) {
driver.assertThat().browser().title().contains(expectedTitle);
}

@Then("I close the browser")
public void i_close_the_browser() {
driver.quit();
}
}

Create src/test/resources/features/search.feature:

Feature: Search functionality

Scenario: Verify DuckDuckGo search
Given I open the target browser
When I navigate to "https://duckduckgo.com/"
Then I should see the page title contains "DuckDuckGo"
When I search for "SHAFT_Engine"
Then I close the browser

For manually created Cucumber projects, edit the Cucumber Java run configuration template and add:

--plugin com.shaft.listeners.CucumberFeatureListener

Run and configure

Use the properties reference to configure SHAFT. After upgrading to a new major release, it is sometimes recommended to delete src/main/resources/properties and allow SHAFT to regenerate the defaults by running any test method.

Join our GitHub Repo stars to get notified by email when a new release is pushed out.

Optional modular integrations

Use shaft-engine first. Add optional modules only when a test calls their capability.

CapabilityAdd this module
Reference-image assertions, negative reference-image assertions, image-path touch actionsshaft-visual
BrowserStack SDK interception and YAML orchestrationshaft-browserstack
Local non-headless desktop recordingshaft-video
SikuliX image-based desktop automationshaft-sikulix
Deterministic web locator recoveryshaft-heal plus healing.strategy=shaft-heal

Reference-image assertions require shaft-visual:

driver.browser().navigateToURL(targetUrl)
.and().element().assertThat(logo).matchesReferenceImage();

Ordinary screenshots, highlighted report screenshots, API tests, Appium locator actions, database tests, CLI tests, direct BrowserStack sessions, and Appium-native recording need only shaft-engine.

Windows desktop Appium sessions also stay in shaft-engine; use shaft-sikulix only when the test needs SikuliX image matching.

Use the module selection and migration guide for the complete method matrix.