# SHAFT User Guide > SHAFT is a unified test automation engine for Web, Mobile, API, CLI, and Database testing. --- # Choose a path Canonical HTML: https://shafthq.github.io/docs/journeys Markdown: https://shafthq.github.io/md/journeys.md # Choose a path SHAFT is a Java test framework (`io.github.shafthq:shaft-engine`). This guide is a set of jobs. Open the one that matches what you have today. Pages that explain how SHAFT works stay on the site for search and for agents, and they are not in this list. | You are | Path | Done when | |---|---|---| | Starting from nothing | [Create a project and run the sample](/docs/journeys/create-a-project) | The sample test runs and the report is on disk | | Already have a Maven test project | [Upgrade a Java project](/docs/journeys/upgrade-a-project) | The project compiles on current SHAFT and one test runs | | Already have a coding agent | [Install SHAFT skills](/docs/journeys/skills) | A new agent session loads `$shaft-developer` | | Working in IntelliJ IDEA | [Use the IntelliJ plugin](/docs/journeys/intellij) | **Tools \| SHAFT \| Open SHAFT** opens and setup verifies | | Using Codex, Copilot, Claude, or another MCP client | [Connect an MCP client](/docs/journeys/mcp) | The client lists SHAFT tools and one tool call returns | | Staying in the terminal | [Use shaft-cli](/docs/journeys/cli) | `shaft-cli` runs one command | | Putting the suite on a server | [Run the suite in CI](/docs/journeys/ci) | The pipeline runs the suite headlessly and keeps the report | | Already running SHAFT, need another kind of test | [Add a test surface](/docs/journeys/add-a-surface) | One new web, API, mobile, Flutter, database, CLI, or contract check runs | | Missing Android, Appium, Grid, or browsers | [Install local infrastructure](/docs/start/local-infrastructure) | `shaft-cli setup verify` passes for the profile you chose | | Want the project-local agent harness | [Install ChaosEngine](/docs/agentic/chaos-engine) | The installer prints a successful JSON result | ```bash mvn test ``` That command is the check for a generated or upgraded project. Other paths name their own check on the page. ## Related - [Create a project and run the sample](/docs/journeys/create-a-project) --- # Create a project and run the sample Canonical HTML: https://shafthq.github.io/docs/journeys/create-a-project Markdown: https://shafthq.github.io/md/journeys/create-a-project.md # Create a project and run the sample For a new repository. The result is a Maven project whose sample test has run and written a report. You need the JDK and Maven version named on [Install SHAFT](/docs/start/installation). ## Steps 1. Open [Install SHAFT](/docs/start/installation) and use the project generator. Pick the test runner and surfaces. Download the project. 2. From the project root, run: ```bash mvn test ``` 3. Confirm the sample ran (the Maven summary is not `Total: 0`) and evidence is at . ## If the sample does not run Stay on [Install SHAFT](/docs/start/installation). That page owns the Java, Maven, browser, and Surefire checks. Do not hand-write a POM until that page says generation is impossible. Next, if you need a pipeline: [Run the suite in CI](/docs/journeys/ci). How the generator chooses modules is background, not part of this path: [Features and modules](/docs/features/modules). ## Related - [Install SHAFT](/docs/start/installation) --- # Upgrade a Java project Canonical HTML: https://shafthq.github.io/docs/journeys/upgrade-a-project Markdown: https://shafthq.github.io/md/journeys/upgrade-a-project.md # Upgrade a Java project For a Maven project that already has tests. The result is that project compiling against current modular SHAFT, with one test run. You need JDK 25, Maven or `mvnw`, and Python 3.9+ as `python`. Commit or stash local edits first. ## Steps 1. From the project root, follow [Run the upgrade](/docs/start/upgrade/run) through the download and the plan prompt. Answer `y` only after you read the plan. 2. Keep the default `basic` type unless you intentionally want Selenium or Appium session code rewritten. The same page owns `session` and `full`. 3. Run one test: ```bash mvn test ``` The check is a compile plus a test count above zero. If Maven reports `Total: 0`, the Surefire provider note on [Install SHAFT](/docs/start/installation) applies. What the upgrader guarantees is background: [How the upgrade works](/docs/start/upgrade/how-it-works). Flags and coordinates: [Upgrade reference](/docs/start/upgrade/reference). ## Related - [Run the upgrade](/docs/start/upgrade/run) --- # Install SHAFT skills in your agent Canonical HTML: https://shafthq.github.io/docs/journeys/skills Markdown: https://shafthq.github.io/md/journeys/skills.md # Install SHAFT skills in your agent For a project that already has an agent (Codex, Claude, Copilot, Grok, or another client that reads skill files). The pack is instructions, not a hosted model. The result is a new session that can load `$shaft-developer`. ## Steps 1. From the project root, install skills only. On macOS or Linux: ```bash curl -fsSL https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/scripts/mcp/install-shaft-agentic-tools.sh | sh -s -- --install-shaft-skills ``` 2. Start a new agent session in that project. 3. Ask it to load `$shaft-developer`. The check is that the agent finds the skill. Other install routes (Claude marketplace, Skills CLI, MCP init) are on [Install SHAFT agent skills](/docs/agentic/skills). Use those only if this installer is not the route you want. If the agent also needs SHAFT tools, continue to [Connect an MCP client](/docs/journeys/mcp) or [Use shaft-cli](/docs/journeys/cli). ## Related - [Install SHAFT agent skills](/docs/agentic/skills) --- # Use the IntelliJ plugin Canonical HTML: https://shafthq.github.io/docs/journeys/intellij Markdown: https://shafthq.github.io/md/journeys/intellij.md # Use the IntelliJ plugin For IntelliJ IDEA. The result is the SHAFT tool window open and setup verified. An AI agent connection is optional. ## Steps 1. Install the SHAFT plugin from JetBrains Marketplace, or install a ZIP and restart if the IDE asks. 2. Open **Tools | SHAFT | Open SHAFT**. If setup is not finished, the IDE shows one **Set up SHAFT** notification. It opens the tool window. It is not a dialog that blocks the IDE. 3. **This project** shows the JDK, build tool, operating system, and whether an MCP command is already present. Continue. 4. **Prerequisites** lists only a missing JDK, Maven 3.9+, or Git. Tools that are already present stay on one **Ready** line. 5. **Agent** recommends one agent. Other agents stay behind **Use a different agent**. An API key field appears only for an agent that needs one, and the plugin stores that key in Password Safe. 6. **Install and check** copies the installer command and pre-types it in the IDE terminal. Review it and press Enter yourself. The plugin does not run the installer. Press **Check** afterward. **Verified** appears only after that check passes. 7. **First success** finishes setup when you choose **Open the Assistant** or **Record a sample flow**. **Skip** leaves setup unfinished and does not delete chat or saved keys. People who already verified MCP are not shown the wizard again. A plugin update drops a stale MCP command and keeps a verified install on the main view. The check is a verified tool window, not a generated test. Recorder, Doctor, and coding-partner actions are documented on [IntelliJ IDEA plugin](/docs/agentic/intellij). Follow one action there after this page is done. ```text Tools | SHAFT | Open SHAFT ``` ## Related - [IntelliJ IDEA plugin](/docs/agentic/intellij) --- # Connect an MCP client Canonical HTML: https://shafthq.github.io/docs/journeys/mcp Markdown: https://shafthq.github.io/md/journeys/mcp.md # Connect an MCP client For an MCP client you already run. The result is that client listing SHAFT tools and one call returning. Do this after a project [runs](/docs/journeys/create-a-project) or [upgrades](/docs/journeys/upgrade-a-project). IntelliJ users should use [the plugin path](/docs/journeys/intellij) instead of this page. ## Steps 1. Change into the project directory. 2. Run the installer for your OS. It configures SHAFT agent tools in the current project: ```bash curl -fsSL "https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/scripts/mcp/install.sh" | bash ``` ```powershell irm "https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/scripts/mcp/install.ps1" | iex ``` 3. Restart the agent session so it reloads MCP servers. 4. Ask it to call guide search: ```json {"tool": "shaft_guide_search", "arguments": {"query": "create a project", "maxResults": 3}} ``` The check is a tool result, not a rewritten test. Client flags and the tool list stay on [Connect shaft-mcp](/docs/agentic/mcp). ## Related - [Connect shaft-mcp](/docs/agentic/mcp) --- # Use shaft-cli Canonical HTML: https://shafthq.github.io/docs/journeys/cli Markdown: https://shafthq.github.io/md/journeys/cli.md # Use shaft-cli For a terminal-only workflow. The result is `shaft-cli` on your PATH running one command. No IDE and no MCP client configuration. ## Steps 1. From any directory, install the CLI and its MCP runtime: ```bash curl -fsSL https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/scripts/mcp/install-shaft-agentic-tools.sh | sh -s -- --install-shaft-cli ``` ```powershell powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/scripts/mcp/install-shaft-agentic-tools.ps1 | iex; Install-ShaftMcp -Arguments @('--install-shaft-cli')" ``` 2. Open a new terminal and run: ```bash shaft-cli --help ``` The check is a help listing, not a browser session. Command names and session mode are on [shaft-cli](/docs/agentic/cli). Local Android, Grid, and browser installs continue at [Install local infrastructure](/docs/start/local-infrastructure). ## Related - [shaft-cli command line](/docs/agentic/cli) --- # Run the suite in CI Canonical HTML: https://shafthq.github.io/docs/journeys/ci Markdown: https://shafthq.github.io/md/journeys/ci.md # Run the suite in CI For a project that already runs locally. The result is the same suite running in a pipeline without a display, and an Allure archive saved as a build artifact. ## Steps 1. Use a JDK and Maven that match [Install SHAFT](/docs/start/installation). 2. Run: ```bash mvn -e test \ -DheadlessExecution=true \ -Dallure.automaticallyOpen=false \ -Dallure.generateArchive=true ``` 3. Publish the Allure results or archive your pipeline already collects. The job is done when the test command exits and the archive artifact exists. Property overrides and Jenkins or GitHub Actions notes stay on [Running tests in CI/CD pipelines](/docs/reference/guides/CI_CD_Integration). ## Related - [Running tests in CI/CD pipelines](/docs/reference/guides/CI_CD_Integration) --- # Add a test surface Canonical HTML: https://shafthq.github.io/docs/journeys/add-a-surface Markdown: https://shafthq.github.io/md/journeys/add-a-surface.md # Add a test surface For a project that already has a green `mvn test`. Pick one surface, follow that page's sample, and stop when that sample is part of the same run. | Surface | Page | |---|---| | Browser | [Web testing](/docs/testing/web) | | HTTP API | [API testing](/docs/testing/api) | | Android, iOS, or mobile web | [Mobile and Flutter testing](/docs/testing/mobile) | | Flutter locators | [Flutter testing](/docs/testing/flutter) | | JDBC | [Database testing](/docs/testing/database) | | Local, Docker, or SSH commands | [CLI testing](/docs/testing/cli) | | Recorded HTTP or browser contracts | [UI and API contract replay](/docs/testing/contracts) | Generate the extra surface from [Install SHAFT](/docs/start/installation) when you would rather start a new module than edit the POM by hand. Device and browser installs are [Install local infrastructure](/docs/start/local-infrastructure), after the sample compiles. ```bash mvn test ``` Why a locator flake happens is background: [How SHAFT reduces flakiness](/docs/testing/flakiness). ## Related - [Web testing](/docs/testing/web) --- # Install managed mobile infrastructure Canonical HTML: https://shafthq.github.io/docs/start/local-infrastructure/mobile Markdown: https://shafthq.github.io/md/start/local-infrastructure/mobile.md # Install managed mobile infrastructure Use these steps to let SHAFT own the Android and Appium toolchain, or the Appium drivers for iOS and Windows. Every install follows the shared [review and approve](/docs/start/local-infrastructure#review-and-approve-an-installation) flow; policy options and exit codes are in the [infrastructure reference](/docs/start/local-infrastructure/reference). ## Install managed Android and Appium :::warning[Not in a published release yet] The `MOBILE_ANDROID` provider is available on `SHAFT_ENGINE` `main` after [engine PR #4913](https://github.com/ShaftHQ/SHAFT_ENGINE/pull/4913) and its [readiness follow-up #4917](https://github.com/ShaftHQ/SHAFT_ENGINE/pull/4917). Use these commands with a source build until a containing SHAFT release is published. ::: Use this profile when you want SHAFT to own one local Android emulator and its Appium server. The release plan contains exactly six ordered actions: 1. Node 24.19.0. 2. Appium 3.6.0. 3. Inspector plugin 2026.7.1. 4. UiAutomator2 8.2.2. 5. Android command-line tools 15859902, platform-tools, Emulator, API 36, build-tools 36.0.0 with `aapt2`, and a host-compatible Google APIs image. 6. A SHAFT-owned Pixel 8 AVD. SHAFT verifies the official command-line-tools archive hash and the packaged Appium dependency lock before publication. It runs npm inside a versioned SHAFT project, not as a global install, and never enables Appium relaxed security for the owned server. ### Check platform prerequisites Install a compatible JDK and enable hardware virtualization for your host before planning a runtime. On Linux, grant the current user access to KVM. On Windows, enable a supported Android Emulator hypervisor path. On macOS, run on a host that can use Apple's virtualization support. SHAFT diagnoses these conditions with `emulator -accel-check`; it does not elevate, enable firmware features, change group membership, install host drivers, or edit shell profiles. Keep loopback ports `5554` and `5555` free for the owned emulator. The default Appium port is `4723`; choose another free port with `--port` when creating the plan. The selected system-image ABI must match the host architecture. ### Review the Android plan and license Run the shared command sequence below. Stop after `plan`, inspect all six actions and the printed digest, then continue with that exact digest: `android-sdk-license` is an explicit approval identifier. Omitting it or supplying a different identifier fails before SHAFT creates setup roots, starts a process, or contacts the network. SHAFT supplies consent only to the reviewed package installation; it does not run a blanket `sdkmanager --licenses` acceptance. Use these selectors on `doctor`, `status`, `plan`, `verify`, `start`, `logs`, or `stop`. You may repeat them on `install`, but every repeated value must match the plan: | Option | Release default | Constraint | |---|---:|---| | `--api-level` | `36` | Must match the release manifest. | | `--device-profile` | `pixel_8` | Must match the release manifest. | | `--image-tag` | `google_apis` | Must match the release manifest. | | `--abi` | `host` | Resolves to `x86_64` or `arm64-v8a` for the current host. | | `--avd-name` | `shaft_pixel_8_api_36` | Use a safe Android identifier. | | `--ram-mb` | `4096` | Choose 2048 through 32768 MB. | | `--cores` | `2` | Choose 1 through 16 cores. | | `--port` | `4723` | Choose 1024 through 65535, excluding `5554` and `5555`. | The persisted plan remains authoritative. Install reconstructs the typed request from the plan and rejects changed, missing, duplicate, or unknown selection metadata. ### Use the cache and offline mode SHAFT stores verified downloads and npm cache data under its cache root. It stores versioned Node, Appium, Android SDK, AVD, receipts, leases, and logs under its data root. Set both `--cache-root` and `--data-root` to absolute, dedicated user paths if you override the defaults. Pass `--offline` to both plan and install when network access is prohibited. The Android provider currently accepts offline installation only when every required managed component is already present and verifies exactly. A cold, partial, corrupt, wrong-version, or linked state fails before npm, `sdkmanager`, or another setup process starts. Run one approved online install to populate the managed state before relying on offline reuse. ### Start, inspect, and recover the owned runtime `start` requires the reviewed plan, matching approval, accepted license, and a compatible final receipt. It starts the emulator first and waits for the exact serial, Android boot completion, package manager, selected AVD, and system image. It then starts Appium on `127.0.0.1` and accepts it only when `/status` reports Appium 3.6.0. Compatible callers share a durable lease and increment its reference count. The final release or `setup stop` shuts down Appium, then the emulator, after validating each PID, start instant, command, root, and endpoint. SHAFT never adopts or kills an unknown process. `setup logs` reads only the two owned log paths and rejects a file larger than 2 MiB. If startup fails after the emulator launches, SHAFT stops only the processes started by that call and retains their logs. If a lease is partially alive or its identity differs from the live process, preserve the lease and logs and inspect them before manual recovery. If both processes are gone, the next start removes the stale lease. Resolve occupied ports, acceleration failures, or wrong SDK/AVD revisions, then retry the same reviewed plan. ### Use the typed Java API Use `AndroidSetupRequest` with the additive `SHAFT.Infrastructure` overloads. The first phase writes a plan for review: ```java title="PlanManagedAndroid.java" public final class PlanManagedAndroid { public static void main(String[] args) throws Exception { SetupOptions options = SetupOptions .defaults(SetupProfile.MOBILE_ANDROID, ShaftCachePaths.current()) .withMode(SetupMode.MANAGED); AndroidSetupRequest request = AndroidSetupRequest.defaults(); SetupPlan plan = SHAFT.Infrastructure.plan(options, request); SetupPlanStore.write(Path.of(args[0]).toAbsolutePath(), plan); System.out.println(plan.digest()); } } ``` Run installation and startup only after a separate review supplies the digest: ```java title="RunReviewedAndroid.java" public final class RunReviewedAndroid { public static void main(String[] args) throws Exception { SetupPlan plan = SetupPlanStore.read(Path.of(args[0]).toAbsolutePath()); AndroidSetupRequest request = AndroidSetupRequest.fromPlan(plan); SetupOptions options = SetupOptions .defaults(SetupProfile.MOBILE_ANDROID, ShaftCachePaths.current()) .withMode(SetupMode.MANAGED); SetupApproval approval = new SetupApproval( System.getenv("SHAFT_APPROVED_SETUP_DIGEST"), Instant.now(), Set.of("android-sdk-license")); SHAFT.Infrastructure.install(plan, approval, options, request); try (ManagedEnvironment runtime = SHAFT.Infrastructure.start(plan, approval, options, request)) { System.out.println(runtime.endpoint().orElseThrow()); } } } ``` Closing `ManagedEnvironment` releases this caller's lease. Another compatible caller can keep the same runtime alive until its own release. ## Install managed iOS and Windows Appium drivers Use `MOBILE_IOS` on macOS to install SHAFT's pinned Appium, Inspector, and XCUITest driver bundle. Install full Xcode 14.3 or newer and create at least one Simulator device first. SHAFT diagnoses those host prerequisites but does not install Xcode, download Simulator runtimes, or change signing and device trust settings. ```bash shaft-cli setup plan \ --profile MOBILE_IOS \ --mode MANAGED \ --output /absolute/path/mobile-ios-plan.json shaft-cli setup install \ --plan /absolute/path/mobile-ios-plan.json \ --approve sha256: shaft-cli setup verify --profile MOBILE_IOS --mode MANAGED ``` The default iOS plan binds the existing Simulator selection and Appium port `4723`. Java callers can bind one exact available Simulator UDID and a different loopback port; reuse the same reviewed plan for installation. Use `MOBILE_WINDOWS` on Windows to install SHAFT's pinned Appium, Inspector, and Windows driver bundle. Enable Developer Mode and install WinAppDriver 1.2.1 separately before planning. SHAFT verifies that prerequisite but never runs the privileged WinAppDriver MSI or changes Developer Mode. ```powershell shaft-cli setup plan ` --profile MOBILE_WINDOWS ` --mode MANAGED ` --output C:\plans\mobile-windows-plan.json shaft-cli setup install ` --plan C:\plans\mobile-windows-plan.json ` --approve sha256: shaft-cli setup verify --profile MOBILE_WINDOWS --mode MANAGED ``` Both profiles keep their npm projects in separate versioned SHAFT roots. A lock checksum, direct package checksum, selected host metadata, execution policy, and destination roots are part of the approved plan. External mode remains diagnostic-only and creates no setup roots. ### Start Appium without owning the host device `start` launches only the SHAFT-owned Appium process from a verified `MOBILE_IOS` or `MOBILE_WINDOWS` receipt. It does not boot or shut down a Simulator. It does not launch WinAppDriver and does not stop an existing WinAppDriver process. Pre-boot the Simulator yourself. Keep Developer Mode and WinAppDriver 1.2.1 already installed on Windows. Those start rules are always in force. The engine also has optional live host tests that skip unless you set `SHAFT_SETUP_IOS_ACCEPTANCE=true` and `SHAFT_SETUP_IOS_UDID` to an existing booted Simulator, or `SHAFT_SETUP_WINDOWS_ACCEPTANCE=true` on a Windows host that already has WinAppDriver. The values `1` and `yes` do not enable those tests. The variables do not change `shaft-cli setup start`. ## Related - [Install SHAFT](/docs/start/installation) - [shaft-cli command line](/docs/agentic/cli) - [Programmatic properties configuration](/docs/reference/properties/Programmatic_Config) - [Reporting](/docs/features/reporting) - [Self-healing locators](/docs/reference/actions/GUI/Locators_And_Self_Healing) - [BrowserStack](/docs/integrations/browserstack) - [Local infrastructure overview](/docs/start/local-infrastructure) --- # Install managed services and tools Canonical HTML: https://shafthq.github.io/docs/start/local-infrastructure/services Markdown: https://shafthq.github.io/md/start/local-infrastructure/services.md # Install managed services and tools Use these steps for the web, Grid, healing, reporting, and cloud-tunnel services SHAFT can own locally. Every install follows the shared [review and approve](/docs/start/local-infrastructure#review-and-approve-an-installation) flow; policy options and exit codes are in the [infrastructure reference](/docs/start/local-infrastructure/reference). ## Install managed Selenium Grid :::warning[Not in a published release yet] The `SELENIUM_GRID` provider is available on `SHAFT_ENGINE` `main` after [engine PR #5042](https://github.com/ShaftHQ/SHAFT_ENGINE/pull/5042). Use these commands with a source build until a containing SHAFT release is published. ::: Use this profile when you want SHAFT to own a local Selenium Grid compose project. Docker 26.1.4+ is a host prerequisite. SHAFT diagnoses Docker; it does not install the engine or daemon. The release plan binds image tag `4.47.0-20260808` for `selenium/hub`, `selenium/node-chrome`, `selenium/node-edge`, and `selenium/node-firefox`. The compose project name is `shaft-selenium-grid`. CLI planning uses the release defaults: hub port `4444`, one Chrome replica, zero Edge replicas, and zero Firefox replicas. Bind a different port or replica count through Java selection components such as `port_4445` and `chrome_2`. Those tokens are not CLI flags. SHAFT never sets `container_name` and never adopts an unknown compose project or port. ```bash shaft-cli setup status --profile SELENIUM_GRID --mode MANAGED shaft-cli setup plan \ --profile SELENIUM_GRID \ --mode MANAGED \ --output /absolute/path/selenium-grid-plan.json shaft-cli setup install \ --plan /absolute/path/selenium-grid-plan.json \ --approve sha256: shaft-cli setup verify --profile SELENIUM_GRID --mode MANAGED shaft-cli setup start \ --plan /absolute/path/selenium-grid-plan.json \ --approve sha256: shaft-cli setup logs --profile SELENIUM_GRID shaft-cli setup stop \ --plan /absolute/path/selenium-grid-plan.json \ --approve sha256: ``` `start` brings up only the owned `shaft-selenium-grid` project and accepts the hub when `/wd/hub/status` is healthy. `stop` runs compose down for that project only. ## Install managed Healenium :::warning[Not in a published release yet] The `HEALENIUM` provider is available on `SHAFT_ENGINE` `main` after [engine PR #5044](https://github.com/ShaftHQ/SHAFT_ENGINE/pull/5044). Use these commands with a source build until a containing SHAFT release is published. ::: Use this profile when tests need a local Healenium backend. Docker 26.1.4+ is a host prerequisite. SHAFT diagnoses Docker; it does not install the engine or daemon. The release plan pins `healenium/hlm-backend:3.4.6`, `healenium/hlm-selector-imitator:1.4`, and `postgres:15.5-alpine` in compose project `shaft-healenium`. CLI defaults are backend port `7878` and imitator port `8000`. Bind different ports through Java selection components `backend_7879` and `imitate_8001`; the two ports must differ. Those tokens are not CLI flags. ```bash shaft-cli setup status --profile HEALENIUM --mode MANAGED shaft-cli setup plan \ --profile HEALENIUM \ --mode MANAGED \ --output /absolute/path/healenium-plan.json shaft-cli setup install \ --plan /absolute/path/healenium-plan.json \ --approve sha256: shaft-cli setup verify --profile HEALENIUM --mode MANAGED shaft-cli setup start \ --plan /absolute/path/healenium-plan.json \ --approve sha256: shaft-cli setup logs --profile HEALENIUM shaft-cli setup stop \ --plan /absolute/path/healenium-plan.json \ --approve sha256: ``` Point `SHAFT.Properties.healenium` at `localhost` and the reviewed backend port. See [self-healing locators](/docs/reference/actions/GUI/Locators_And_Self_Healing#self-healing-locators) for the opt-in engine property. ## Install managed ReportPortal :::warning[Not in a published release yet] The `REPORT_PORTAL` provider is available on `SHAFT_ENGINE` `main` after [engine PR #5046](https://github.com/ShaftHQ/SHAFT_ENGINE/pull/5046). Use these commands with a source build until a containing SHAFT release is published. ::: Use this profile only for local development. SHAFT owns an official-core ReportPortal compose project named `shaft-reportportal` and does not start the analyzer. Docker 26.1.4+ is a host prerequisite. SHAFT diagnoses Docker; it does not install the engine or daemon. The CLI default UI port is `8080`. Bind a different UI port through Java selection component `ui_8081`. That token is not a CLI flag. ```bash shaft-cli setup status --profile REPORT_PORTAL --mode MANAGED shaft-cli setup plan \ --profile REPORT_PORTAL \ --mode MANAGED \ --output /absolute/path/reportportal-plan.json shaft-cli setup install \ --plan /absolute/path/reportportal-plan.json \ --approve sha256: shaft-cli setup verify --profile REPORT_PORTAL --mode MANAGED shaft-cli setup start \ --plan /absolute/path/reportportal-plan.json \ --approve sha256: shaft-cli setup logs --profile REPORT_PORTAL shaft-cli setup stop \ --plan /absolute/path/reportportal-plan.json \ --approve sha256: ``` The stack uses official ReportPortal local-dev defaults. Change those credentials before exposing the UI beyond loopback. ## Install managed BrowserStack Local :::warning[Not in a published release yet] The `BROWSERSTACK_LOCAL` provider is available on `SHAFT_ENGINE` `main` after [engine PR #5048](https://github.com/ShaftHQ/SHAFT_ENGINE/pull/5048) and [stop-until-dead follow-up #5049](https://github.com/ShaftHQ/SHAFT_ENGINE/pull/5049). Use these commands with a source build until a containing SHAFT release is published. ::: Use this profile when cloud sessions need a SHAFT-owned BrowserStack Local tunnel to a private network. The plan pins official BrowserStack Local v8.9 archives for Windows, Linux x64, and macOS. Linux ARM64 has no versioned v8.9 archive; use x64 or a later pin. ```bash shaft-cli setup status --profile BROWSERSTACK_LOCAL --mode MANAGED shaft-cli setup plan \ --profile BROWSERSTACK_LOCAL \ --mode MANAGED \ --output /absolute/path/browserstack-local-plan.json shaft-cli setup install \ --plan /absolute/path/browserstack-local-plan.json \ --approve sha256: shaft-cli setup verify --profile BROWSERSTACK_LOCAL --mode MANAGED ``` Set `BROWSERSTACK_ACCESS_KEY` in the environment before `start`. Do not put the key in the plan file or in a checked-in properties example. ```bash shaft-cli setup start \ --plan /absolute/path/browserstack-local-plan.json \ --approve sha256: shaft-cli setup logs --profile BROWSERSTACK_LOCAL shaft-cli setup stop \ --plan /absolute/path/browserstack-local-plan.json \ --approve sha256: ``` `stop` waits until the owned process is dead. A later `start` then cannot race the dying tunnel. See [BrowserStack sessions](/docs/integrations/browserstack) for direct cloud sessions and the optional SDK module. ## Diagnose agent tools :::warning[Not in a published release yet] The `AGENT_TOOLS` provider is available on `SHAFT_ENGINE` `main` after [engine PR #5052](https://github.com/ShaftHQ/SHAFT_ENGINE/pull/5052). Use these commands with a source build until a containing SHAFT release is published. ::: Use this profile to diagnose host agent prerequisites. `JAVA`, `MAVEN`, `PYTHON`, and `NODE` stay diagnose-only: Java 25+, Maven 3.9.0+, Python 3.10+, and Node 20+. SHAFT does not install those host tools. In `MANAGED` mode the `AGENT_CLI` action writes a pinned `agent-clients.json` that detects `gh`. It does not download vendor CLIs. ```bash shaft-cli setup status --profile AGENT_TOOLS --mode MANAGED shaft-cli setup plan \ --profile AGENT_TOOLS \ --mode MANAGED \ --output /absolute/path/agent-tools-plan.json shaft-cli setup install \ --plan /absolute/path/agent-tools-plan.json \ --approve sha256: shaft-cli setup verify --profile AGENT_TOOLS --mode MANAGED ``` `start` and `stop` are unsupported. Install a missing JDK, Maven, Python, or Node yourself, then rerun `status` or `verify`. ## Install managed Playwright browsers :::warning[Not in a published release yet] The `PLAYWRIGHT` provider is tracked by [engine PR #4922](https://github.com/ShaftHQ/SHAFT_ENGINE/pull/4922). Use these commands with a source build until a containing SHAFT release is published. ::: Use the managed profile when local Playwright tests need SHAFT-owned browser payloads. The plan couples Playwright Java 1.62.0 to Chromium revision 1234, Firefox revision 1538, WebKit revision 2336, FFmpeg revision 1011, and the shared Node 24.19.0 owner. Each official archive URL, size, and SHA-256 digest is part of the reviewed release manifest and plan. Run the shared command sequence. Stop after `plan`, inspect all five actions and the printed digest, then install that exact plan: SHAFT downloads each archive through the verified artifact store first. It then points Playwright's downloader at a temporary `127.0.0.1` mirror that serves only those verified files. Setup never runs `install-deps`, `sudo`, `apt`, `dnf`, `brew`, a GUI installer, or a package-manager elevation flow. Install required host libraries separately when a Linux browser diagnostic reports a missing shared library. The release accepts Windows x64 or ARM64, Ubuntu 24.04 x64, and macOS 15 on x64 or ARM64. It rejects a different Linux distribution, Ubuntu release, macOS major version, or unsupported architecture instead of substituting a nearby browser build. Pass `--offline` to both `plan` and `install` to prohibit network access. A warm verified artifact cache or a compatible published receipt can be reused. A cold, incomplete, corrupt, wrong-version, or linked state fails without an outbound fallback or partial final receipt. Configure the engine to consume the reviewed installation: ```properties title="src/main/resources/properties/custom.properties" infrastructure.mode=MANAGED infrastructure.profile=PLAYWRIGHT infrastructure.cacheDirectory=/absolute/path/shaft-playwright-cache playwright.connectionMode=local playwright.channel= playwright.browserName=chromium headlessExecution=true ``` Use an absolute cache directory. For a local session with no channel override, SHAFT validates the compatible receipt and supplies the owned browser root only to the Playwright child process. It does not install a missing browser during driver creation and does not change the parent process environment. An explicit remote connection, Chromium channel, or existing `PLAYWRIGHT_BROWSERS_PATH` wins over managed lookup. This keeps caller-owned browser paths and remote endpoints unchanged. ## Install managed Lighthouse :::warning[Not released] This workflow depends on [SHAFT Engine issue #4884](https://github.com/ShaftHQ/SHAFT_ENGINE/issues/4884). It is not yet available on `SHAFT_ENGINE` `main` or in a published SHAFT release. Keep using the current Lighthouse flow until a release that contains the managed `LIGHTHOUSE` provider is available. Install the `LIGHTHOUSE` profile before a test calls `generateLightHouseReport()`. SHAFT manages its own Node 24.19.0 and Lighthouse 13.4.1 under the configured tool root. It does not use a global Node or npm installation. Stop after `plan` and review the JSON plus its printed digest before running `install`. The plan binds the exact Node artifact, Lighthouse package archive, bundled dependency lock, destinations, and policy. Report generation only uses an already verified installation; it never installs a missing toolchain. For a cold offline install, pass `--offline` to both `plan` and `install`. The SHAFT cache must already contain the verified Node and Lighthouse artifacts and the complete transitive npm cache required by the bundled lock. An already verified managed installation can be reused without those cached downloads. A missing or corrupt entry needed by a cold install fails without network access or a partially published Lighthouse installation. Use the default SHAFT roots for this preview. The CLI can bind separate custom cache and data roots, but the Browser Actions runtime currently exposes only `infrastructure.cacheDirectory` and cannot reproduce every such layout. ::: ## Related - [Install SHAFT](/docs/start/installation) - [shaft-cli command line](/docs/agentic/cli) - [Programmatic properties configuration](/docs/reference/properties/Programmatic_Config) - [Reporting](/docs/features/reporting) - [Self-healing locators](/docs/reference/actions/GUI/Locators_And_Self_Healing) - [BrowserStack](/docs/integrations/browserstack) - [Local infrastructure overview](/docs/start/local-infrastructure) --- # Install SHAFT Canonical HTML: https://shafthq.github.io/docs/start/installation Markdown: https://shafthq.github.io/md/start/installation.md # Install SHAFT Use this page to get a SHAFT project running on your machine: check the requirements, generate a project, run the first test, and confirm the report. Adding SHAFT to an existing Maven project or upgrading an older suite is covered further down. ## Requirements ## Create a new project The **SHAFT Project Generator is the only recommended way to create a new project**. Choose the test runner and testing surfaces you need, then download the ready-to-run project. :::tip Generator not visible? Open the [SHAFT Project Generator in a new tab](/project-generator). ::: The generated project includes the selected test runner, configuration, sample tests, reporting setup, optional CI files, and shared IntelliJ IDEA run configuration templates that use `@argFiles` for direct IDE runs. Run the generated project from its root: Expected first-run outcome: - Maven downloads the selected SHAFT modules and test runner dependencies. - The generated sample test runs with its default local configuration. - SHAFT creates or refreshes runtime configuration under `src/main/resources/properties`. - Evidence is written to . If that works, before moving on. It is the shortest way to find the framework again when your team starts expanding the suite. Common first-run blockers: | Symptom | Check | |---|---| | Java version error | Install the Java version listed in Requirements above. | | Maven cannot resolve SHAFT | Check proxy settings, repository access, and Maven Central connectivity. | | Browser session does not start | Confirm the selected browser is installed or use the generated headless defaults. | | Report does not open automatically | Open the generated report from the project target output or rerun from an interactive desktop session. | | Build passes but `Total: 0` tests ran | Your pom is missing the TestNG Surefire provider — see [Adding SHAFT to an existing Maven project](#adding-shaft-to-an-existing-maven-project) below. Generated projects already include it. | ## Adding SHAFT to an existing Maven project Generated projects come pre-wired, but if you add the `shaft-engine` dependency to a hand-written pom, one extra block is required: SHAFT ships the JUnit Platform, so a bare pom makes Maven Surefire auto-detect the JUnit provider and **silently run zero TestNG tests** (the build still "passes"). SHAFT prints a loud warning when this happens. Configure Surefire with the TestNG provider explicitly: ```xml org.apache.maven.plugins maven-surefire-plugin 3.5.5 org.apache.maven.surefire surefire-testng 3.5.5 ``` ## Upgrade an existing project Do not recreate or manually reconfigure an existing automation project. Use the [automated upgrade tool](/docs/start/upgrade) for: - Selenium projects - Appium projects - REST Assured projects - Cucumber projects - Projects using an older SHAFT version The upgrader detects the project type, applies the modular SHAFT dependencies, validates the result with Maven, and rolls back its changes if compilation fails. ## Related - [Quick start](/docs/start/quick-start) - [Set up local infrastructure](/docs/start/local-infrastructure) - [Upgrade an existing project](/docs/start/upgrade) - [Features and modules](/docs/features/modules) --- # Local infrastructure reference Canonical HTML: https://shafthq.github.io/docs/start/local-infrastructure/reference Markdown: https://shafthq.github.io/md/start/local-infrastructure/reference.md # Local infrastructure reference Look up the policy options, Java API, and CLI results shared by every setup profile. For the step-by-step flow, start at [Set up local infrastructure](/docs/start/local-infrastructure). ## Keep policy options identical Plan and install accept the same execution policy: | Option | Default | Effect | |---|---:|---| | `--offline` | `false` | Require verified cached artifacts and disable network access. | | `--auto-start` | `false` | Bind a startup request for providers that own a service. | | `--prefer-system-tools=true\|false` | `true` | Bind whether a provider may prefer a compatible host tool. | | `--reuse-owned-processes=true\|false` | `true` | Bind whether a provider may reuse compatible SHAFT-owned processes. | | `--startup-timeout ` | `PT2M` | Bind a positive ISO-8601 startup timeout for providers with lifecycle support. | | `--shutdown-timeout ` | `PT30S` | Bind a positive ISO-8601 shutdown timeout for providers with lifecycle support. | Pass any non-default option to both commands. You may also pass an absolute `--cache-root` and `--data-root` pair to both commands; SHAFT rejects a single root or a relative path. The current `REPORTING` provider enforces `--offline`. It has no owned service, so auto-start, process reuse, and lifecycle timeouts are policy-bound for provider parity but do not change a reporting install. Reporting installs SHAFT-owned portable tools rather than adopting system Node or Allure. The unreleased `LIGHTHOUSE` provider follows the same lifecycle shape. :::warning Custom roots become mutable SHAFT-owned storage. Use dedicated, user-scoped directories. Do not point them at a repository, shared or system directory, or a path reached through a symlink alias. ::: ## Use the Java API Configure the same policy through `SHAFT.Properties.infrastructure`, then plan and explicitly approve the immutable result: ```java title="ReportingInfrastructure.java" public final class ReportingInfrastructure { public static void main(String[] args) throws Exception { SHAFT.Properties.infrastructure.set() .profile(SetupProfile.REPORTING) .mode(SetupMode.MANAGED) .offline(false) .autoStart(false); SetupPlan plan = SHAFT.Infrastructure.plan(); SetupPlanStore.write(Path.of(args[0]).toAbsolutePath(), plan); // Stop this phase and review the written JSON plus this digest. System.out.println(plan.digest()); } } ``` Run the mutation in a separate phase. Supply the digest you reviewed instead of deriving it from a newly generated plan: ```java title="InstallReviewedReportingPlan.java" public final class InstallReviewedReportingPlan { public static void main(String[] args) throws Exception { SetupPlan plan = SetupPlanStore.read(Path.of(args[0]).toAbsolutePath()); String reviewedDigest = System.getenv("SHAFT_APPROVED_SETUP_DIGEST"); // Recreate every policy value used by the planning phase. SHAFT.Properties.infrastructure.set() .profile(SetupProfile.REPORTING) .mode(SetupMode.MANAGED) .offline(false) .autoStart(false); if (!plan.executionPolicyDigest().equals( SHAFT.Infrastructure.options().policyDigest())) { throw new IllegalStateException( "Current setup policy differs from the reviewed plan"); } SetupApproval approval = new SetupApproval( reviewedDigest, Instant.now(), Set.of()); SetupReceipt receipt = SHAFT.Infrastructure.install(plan, approval); System.out.println(receipt.planDigest()); } } ``` Use `SHAFT.Infrastructure.catalog()`, `doctor()`, `status()`, and `verify()` for read-only inspection. `install(...)` and `start(...)` require both the exact plan and its approval; there is no unapproved mutation overload. Use an absolute plan path in both Java phases. Reproduce every property and path from the planning phase before installation; schema 3 rejects even a single policy or destination difference. The configuration defaults are: ```properties infrastructure.mode=EXTERNAL infrastructure.profile=REPORTING infrastructure.cacheDirectory= infrastructure.offline=false infrastructure.autoStart=false infrastructure.preferSystemTools=true infrastructure.reuseOwnedProcesses=true infrastructure.startupTimeout=PT2M infrastructure.shutdownTimeout=PT30S ``` Set `infrastructure.cacheDirectory` only to an absolute path. An empty value uses the platform-specific SHAFT cache and application-data locations. ## Interpret CLI failures | Exit code | Meaning | |---:|---| | `0` | Ready or successful. | | `2` | Invalid input, policy, or approval. | | `3` | Missing or degraded readiness. | | `4` | No provider supports the requested operation. | | `5` | Execution or integrity failure. | An install is atomic per action, not across the entire plan. If a later action fails, an earlier verified action can remain installed while the final profile receipt is absent. Fix the failure and retry the same approved plan; SHAFT re-verifies compatible completed state before continuing. If setup reports that both an artifact destination and its `.quarantine` recovery file exist, preserve both files and stop. Verify each against the reviewed action checksum, then deliberately retain the valid copy before retrying. Never blindly delete the quarantine: it may be the only known-good pre-replacement artifact. `start` and `stop` return unsupported for profiles without an owned service. SHAFT does not adopt or stop an unknown process. Use `shaft-cli setup logs --profile REPORTING` to read an existing provider log; it returns `3` when no owned log exists. ## Related - [Install SHAFT](/docs/start/installation) - [shaft-cli command line](/docs/agentic/cli) - [Programmatic properties configuration](/docs/reference/properties/Programmatic_Config) - [Reporting](/docs/features/reporting) - [Self-healing locators](/docs/reference/actions/GUI/Locators_And_Self_Healing) - [BrowserStack](/docs/integrations/browserstack) - [Local infrastructure overview](/docs/start/local-infrastructure) --- # Modular SHAFT upgrade reference Canonical HTML: https://shafthq.github.io/docs/start/upgrade/reference Markdown: https://shafthq.github.io/md/start/upgrade/reference.md # Modular SHAFT upgrade reference These sections describe the exact command surface and Maven result produced by the upgrader. They remain useful for review, unusual project layouts, and troubleshooting; they are not an alternative migration path. To perform an upgrade, follow [Run the upgrade](/docs/start/upgrade/run). ## Command reference | Option | Purpose | | --- | --- | | `--project PATH` | Project root. Defaults to the current directory. | | `--shaft-version VERSION` | Use a controlled version instead of Maven Central's latest release. Useful for local/offline repositories. | | `--compile-command COMMAND` | Override the default Maven `dependency:go-offline test-compile -DskipTests -Dgpg.skip` command. | | `--compile-timeout SECONDS` | Set the timeout for each compile invocation. Default: 900 seconds. | | `--upgrade-type TYPE` | Select `basic`, `session`, or `full`. `basic` is the default; `session` and `full` require Selenium/Appium. | | `--agent-plan` | Print the three upgrade choices as JSON for AI agents, then exit without writing or compiling. | | `--skip-baseline-compile` | Skip the unchanged-project compile. This weakens failure attribution and is not recommended. | | `--dry-run` | Print POM and optional source diffs without writing or compiling. | | `--yes` | Do not prompt before applying changes. | | `--report PATH` | Write an optional JSON result report. | | `--prompt-for-openai-key` | Securely prompt for an optional API key. | | `--openai-key-env NAME` | Read the API key from another environment variable. Default: `OPENAI_API_KEY`. | | `--openai-model MODEL` | Select another Responses API model. | | `--no-ai` | Disable AI repair even if an API key is present. | ## Migration outcome A completed migration has: - `shaft-bom` managing one SHAFT version. - `shaft-engine` replacing the old uppercase artifact. - Only the optional modules used by the project. - No mixture of `SHAFT_ENGINE` and modular JARs in the dependency tree. - Passing compile/tests with a populated Allure result set when SHAFT tests run. ## Coordinates: before and after The shared version above is updated by the release workflow. Verify availability through the [canonical Maven Central artifact](https://central.sonatype.com/artifact/io.github.shafthq/shaft-engine) before changing a production build. ## Module map Importing `shaft-bom` aligns versions; it does not add runtime code. ```mermaid flowchart LR Project["Your test project"] --> BOM["shaft-bom version alignment"] Project --> Engine["shaft-engine required"] Project -.->|"provider-neutral Pilot contracts"| PilotCore["shaft-pilot-core"] Project -.->|"recording model + privacy"| Capture["shaft-capture"] Project -.->|"offline failure diagnosis"| Doctor["shaft-doctor"] Project -.->|"optional direct AI providers"| AI["shaft-ai"] Project -.->|"deterministic web element recovery"| Heal["shaft-heal"] Project -.->|"only for BrowserStack SDK runtime"| BrowserStack["shaft-browserstack"] Project -.->|"only for local desktop recording"| Video["shaft-video"] Project -.->|"only for image-engine operations"| Visual["shaft-visual"] Project -.->|"only for SikuliX image matching"| SikuliX["shaft-sikulix"] BrowserStack --> Engine Video --> Engine Visual --> Engine SikuliX --> Engine PilotCore --> Engine Capture --> PilotCore Doctor --> PilotCore AI --> PilotCore Heal --> Engine Heal --> PilotCore Engine --> Core["Web + Appium + API + DB + CLI reporting + screenshots + accessibility"] BrowserStack --> SDK["browserstack.yml orchestration SDK listeners/interception"] Video --> Desktop["local non-headless desktop video Automation Remarks + JAVE/FFmpeg"] Visual --> CV["reference-image assertions OpenCV + Eyes + Shutterbug"] SikuliX --> DesktopImage["SikuliX desktop image matching"] Heal --> Recovery["action-scoped recovery history + structured reports"] ``` ## Select dependencies by functionality | Functionality | Required artifact | Do not add an optional module for | |------------------------------------------------------------------------------|----------------------|----------------------------------------------------------------------------------| | WebDriver browser actions, element actions, locators, screenshots, reporting | `shaft-engine` | Normal local, Docker, Selenium Grid, LambdaTest, or direct BrowserStack sessions | | Appium native/mobile web/Flutter actions and Appium screen recording | `shaft-engine` | Driver-native Android/iOS recording | | REST Assured API, database, CLI, test data, accessibility, Cucumber steps | `shaft-engine` | Any of these capabilities by themselves | | Provider-neutral Pilot requests, approval, redaction, and deterministic fallback | `shaft-pilot-core` | Direct provider HTTP calls | | Versioned browser recording, privacy classification, and capture JSON | `shaft-capture` | Ordinary engine screenshots or desktop video | | Portable evidence bundles and deterministic offline failure diagnosis | `shaft-doctor` | Allure report rendering or direct AI provider calls | | Direct OpenAI, Anthropic, Gemini, or Ollama provider calls | `shaft-ai` | Deterministic Capture creation, validation, migration, or replay data | | Deterministic explainable web element recovery | `shaft-heal` | Normal element actions when locator recovery is not explicitly enabled | | BrowserStack SDK interception and `browserstack.yml` orchestration | `shaft-browserstack` | Direct BrowserStack WebDriver/Appium sessions built by SHAFT | | Local, non-headless desktop recording managed by SHAFT | `shaft-video` | Remote-provider video or Appium `startRecordingScreen()` | | Reference-image assertions and image-based touch lookup | `shaft-visual` | Ordinary screenshots, screenshot highlighting, GIFs, or folder comparison | | SikuliX image-based desktop automation | `shaft-sikulix` | Appium Windows desktop sessions through `shaft-engine` | Add optional modules beside `shaft-engine`; their versions come from the BOM: ```xml io.github.shafthq shaft-visual ``` Use the same shape for `shaft-pilot-core`, `shaft-capture`, `shaft-doctor`, `shaft-ai`, `shaft-heal`, `shaft-browserstack`, `shaft-video`, or `shaft-sikulix`, but add only the artifacts selected by the tables below. ## SHAFT Heal dependency boundary Add `shaft-heal` only when WebDriver locator recovery is required, then opt in with `healing.strategy=shaft-heal`. The module is disabled by default and does not alter native mobile execution. Use `healing.strategy=composite` only when both SHAFT Heal and Healenium are intentionally required; legacy `heal-enabled=true` continues to select Healenium when no explicit SHAFT Heal strategy is configured. Visual evidence remains local and requires both `shaft-visual` and `healing.visual.enabled=true`. AI candidate reranking requires `healing.ai.enabled=true` plus the normal Pilot approval/provider controls. Neither optional score can bypass deterministic confidence and ambiguity checks. See the [SHAFT Heal guide](/docs/agentic/heal). ## Capture dependency boundary Add `shaft-capture` when a project creates, validates, migrates, reviews, or persists SHAFT Capture recording JSON. It transitively uses `shaft-pilot-core` for provider-neutral security contracts but does not resolve `shaft-ai`. Recording and privacy enforcement remain deterministic with `pilot.ai.enabled=false`. See the [SHAFT Capture format guide](/docs/agentic/capture). ## Doctor dependency boundary Add `shaft-doctor` when a project needs allowlisted local evidence collection, portable redacted bundles, deterministic root-cause classification, or JSON/Markdown diagnosis reports. It uses `shaft-pilot-core` security helpers but does not resolve `shaft-ai` or make network calls. See the [SHAFT Doctor guide](/docs/agentic/doctor). ## Visual dependency boundary `shaft-visual` is required when execution reaches the optional `VisualProcessingProvider`. Merely taking or attaching a screenshot does not reach that provider. ### Methods that require `shaft-visual` matchesReferenceImage() Defaults to EXACT_SHUTTERBUG ; all reference-image engines are implemented by the provider. matchesReferenceImage(VisualValidationEngine) EXACT_SHUTTERBUG , EXACT_OPENCV , EXACT_EYES , STRICT_EYES , CONTENT_EYES , and LAYOUT_EYES all delegate to shaft-visual . doesNotMatchReferenceImage() and its engine overload The default is EXACT_OPENCV ; every overload delegates to the provider. Cucumber reference-image assertion steps The built-in OpenCV, Shutterbug, and Eyes steps invoke the same validation path. TouchActions.tap(String) Locates the reference image inside the current screenshot using OpenCV. TouchActions.waitUntilElementIsVisible(String) Uses OpenCV to find the supplied image. TouchActions.swipeElementIntoView(String, ...) and the scrollable-container overload Uses image matching after each swipe. ImageProcessingActions.findImageWithinCurrentPage(...) Direct provider operation. ImageProcessingActions.compareAgainstBaseline(...) Direct provider operation for every visual engine. ImageProcessingActions.loadOpenCV() Explicitly loads the optional provider. The TestNG and JUnit web samples contain this visual assertion: ```java @Test public void navigateToDuckDuckGoAndAssertLogoIsDisplayedCorrectly() { driver.browser().navigateToURL(targetUrl) .and().element().assertThat(logo).matchesReferenceImage(); } ``` Their POMs therefore include `shaft-visual`. Removing the visual test allows those projects to return to `shaft-engine` only. ### Functionality that remains in `shaft-engine` Selenium/Appium screenshot capture and Allure attachments Uses WebDriver/Appium screenshot APIs and SHAFT reporting. ImageProcessingActions.highlightElementInScreenshot(...) Uses JDK BufferedImage and Graphics2D . ImageProcessingActions.compareImageFolders(...) Uses JDK ImageIO and image data buffers. formatElementLocatorToImagePath(...) , getReferenceImage(...) , getShutterbugDifferencesImage(...) Performs naming and file access only. Animated GIF creation Does not invoke the visual provider. Healenium integration Independent of OpenCV. Normal locator-based tap(By) , waitUntilElementIsVisible(By) , and swipeElementIntoView(By, ...) Uses Selenium/Appium locators, not reference images. See the detailed [visual module guide](/docs/integrations/visual). ## BrowserStack dependency boundary `shaft-browserstack` does not add a new SHAFT facade method. It adds the BrowserStack Java SDK runtime. The ordinary BrowserStack driver path remains in `shaft-engine`. ### Works with `shaft-engine` only The standard web sample remains unchanged when a direct BrowserStack session is selected: ```java @BeforeMethod public void beforeMethod() { driver = new SHAFT.GUI.WebDriver(); } @Test public void searchForQueryAndAssert() { driver.browser().navigateToURL(targetUrl) .and().element().type(searchBox, testData.get("searchQuery") + Keys.ENTER) .and().element().click(firstSearchResult) .and().assertThat().title().contains(testData.get("expectedResultTitle")) .and().element().assertThat(By.tagName("body")).text() .contains(testData.get("expectedResultText")); } ``` With `executionAddress=browserstack`, `shaft-engine` performs all of the following without `shaft-browserstack`: - `new SHAFT.GUI.WebDriver()` and `DriverFactory` BrowserStack routing. - Desktop web, mobile web, and native Appium session creation. - W3C `bstack:options` capability construction. - BrowserStack app upload when `browserStack.appRelativeFilePath` is used. - Credentials, device/browser/OS selection, local flag, debug/network logs, Selenium/Appium version, geolocation, and custom capability handling. - Generation or copying of `browserstack.yml` through `BrowserStackSdkHelper.generateBrowserStackYml()`. The generated YAML is harmless but has no SDK orchestration effect when the SDK runtime is absent. ### Requires `shaft-browserstack` Add the module when BrowserStack's SDK must consume `browserstack.yml` and intercept/orchestrate the test runtime: ```xml io.github.shafthq shaft-browserstack ``` These SHAFT properties configure SDK-only behavior: ```java SHAFT.Properties.browserStack.set() .platformsList(""" [ {"os":"Windows","osVersion":"11","browserName":"Chrome"}, {"os":"OS X","osVersion":"Sonoma","browserName":"Safari"} ] """) .parallelsPerPlatform(2) .browserstackAutomation(true); ``` browserStack.platformsList multi-platform expansion The value is written to YAML, but no SDK consumes it. browserStack.parallelsPerPlatform SDK parallel orchestration Direct SHAFT session creation still follows the test runner's own parallelism. browserStack.browserstackAutomation interception switch No BrowserStack SDK is present to intercept WebDriver creation. browserStack.customBrowserStackYmlPath as SDK configuration SHAFT can copy the file, but only the SDK interprets its orchestration settings. SDK listeners, automatic capability override, and SDK reporting/orchestration Unavailable; the direct SHAFT BrowserStack session still works. See the [BrowserStack module guide](/docs/integrations/browserstack) and [BrowserStack's SDK architecture](https://www.browserstack.com/docs/automate/selenium/how-sdk-works). ## Video dependency boundary `shaft-video` is required only when `videoParamsRecordVideo=true` starts local, non-headless desktop recording. `RecordManager.startVideoRecording()` then discovers the desktop provider and fails with an actionable message when it is absent. Appium native recording through `RecordManager.startVideoRecording(WebDriver)` and Android/iOS `startRecordingScreen()` remains in `shaft-engine`. Remote cloud video configured through provider capabilities also does not use `shaft-video`. See the [video module guide](/docs/integrations/desktop-and-video#video-recording). ## Legacy relocation and support window The old `io.github.shafthq:SHAFT_ENGINE` coordinate is a relocation POM pointing to `io.github.shafthq:shaft-engine` for the modular release line. It contains no classes and selects only `shaft-engine`. Maven cannot infer whether a project needs BrowserStack SDK orchestration, desktop recording, or image-engine operations, so relocation never adds optional modules. Use relocation only as a temporary compatibility bridge. Do not declare the legacy coordinate together with modular artifacts. ## Verified cold-cache download measurements The table records compressed classpath JAR bytes from isolated Maven repositories. The monolithic graph is the committed `10.2.20260605` baseline at commit `570a836`; the modular graph is `shaft-engine` only. MiB uses 1,048,576 bytes. | Supported platform | Before: `SHAFT_ENGINE` | After: `shaft-engine` only | Saved | | --- | ---: | ---: | ---: | | Linux x64 | 352,001,986 (335.70 MiB) | 169,941,464 (162.07 MiB) | 182,060,522 (173.63 MiB / 51.7%) | | Linux ARM64 | 348,005,291 (331.88 MiB) | 169,941,464 (162.07 MiB) | 178,063,827 (169.81 MiB / 51.2%) | | Windows x64 | 350,617,708 (334.38 MiB) | 169,941,464 (162.07 MiB) | 180,676,244 (172.31 MiB / 51.5%) | | macOS x64 | 344,700,722 (328.73 MiB) | 169,941,464 (162.07 MiB) | 174,759,258 (166.66 MiB / 50.7%) | | macOS ARM64 | 341,487,898 (325.67 MiB) | 169,941,464 (162.07 MiB) | 171,546,434 (163.60 MiB / 50.2%) | Reproduce the current modular graph after a reactor build: ```bash mvn clean install -DskipTests -Dgpg.skip python3 scripts/ci/measure_consumer_dependencies.py \ --fixture api \ --output target/modular-measurement ``` The platform totals substitute the exact JAVE native JAR selected by the old POM: Linux x64 28,201,169 bytes, Linux ARM64 24,204,474 bytes, Windows x64 26,816,891 bytes, macOS x64 20,899,905 bytes, and macOS ARM64 17,687,081 bytes. ## Related - [Installation](/docs/start/installation) - [Quick Start](/docs/start/quick-start) - [Modules](/docs/features/modules) - [Upgrade overview](/docs/start/upgrade) --- # Preview managed local AI and OCR setup Canonical HTML: https://shafthq.github.io/docs/start/local-infrastructure/previews Markdown: https://shafthq.github.io/md/start/local-infrastructure/previews.md # Preview managed local AI and OCR setup These workflows are previews: each section states whether it is available on `SHAFT_ENGINE` `main` or in a published release. Every install follows the shared [review and approve](/docs/start/local-infrastructure#review-and-approve-an-installation) flow; policy options and exit codes are in the [infrastructure reference](/docs/start/local-infrastructure/reference). ## Preview managed local AI :::warning[Not in a published release yet] The `LOCAL_AI` managed provider is available on `SHAFT_ENGINE` `main` after [engine PR #4921](https://github.com/ShaftHQ/SHAFT_ENGINE/pull/4921). The delivered merge commit is [`a585b41980f78315aa353003ce5b5891f350a6a1`](https://github.com/ShaftHQ/SHAFT_ENGINE/commit/a585b41980f78315aa353003ce5b5891f350a6a1). Use this workflow with a source build until a containing SHAFT release is published. ::: Use this profile when you want SHAFT to own a pinned `llama.cpp` runtime and a reviewed GGUF model. You do not need a model manager, container, service, endpoint, credential, administrator install, or machine-wide daemon. SHAFT starts an authenticated loopback child process only when an approved AI request needs it. SHAFT continuously measures aggregate resident memory for the managed process tree. If the tree exceeds 4 GiB, or its live memory inventory cannot be read safely, SHAFT retires the session and returns the request's deterministic fallback. Start with explicit provisioning. Enable managed local processing, select the manual compact candidate, and prevent an inference request from downloading missing artifacts before you review the setup plan: ```properties title="src/main/resources/properties/custom.properties" managedLocalAi.enabled=true managedLocalAi.transparentProvisioning=false managedLocalAi.model=qwen3-0.6b-q8_0 ``` Run the shared review flow. Stop after `plan`, inspect both actions and the printed digest, then install that exact plan: The plan binds the exact runtime and model URL, version or revision, SHA-256, license, and artifact size. Installation reports phase, completed bytes, total bytes, and integer percentage on stderr, so `--json` output on stdout stays machine-readable. An interrupted, failed, or cancelled operation returns no receipt; fix the reported condition and retry the same still-current approved plan. Leave `--cache-root` and `--data-root` unset to use the effective managed-local cache and the normal SHAFT data root. If you supply roots, pass both as absolute paths on plan and install, and make `--cache-root` exactly equal to `managedLocalAi.cacheDirectory`. SHAFT rejects a different cache instead of provisioning files that inference will not use. ### Inspect readiness and privacy-safe diagnostics Use `setup status` or `setup verify` for the two managed targets. Those commands print `target readiness version detail`. Version is empty unless READY. They report readiness/version/action, not the reviewed pin table. Use `doctor local-ai-status` or MCP `doctor_managed_local_ai_status` for enablement, eligibility, and the DISABLED snapshot. They do not list the reviewed pin table. The MCP setup equivalents are `setup_status`, `setup_verify`, `setup_plan`, and `setup_install`. IntelliJ uses those generic MCP setup tools instead of a second lifecycle implementation. The pin table on this page is the documented inventory. Setup commands do not print revision, license, provenance, size, floors, update, cleanup, or fallback. Diagnostics exclude absolute cache paths, filenames, checksums, executable names, and exact host RAM, CPU, and free-disk values. :::warning Managed-local enablement grants local-processing consent only. It does not grant remote or on-premises processing consent. The compact candidate failed the current recommendation-quality gates, so use it only for manual evaluation and keep deterministic SHAFT results authoritative. `managedLocalAi.enabled` defaults to `false`. Do not flip that default to enable managed-local automatically. ::: ### Inventory, defaults, and troubleshooting The pin table on this page is the documented inventory. `setup status` and `setup verify` print `target readiness version detail` even when the cache is missing or the feature is still disabled. Version is empty unless READY. Commands report readiness/version/action, not those pin-table fields: | Field | Compact preview pin | |---|---| | Revision | runtime `b10400` / model `23749fefcc72300e3a2ad315e1317431b06b590a` | | License | `MIT` / `Apache-2.0` | | Provenance | `github.com/ggml-org/llama.cpp` and `huggingface.co/Qwen/Qwen3-0.6B-GGUF` | | Size | platform runtime archive plus `639446688` model bytes | | Storage | `SHAFT_USER_CACHE` — never an absolute cache path | | Resources | compact floor `2` GB RAM, `2` CPUs, `3` GB free disk | | Update | explicit reviewed plan; pin-bound; no silent float | | Cleanup | owner-manifest only; unknown siblings preserved | | Fallback | deterministic SHAFT result remains authoritative | Release automation cannot float or silently replace model or runtime bytes. A revoked or hash-mismatched artifact stays unpublished; the last good activation remains active. An interrupted upgrade returns no receipt and does not activate the new pair. Offline setup reuses a ready cache and never downloads or substitutes a fallback URL. Inspect readiness from each setup surface: | Surface | Command | |---|---| | CLI | `shaft-cli setup status --profile LOCAL_AI --mode MANAGED` and `shaft-cli setup verify --profile LOCAL_AI --mode MANAGED` | | MCP | `setup_status` and `setup_verify` | | IntelliJ | the generic MCP setup tools; there is no second lifecycle | | CI / headless | keep `managedLocalAi.enabled=false`; normal Maven never provisions or downloads; always pass `-Dallure.automaticallyOpen=false` | `shaft-cli doctor local-ai-status` and `doctor_managed_local_ai_status` report enablement, eligibility, and the DISABLED snapshot. They do not list the reviewed pin table. If Maven or CI needs a local-AI assertion, run the unit tests that inspect status, setup, cache, and properties. Do not add a `provision` or `benchmark` step to a default pipeline. If a job must stay offline, pass `--offline` on both `plan` and `install`; a missing or revoked pin fails closed. ### Clean, update, and roll back Create and approve a separate plan for every maintenance operation. Clean removes only unchanged installations owned by the current reviewed manifest; changed, unknown, and other-version content is preserved. If changed or unknown owned content blocks cleanup, the command returns a generic failure without a receipt rather than listing each preserved installation: ```bash shaft-cli setup plan \ --profile LOCAL_AI \ --mode MANAGED \ --operation CLEAN \ --output /absolute/path/local-ai-clean.json shaft-cli setup install \ --plan /absolute/path/local-ai-clean.json \ --approve sha256: \ --accept-license MIT \ --accept-license Apache-2.0 ``` Clean verifies the activation record and every selected installation before it removes anything. If changed or unknown content blocks cleanup, SHAFT preserves the verified active/previous record and its rollback candidate. After the reviewed artifacts clean successfully, SHAFT clears the activation record, so provision and activate two different reviewed pairs again before expecting `ROLLBACK` planning to succeed. For an update, create a new `INSTALL` plan from the current release and apply it with `setup install` or its `setup update` alias. SHAFT provisions the new content-addressed pair before activation and retains one exact prior reviewed pair. Rollback is available only after two different reviewed pairs have activated successfully and the prior pair still verifies exactly. It swaps the cached active and previous pairs without downloading or deleting artifacts: ```bash shaft-cli setup plan \ --profile LOCAL_AI \ --mode MANAGED \ --operation ROLLBACK \ --output /absolute/path/local-ai-rollback.json shaft-cli setup install \ --plan /absolute/path/local-ai-rollback.json \ --approve sha256: \ --accept-license MIT \ --accept-license Apache-2.0 ``` SHAFT rechecks the approved candidate, complete owned file inventories, and current host eligibility under the cache lock before changing activation. A missing, changed, corrupt, or newly ineligible prior pair fails without a rollback receipt. ## Preview: managed OCR setup :::warning[Not released] This workflow is not yet available on `SHAFT_ENGINE` `main` or in a published SHAFT release. Keep using the [current OCR first-use model flow](../../integrations/ocr.md#configure-language-models) until a release that contains the complete setup stack is available. The preview defaults to the `eng` and `ara` model bundle. Omit `--language` to use that bundle, or repeat exact Tesseract codes on plan and selected status/verify commands. Install recovers the selected languages from the reviewed plan: ```bash shaft-cli setup plan --profile OCR --mode MANAGED \ --language fra --language deu \ --output /absolute/path/ocr-plan.json shaft-cli setup install --plan /absolute/path/ocr-plan.json \ --approve sha256: shaft-cli setup verify --profile OCR --language fra --language deu ``` The reviewed actions bind the normalized component set. Repeating `--language` during install is optional and must match when supplied. The planned OCR provider enforces `--offline`: it can accept a verified installed model, legacy model, or artifact-cache entry without network access. It has no start, stop, or logs lifecycle. ::: ## Related - [Install SHAFT](/docs/start/installation) - [shaft-cli command line](/docs/agentic/cli) - [Programmatic properties configuration](/docs/reference/properties/Programmatic_Config) - [Reporting](/docs/features/reporting) - [Self-healing locators](/docs/reference/actions/GUI/Locators_And_Self_Healing) - [BrowserStack](/docs/integrations/browserstack) - [Local infrastructure overview](/docs/start/local-infrastructure) --- # Quick start Canonical HTML: https://shafthq.github.io/docs/start/quick-start Markdown: https://shafthq.github.io/md/start/quick-start.md # 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 1. New project generation Use the SHAFT Project Generator to create a ready-to-run Maven project. 2. Existing project upgrade Run the transactional upgrader for Selenium, Appium, REST Assured, Cucumber, or older SHAFT projects. 3. MCP integration Connect agents, Capture, Doctor, and reviewed repair workflows after the project compiles. 4. Manual creation Add dependencies, create folders, and copy one runner sample when generation is not possible. ## Workflow map ```mermaid flowchart TD Goal{"What are you starting from?"} Goal --> New["New repository"] Goal --> Existing["Existing automation project"] Goal --> Agent["Agent or MCP workflow"] Goal --> Manual["Constrained manual setup"] New --> Generator["Project Generator"] Existing --> Upgrade["Upgrade guide"] Agent --> IntelliJ["IntelliJ plugin"] Agent --> MCP["MCP guide"] IntelliJ --> MCP Manual --> Dependencies["Add BOM and shaft-engine"] Generator --> Run["mvn test"] Upgrade --> Run MCP --> Review["Review generated changes"] Dependencies --> Sample["Copy one runner sample"] Sample --> Run ``` ## Watch the technical walkthrough A 3.5-minute tour of the API, synchronization, evidence, scale options, the upgrader, and the optional agentic tools, using real SHAFT code and real runs. English captions are on by default. - **0:00** SHAFT in three lines, and the plumbing it replaces - **0:29** One API - **0:59** Reliability - **1:31** Evidence - **1:54** Scale - **2:25** Migrate - **2:50** Agentic, optional - **3:16** Start The narration uses a synthetic voice. The full-quality master, captions, and sources are on the [SHAFT feature video release](https://github.com/ShaftHQ/SHAFT_ENGINE/releases/tag/shaft-feature-video-v2-20261009). ## 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](/docs/start/installation). 2. Run the project from its root: 3. Confirm Maven reports passing tests. 4. Open the generated evidence under . See [Reporting and evidence](/docs/features/reporting) for what each report contains and how to configure it. 5. If the sample test, screenshots, logs, and report match what you expected, 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: Once the local run looks right, wire the same command into your pipeline with [CI/CD Integration](/docs/reference/guides/CI_CD_Integration). ## New project generation Use this when you are creating a SHAFT project from scratch. :::tip Default path The [SHAFT Project Generator](/docs/start/installation) is the recommended way to create a new project. ::: 1. Open the [Project Generator](/docs/start/installation). 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: 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. :::tip Safe migration path The [automated upgrade tool](/docs/start/upgrade) updates Maven dependencies, validates the result, and rolls changes back if compilation fails. ::: Open the [automated upgrade and rollback guide](/docs/start/upgrade/run#download-and-run) 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. If you work in IntelliJ IDEA, start with the [SHAFT IntelliJ plugin](/docs/agentic/intellij); it wraps the same MCP tools for recording, coding-partner planning, Doctor, Healer, and guide search. 2. For other clients, connect from the [SHAFT MCP guide](/docs/agentic/mcp#applications). 3. Keep model credentials in the MCP client or provider configuration, not in source files. 4. Use Capture and Doctor output as evidence, then review generated code before committing it. The IntelliJ plugin's first-run wizard has five steps: this project, prerequisites, one recommended agent, install and check, then a first success action. The install step copies a command and pre-types it in the terminal. Review it and press Enter yourself. The plugin does not execute the installer. **Open the Assistant** or **Record a sample flow** finishes setup. ![SHAFT IntelliJ first-run wizard on the project step, with Continue under the detected facts](/img/agentic/intellij-plugin-first-run-wizard.png) See the [IntelliJ plugin guide](/docs/agentic/intellij) for the copied command, what a setup report contains, and what happens on a failed check. Use the [MCP command reference](/docs/agentic/mcp#mcp-command-reference) for Capture, Doctor, and remote-server commands. Use the [manual configuration section](/docs/agentic/mcp#manual-configuration) 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`: 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: ```json { "searchQuery": "SHAFT_Engine", "expectedTitle": "DuckDuckGo", "expectedResultTitle": "SHAFT Engine", "expectedResultText": "SHAFT Engine" } ``` 4. Pick one runner sample below. 5. 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. :::note 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`: ```java package testPackage; 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 = By.xpath( "(//article[@data-testid='result'])[1]//a[@data-testid='result-title-a']"); @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 searchForQueryAndOpenFirstResultAndAssertTitleAndText() { driver.browser().navigateToURL(targetUrl) .and().element().type(searchBox, testData.get("searchQuery") + Keys.ENTER) .and().element().click(firstSearchResult) .and().assertThat().title().contains(testData.get("expectedResultTitle")) .and().element().assertThat(By.tagName("body")).text().contains(testData.get("expectedResultText")); } @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`: ```java package testPackage; 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 = By.xpath( "(//article[@data-testid='result'])[1]//a[@data-testid='result-title-a']"); @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 searchForQueryAndOpenFirstResultAndAssertTitleAndText() { driver.browser().navigateToURL(targetUrl) .and().element().type(searchBox, testData.get("searchQuery") + Keys.ENTER) .and().element().click(firstSearchResult) .and().assertThat().title().contains(testData.get("expectedResultTitle")) .and().element().assertThat(By.tagName("body")).text().contains(testData.get("expectedResultText")); } @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`: ```java package cucumberTestRunner; @Listeners({com.shaft.listeners.TestNGListener.class}) public class CucumberTests extends AbstractTestNGCucumberTests { } ``` Create `src/test/java/customCucumberSteps/StepDefinitions.java`: ```java package customCucumberSteps; 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 open the first result") public void i_open_the_first_result() { driver.element().click( By.xpath("(//article[@data-testid='result'])[1]//a[@data-testid='result-title-a']")); } @Then("I should see the page text contains {string}") public void i_should_see_the_page_text_contains(String expectedText) { driver.element().assertThat(By.tagName("body")).text().contains(expectedText); } @Then("I close the browser") public void i_close_the_browser() { driver.quit(); } } ``` Create `src/test/resources/features/search.feature`: ```gherkin 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 open the first result Then I should see the page title contains "SHAFT Engine" And I should see the page text contains "SHAFT Engine" Then I close the browser ``` For manually created Cucumber projects, edit the Cucumber Java run configuration template and add: ```bash --plugin com.shaft.listeners.CucumberFeatureListener ``` ### Run and configure Use the [properties reference](/docs/reference/properties/PropertiesList) 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](https://img.shields.io/github/stars/shafthq/shaft_engine?logoColor=black&style=social) 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. | Capability | Add this module | |---|---| | Reference-image assertions, negative reference-image assertions, image-path touch actions | `shaft-visual` | | BrowserStack SDK interception and YAML orchestration | `shaft-browserstack` | | Local non-headless desktop recording | `shaft-video` | | SikuliX image-based desktop automation | `shaft-sikulix` | | Deterministic web locator recovery | `shaft-heal` plus `healing.strategy=shaft-heal` | Reference-image assertions require `shaft-visual`: ```java 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](/docs/start/upgrade) for the complete method matrix. ## Related - [Installation](/docs/start/installation) - [Reporting and evidence](/docs/features/reporting) - [CI/CD Integration](/docs/reference/guides/CI_CD_Integration) - [Upgrade](/docs/start/upgrade) - [SHAFT MCP](/docs/agentic/mcp) - [Modules](/docs/features/modules) --- # Run the modular SHAFT upgrade Canonical HTML: https://shafthq.github.io/docs/start/upgrade/run Markdown: https://shafthq.github.io/md/start/upgrade/run.md # Run the modular SHAFT upgrade Follow these steps to upgrade a Maven project with the transactional `upgrade_to_modular_shaft.py` script. For what the script does internally, see [How the upgrade works](/docs/start/upgrade/how-it-works). For command options, coordinates, and module boundaries, see the [upgrade reference](/docs/start/upgrade/reference). Start from the [upgrade overview](/docs/start/upgrade) if you are not sure which path applies. ## Prerequisites - A Maven project with at least one `pom.xml`. - Python 3.9 or newer available as `python`. The script uses only the Python standard library. - Maven on `PATH`, or a project Maven wrapper (`mvnw`/`mvnw.cmd`). - A JDK compatible with the latest SHAFT release. Current SHAFT builds require JDK 25. - A clean version-control working tree is strongly recommended. The script has its own transaction, but Git remains useful for reviewing the successful migration. ## Download and run From the project that should be upgraded: ```shell python -c "import runpy,sys,urllib.request as u;p='upgrade_to_modular_shaft.py';u.urlretrieve('https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/shaft-upgrader/upgrade_to_modular_shaft.py',p);sys.argv=[p,'--project','.'];runpy.run_path(p,run_name='__main__')" ``` The script prints: - Detected project type, native automation stack, and test runner. - Every POM selected for migration. - Whether each optional module will be added. - The source/configuration evidence behind each optional-module decision. - Baseline, upgraded, and optional AI repair compilation status. Review the plan and answer `y` to start the transaction. ## Choose an upgrade type Agents and humans should choose one of three upgrade depths: | Upgrade type | Risk | Supported projects | What changes | | --- | --- | --- | --- | | `basic` | Low | Legacy SHAFT, modular SHAFT, Selenium, Appium, REST Assured, or Cucumber Maven projects | Updates selected POMs to modular SHAFT and leaves existing test source intact. Existing tests keep using their current APIs; new tests can use SHAFT and Allure reporting. | | `session` | Medium | Selenium/Appium projects only | Runs `basic`, then rewrites supported driver/session creation to `SHAFT.GUI.WebDriver`, wraps existing Selenium/Appium driver constructors, and rewrites `driver.close()` termination calls to `driver.quit()` where the driver variable was migrated. | | `full` | High | Selenium/Appium projects only | Runs `session`, then rewrites simple browser and element actions such as `get`, `navigate().to`, `findElement(...).click()`, `sendKeys`, `getText`, `isDisplayed`, and `getAttribute` to SHAFT engine calls. Element and browser assertions remain manual. | `basic` is the default: ```bash python upgrade_to_modular_shaft.py --project . --upgrade-type basic ``` For Selenium/Appium projects, opt into session or action migration explicitly: ```bash python upgrade_to_modular_shaft.py --project . --upgrade-type session python upgrade_to_modular_shaft.py --project . --upgrade-type full ``` AI agents that use SHAFT MCP, Codex, Claude, or another automation shell should ask for the user's preferred risk level before applying changes. The script can print the same decision menu as JSON without writing files or running Maven: ```bash python upgrade_to_modular_shaft.py --project . --agent-plan ``` The JSON response contains `basic`, `session`, and `full` entries with risk, eligibility, reason, description, detected stacks/runners, candidate POMs, and a recommended command. `session` and `full` are marked ineligible unless the project is detected as Selenium or Appium. ## Preview without changing files ```bash python upgrade_to_modular_shaft.py --project . --dry-run ``` Dry-run prints unified POM and optional source diffs for the selected `--upgrade-type`. It does not write files or run Maven. ## Non-interactive usage ```bash python upgrade_to_modular_shaft.py \ --project . \ --yes \ --report target/shaft-upgrade-report.json ``` `--yes` is required when standard input is not interactive. The optional report records the selected upgrade type, version, POMs, detected modules, evidence, source files changed by the selected upgrade type, compile count, AI attempt count, and rollback status. It never contains the API key. ## CI and cache migration 1. Change cache keys when `pom.xml` hashes are not already part of the key. 2. Do not copy the old `SHAFT_ENGINE` repository directory to `shaft-engine`. 3. Let Maven resolve and checksum the new paths under `~/.m2/repository/io/github/shafthq/`. 4. Use the purge command only for a suspected stale or failed relocation: ```bash mvn dependency:purge-local-repository \ -DmanualInclude=io.github.shafthq:SHAFT_ENGINE,io.github.shafthq:shaft-engine \ -DreResolve=false ``` 5. Build once with an empty CI cache before comparing dependency size or timing. ## Validate the migration ```bash mvn dependency:tree -Dincludes=io.github.shafthq mvn clean install -DskipTests -Dgpg.skip mvn test ``` The dependency tree should contain one aligned SHAFT version, `shaft-engine`, and only the selected optional modules. When tests run, confirm the expected number of `allure-results/*-result.json` files exists before treating the Allure summary as authoritative. ### Missing-provider troubleshooting Reference-image assertion or image-path touch action reports no visual provider Add shaft-visual ; verify org.openpnp:opencv , Applitools Eyes, and Selenium Shutterbug resolve transitively. Ordinary screenshot capture fails Do not add shaft-visual reflexively; diagnose WebDriver/Appium screenshot support and reporting paths. Direct BrowserStack session fails to start Check credentials, execution address, capabilities, and connectivity first; shaft-browserstack is not required for the direct path. platformsList or parallelsPerPlatform has no effect Add shaft-browserstack and verify the BrowserStack SDK is active and reading the generated browserstack.yml . Desktop recording reports no provider Add shaft-video ; confirm the OS-specific ws.schild:jave-nativebin-* artifact resolves. SHAFT.GUI.SikuliX reports that SikuliX classes are missing Add shaft-sikulix ; Appium Windows sessions do not require it. healing.strategy=shaft-heal logs that the provider is absent Add shaft-heal ; confirm the BOM aligns it with shaft-engine . SHAFT Heal rejects every candidate Inspect the Allure healing report, confidence threshold, ambiguity margin, and retained fingerprint instead of weakening checks blindly. Mobile recording changed unexpectedly Do not add shaft-video ; verify the Appium driver supports native recording. NoSuchMethodError or mixed SHAFT modules Import shaft-bom , remove explicit mismatched module versions, and inspect mvn dependency:tree -Dincludes=io.github.shafthq . ## Completion checklist 1. Replace `SHAFT_ENGINE` with `shaft-engine`. 2. Import `shaft-bom` and remove explicit versions from SHAFT module dependencies. 3. Add `shaft-visual` only for the listed reference-image and image-lookup methods. 4. Add `shaft-heal` only for explicitly configured web locator recovery. 5. Add `shaft-browserstack` only for BrowserStack SDK orchestration. 6. Add `shaft-video` only for local non-headless desktop recording. 7. Update CI cache keys and run one clean-cache build. 8. Verify one SHAFT version with `mvn dependency:tree`. 9. Compile, test, and confirm Allure results are populated. ## Rollback If migration blocks a release, revert the POM to `io.github.shafthq:SHAFT_ENGINE:10.2.20260605`, restore the previous dependency cache key, and remove all modular artifact declarations. Do not mix the old JAR with modular artifacts. Capture `mvn dependency:tree` before rollback so a missing provider or version mismatch can be diagnosed before the next attempt. ## Related - [Installation](/docs/start/installation) - [Quick Start](/docs/start/quick-start) - [Modules](/docs/features/modules) - [Upgrade overview](/docs/start/upgrade) --- # Set up local infrastructure Canonical HTML: https://shafthq.github.io/docs/start/local-infrastructure Markdown: https://shafthq.github.io/md/start/local-infrastructure.md # Set up local infrastructure Use SHAFT's setup surface to inspect external prerequisites or install supported tools into SHAFT-owned user directories. The safe default is `EXTERNAL`: SHAFT diagnoses the host without downloading, installing, or starting anything. The setup catalog includes web, mobile, Grid, reporting, OCR, Healenium, ReportPortal, BrowserStack Local, agent-tool, and local-AI profiles. Provider-backed managed installation includes `REPORTING`, `PLAYWRIGHT`, `MOBILE_ANDROID`, `MOBILE_IOS`, `MOBILE_WINDOWS`, `SELENIUM_GRID`, `HEALENIUM`, `REPORT_PORTAL`, `BROWSERSTACK_LOCAL`, and `AGENT_TOOLS`. `LOCAL_AI` uses the existing ServiceLoader provider. `WEB_LOCAL` has no provider: Selenium Manager and a host browser remain the local-web path. Reporting uses SHAFT's pinned, SHA-256-verified portable Node and adds Allure 3. Playwright adds reviewed Chromium, Firefox, WebKit, and FFmpeg payloads. Android uses the same portable Node owner and adds a reviewed Android SDK, Appium project, emulator, and SHAFT-owned virtual device. ## Inspect the catalog and host Install `shaft-cli` through the [shaft-cli installation flow](/docs/agentic/cli#install), then list the setup profiles: ```bash shaft-cli setup catalog shaft-cli setup doctor --profile REPORTING shaft-cli setup status --profile REPORTING ``` Add `--json` to `catalog`, `doctor`, `status`, or `verify` when a script needs a versioned machine-readable result. Readiness commands exit with `0` when ready and `3` when the profile is missing or degraded. ## Review and approve an installation Create an exact plan before allowing any mutation. Use an absolute path for the plan file: ```bash shaft-cli setup plan \ --profile REPORTING \ --mode MANAGED \ --output /absolute/path/reporting-plan.json ``` Review the JSON plan and copy the printed `sha256:` digest. Apply that exact plan with the same policy options used to create it: ```bash shaft-cli setup install \ --plan /absolute/path/reporting-plan.json \ --approve sha256: shaft-cli setup verify --profile REPORTING ``` `apply` and `update` are aliases for `install`. SHAFT rejects a changed or stale plan, a mismatched policy, a missing license acceptance, or an artifact whose checksum does not match before publishing it as installed. :::warning Treat the plan digest as a one-plan approval, not a general consent switch. Changing a version, source, checksum, destination, timeout, or policy option changes the digest and requires a new review. ::: ## Pages in this guide | Page | Type | Use it when | | --- | --- | --- | | [Mobile infrastructure](/docs/start/local-infrastructure/mobile) | How-to | Installing managed Android and Appium, or iOS and Windows Appium drivers. | | [Services and tools](/docs/start/local-infrastructure/services) | How-to | Installing managed Selenium Grid, Healenium, ReportPortal, BrowserStack Local, Playwright browsers, or Lighthouse, or diagnosing agent tools. | | [Previews: local AI and OCR](/docs/start/local-infrastructure/previews) | How-to (preview) | Evaluating the managed local AI provider or the unreleased managed OCR setup from a source build. | | [Infrastructure reference](/docs/start/local-infrastructure/reference) | Reference | Looking up plan/install policy options, the Java API, properties, and CLI exit codes. | ## Understand remote precedence An explicit remote execution address keeps endpoint-backed profiles external, even when `infrastructure.mode=MANAGED`. This applies to web, Selenium Grid, mobile, and Healenium profiles, so a remote test configuration cannot unexpectedly provision local infrastructure. It does not change unrelated profiles such as `REPORTING` or `LIGHTHOUSE`. ## Related - [Install SHAFT](/docs/start/installation) - [shaft-cli command line](/docs/agentic/cli) - [Programmatic properties configuration](/docs/reference/properties/Programmatic_Config) - [Reporting](/docs/features/reporting) - [Self-healing locators](/docs/reference/actions/GUI/Locators_And_Self_Healing) - [BrowserStack](/docs/integrations/browserstack) --- # SHAFT at a glance Canonical HTML: https://shafthq.github.io/docs/start/overview Markdown: https://shafthq.github.io/md/start/overview.md # One engine. Every test surface. SHAFT is a Maven-published Java automation framework that combines browser, mobile, API, terminal, database, validation, evidence, and reporting workflows behind one fluent API. Web Selenium sessions, resilient actions, assertions, screenshots, and reports. Mobile Appium native, mobile web, Android, iOS, and Flutter workflows. API REST Assured requests, authentication, extraction, schema checks, and assertions. IntelliJ coding partner Record, refactor, reuse Page Objects, diagnose failures, and call MCP from the IDE. ```mermaid flowchart LR Test["Your Test"] --> Engine["shaft-engine"] Engine --> Web["Web"] Engine --> Mobile["Mobile"] Engine --> API["API"] Engine --> CLI["CLI"] Engine --> DB["Database"] Engine --> Evidence["Allure evidence"] Evidence --> Doctor["Doctor"] Agent["Codex / Copilot / ChatGPT / Claude / Gemini"] --> IntelliJ["IntelliJ plugin"] Agent --> MCP["SHAFT MCP"] IntelliJ --> MCP MCP --> Engine ``` ## Watch SHAFT in 100 seconds Why teams choose SHAFT for new Java test automation, and how an existing Selenium suite adopts it without a rewrite. English captions are on by default; the video never plays until you start it. - **0:00** Red build: bug or flaky test? - **0:20** One engine on proven tools - **0:36** Waits, reports and scale built in - **0:57** Upgrade an existing suite - **1:16** Open source, and how to start The narration uses a synthetic voice. A 9:16 cut, full-quality masters, captions, and a source for every number are on the [SHAFT feature video release](https://github.com/ShaftHQ/SHAFT_ENGINE/releases/tag/shaft-feature-video-v2-20261009). ## Start in 90 seconds Use the [SHAFT Project Generator](/docs/start/installation) to choose your test runner and testing surfaces, then download a ready-to-run project. Run it from the project root: A SHAFT web step reads like this: ```java SHAFT.GUI.WebDriver driver = new SHAFT.GUI.WebDriver(); driver.browser().navigateToURL("https://example.com"); driver.element().click(By.id("submit")); ``` If the generated sample runs and the report gives you the evidence you expected, before you leave the guide. ## Why teams use SHAFT - One lifecycle and reporting model across test surfaces. - Built-in synchronization, screenshots, logging, and Allure evidence. - TestNG, JUnit, and Cucumber integration. - Optional modules are explicit; projects pay only for the capabilities they use. - Deterministic Capture, Doctor, and Heal workflows operate without a model provider. SHAFT is listed in the [Selenium ecosystem](https://www.selenium.dev/ecosystem/#frameworks) and received a [Google Open Source Peer Bonus](https://opensource.googleblog.com/2023/05/google-open-source-peer-bonus-program-announces-first-group-of-winners-2023.html). ## Choose your path The guide home is [Choose a path](/docs/journeys). Use that list. The table below matches the same jobs. | Goal | Go directly to | |---|---| | Create a new project | [Project Generator](/docs/start/installation) | | Find features added since modularization | [What's new](/docs/features/whats-new) | | Run the first test | [Quick start](/docs/start/quick-start) | | See where the evidence and reports come from | [Reporting and evidence](/docs/features/reporting) | | Run the suite in a pipeline | [CI/CD Integration](/docs/reference/guides/CI_CD_Integration) | | Use SHAFT from IntelliJ | [IntelliJ IDEA plugin](/docs/agentic/intellij) | | Connect another AI coding agent | [SHAFT MCP](/docs/agentic/mcp) | | Diagnose a failed run | [SHAFT Doctor](/docs/agentic/doctor) | | Recover a changed locator | [SHAFT Heal](/docs/agentic/heal) | | Upgrade a Selenium, Appium, REST Assured, or older SHAFT project | [Automated upgrade tool](/docs/start/upgrade) | ## Related - [Installation](/docs/start/installation) - [Quick Start](/docs/start/quick-start) - [Reporting and evidence](/docs/features/reporting) - [CI/CD Integration](/docs/reference/guides/CI_CD_Integration) - [Upgrade](/docs/start/upgrade) - [Modules](/docs/features/modules) - [What's new](/docs/features/whats-new) --- # Upgrade to modular SHAFT Canonical HTML: https://shafthq.github.io/docs/start/upgrade Markdown: https://shafthq.github.io/md/start/upgrade.md # Upgrade to modular SHAFT The only recommended upgrade route is the transactional `shaft-upgrader` module and its [`upgrade_to_modular_shaft.py`](https://github.com/ShaftHQ/SHAFT_ENGINE/blob/main/shaft-upgrader/upgrade_to_modular_shaft.py) script. It upgrades: - Native Selenium, Appium, REST Assured, or Cucumber Maven projects using TestNG, JUnit, or Cucumber. - Projects already using modular SHAFT. - Legacy projects using `io.github.shafthq:SHAFT_ENGINE`. For native projects, the default `basic` upgrade preserves existing Selenium, Appium, REST Assured, Cucumber, TestNG, and JUnit source and dependencies. It adds modular SHAFT so the project can adopt the SHAFT API incrementally. Native projects start with `shaft-engine` only; their existing third-party BrowserStack, OpenCV, or video dependencies are preserved without being reinterpreted as SHAFT optional-module usage. Selenium and Appium projects can opt into deeper source migration with `--upgrade-type session` or `--upgrade-type full`. These modes stay inside the same compile-validated transaction and roll back POM and source changes together when validation fails. For legacy SHAFT projects, Java imports remain under `com.shaft`. The script replaces the old Maven coordinate, imports the BOM, scans source and configuration for optional capabilities, compiles the result, and rolls back every changed file if compilation does not pass. The [manual migration reference](/docs/start/upgrade/reference) exists only for reviewing the automated result and troubleshooting; it is not an alternative migration path. ## Scan-first upgrade path Use this section when you only need to decide what to run. | Starting point | Recommended path | Why | |---|---|---| | Old `SHAFT_ENGINE` dependency | Run the automated upgrader in `basic` mode | Replaces coordinates, imports the BOM, infers optional modules, and keeps Java imports under `com.shaft`. | | Selenium, Appium, REST Assured, or Cucumber Maven project | Run the automated upgrader in `basic` mode first | Adds `shaft-engine` without rewriting working tests. | | Selenium/Appium project ready for source migration | Use `--upgrade-type session` or `--upgrade-type full` after a clean baseline | Rewrites source only inside the same rollback-protected transaction. | | Broken compile before migration | Fix the baseline before upgrading | The upgrader stops before touching files when the unchanged project does not compile. | To see which starting point you have, search the project's POMs for SHAFT coordinates: ```bash grep -rn --include=pom.xml "io.github.shafthq" . ``` An `SHAFT_ENGINE` artifact means a legacy SHAFT project; `shaft-engine`, `shaft-bom`, or another SHAFT module means modular SHAFT; no match means a native project. The upgrader applies the same rules; see [project detection](/docs/start/upgrade/how-it-works#project-detection). The safe first command is the one in [Download and run](/docs/start/upgrade/run#download-and-run). Review the generated diff, run your normal tests, then use the [module map](/docs/start/upgrade/reference#module-map) only when you need to explain why an optional module was added. Rollback rule: if validation fails, every touched file is restored byte-for-byte unless an allowed repair succeeds inside the same transaction. ## Pages in this guide | Page | Type | Use it when | | --- | --- | --- | | [Run the upgrade](/docs/start/upgrade/run) | How-to | You are ready to upgrade a project: prerequisites, the download-and-run command, upgrade types, dry-run, CI, validation, checklist, and rollback. | | [How the upgrade works](/docs/start/upgrade/how-it-works) | Explanation | You need to understand the script's guarantees, project detection, optional-module evidence, compile-and-rollback transaction, AI repair, and Selenium source rewrites. | | [Upgrade reference](/docs/start/upgrade/reference) | Reference | You are reviewing the result: command options, coordinates, the module map, dependency boundaries, the legacy relocation, and measured download sizes. | ## Related - [Installation](/docs/start/installation) - [Quick Start](/docs/start/quick-start) - [Modules](/docs/features/modules) --- # API testing Canonical HTML: https://shafthq.github.io/docs/testing/api Markdown: https://shafthq.github.io/md/testing/api.md # API testing Start here when you want one REST call, one assertion, and request/response evidence in the same report as your browser and mobile tests. ## Prerequisites - A SHAFT Maven project. [Install SHAFT](/docs/start/installation) walks through generating one. - Network access from the test machine to the API under test (the sample calls `restcountries.com`), including any proxy settings your network requires. - Credentials for protected APIs, kept outside source control. See [API Authentication](/docs/reference/actions/API/API_Authentication). ```java public class CountryApiTest { @Test public void getCountryByCapital() { SHAFT.API api = new SHAFT.API("https://restcountries.com/v3.1/"); api.get("capital/Cairo"); api.assertThatResponse() .extractedJsonValue("$[0].name.common") .isEqualTo("Egypt"); } } ``` ```mermaid flowchart LR Builder["Build request"] --> Send["Send request"] Send --> Evidence["Request + response evidence"] Evidence --> Assert["Fluent response assertion"] ``` Continue with [request building](/docs/reference/actions/API/Request_Builder), [authentication](/docs/reference/actions/API/API_Authentication), and [response assertions](/docs/reference/actions/API/Response_Validations). When the API is part of a browser journey, use [UI and API contract replay](/docs/testing/contracts) to record the exchanged traffic once and assert or replay it later. ## Run and inspect evidence Run the test from the project root: SHAFT attaches request, response, status, headers, body, and assertion evidence under . If the request fails before assertions, check the base URI, proxy settings, authentication, and whether automatic `2xx` status assertion is appropriate for the scenario. ## Verify - Maven reports the test as run and passed. - The Allure report for the test shows the request, the response status, headers, and body, and the `extractedJsonValue` assertion. - To prove the assertion is live, change `"Egypt"` to another value and rerun. The test fails, and the report shows the expected and actual values. ## First useful next steps | Need | Start with | |---|---| | Add headers, query parameters, or body | [Request Builder](/docs/reference/actions/API/Request_Builder) | | Reuse tokens or basic auth | [API Authentication](/docs/reference/actions/API/API_Authentication) | | Validate JSON fields or schemas | [Response Validations](/docs/reference/actions/API/Response_Validations) | | Record and replay UI/API traffic | [UI and API contract replay](/docs/testing/contracts) | | Track OpenAPI coverage | [API configuration](/docs/reference/configuration/apiConfig) | ## Related - [Request Builder](/docs/reference/actions/API/Request_Builder) - [Response Validations](/docs/reference/actions/API/Response_Validations) - [API Authentication](/docs/reference/actions/API/API_Authentication) - [UI and API contract replay](/docs/testing/contracts) --- # CLI testing Canonical HTML: https://shafthq.github.io/docs/testing/cli Markdown: https://shafthq.github.io/md/testing/cli.md # CLI testing SHAFT provides terminal, Docker, SSH, and file actions with the same reporting model used by browser and API tests. ## Prerequisites - A SHAFT Maven project. [Install SHAFT](/docs/start/installation) walks through generating one. - The command you test available to the user that runs Maven. For Docker actions, a running Docker daemon. For SSH actions, a reachable host, user, and key, with secrets injected by CI rather than committed. ```mermaid flowchart LR Test --> Terminal["Terminal action"] Terminal --> Local["Local process"] Terminal --> Docker["Docker"] Terminal --> SSH["SSH"] Local --> Report["Allure evidence"] Docker --> Report SSH --> Report ``` Open the [terminal actions reference](/docs/reference/actions/CLI/Terminal_Actions) for executable examples and result handling. ## First useful command test Use CLI actions when the product behavior depends on a process, file, Docker command, deployment script, or remote shell state. Start with a harmless local command and assert on the captured output: ```java SHAFT.CLI terminal = new SHAFT.CLI(); String output = terminal.performTerminalCommand("echo Hello SHAFT"); SHAFT.Validations.assertThat().object(output).contains("Hello SHAFT"); ``` Run the test with Maven: ```bash mvn test ``` The command, output, and validation status are attached to the same Allure evidence flow as the rest of the suite. ## Verify - Maven reports the test as run and passed. - The Allure report shows the command, its captured output, and the `contains("Hello SHAFT")` validation. - To prove the assertion is live, change the expected text and rerun. The validation fails with the captured output attached. ## Troubleshooting | Symptom | Check | |---|---| | Command works locally but fails in CI | Use absolute paths or set the working directory explicitly. | | Docker command times out | Verify Docker is running before tuning command timeout properties. | | SSH command cannot connect | Validate host, user, key, network route, and CI secret injection. | | File assertion fails on Windows | Prefer forward slashes in Java paths; Java resolves them on Windows. | ## Related - [Terminal Actions](/docs/reference/actions/CLI/Terminal_Actions) - [File Actions](/docs/reference/actions/CLI/File_Actions) - [Docker Terminal](/docs/reference/actions/CLI/Docker_Terminal) --- # Database testing Canonical HTML: https://shafthq.github.io/docs/testing/database Markdown: https://shafthq.github.io/md/testing/database.md # Database testing Use SHAFT database actions to open a JDBC connection, execute statements, and attach query evidence to the test report. ## Prerequisites - A SHAFT Maven project. [Install SHAFT](/docs/start/installation) walks through generating one. - The JDBC driver dependency for the database you connect to, on the test classpath. See [connection strings](/docs/reference/actions/DB/Connection_Strings) and, for Oracle, the [Oracle setup](/docs/reference/actions/DB/Oracle_JDBC_Setup). - A reachable, disposable database or schema with read permission. Keep credentials outside source control. ```mermaid flowchart LR Test --> Config["Connection string"] Config --> JDBC["SHAFT database action"] JDBC --> Query["Query / update"] Query --> Result["Result validation + report"] ``` See [database actions](/docs/reference/actions/DB/DB_Actions), [connection strings](/docs/reference/actions/DB/Connection_Strings), and the [Oracle setup](/docs/reference/actions/DB/Oracle_JDBC_Setup). ## First useful query test Start with a read-only query against a disposable schema, containerized database, or in-memory database. Keep credentials outside source control and validate a small result before adding write operations. ```java SHAFT.DB database = new SHAFT.DB("jdbc:h2:mem:test"); ResultSet rows = database.executeSelectQuery("SELECT 1"); SHAFT.Validations.assertThat().object(rows).isNotNull(); ``` Run the test with Maven: ```bash mvn test ``` The query, connection target, and validation result are attached to the test report. If the connection fails, verify the JDBC URL, driver dependency, network route, schema permissions, and whether CI has the required secret values. ## Verify - Maven reports the test as run and passed. - The report for the test shows the query, the connection target, and the validation result. - If the connection fails, work through the checks above in order: JDBC URL, driver dependency, network route, schema permissions, CI secrets. ## Choose the next reference | Need | Start with | |---|---| | Build a JDBC URL | [Connection Strings](/docs/reference/actions/DB/Connection_Strings) | | Run selects, updates, or result validation | [DB Actions](/docs/reference/actions/DB/DB_Actions) | | Connect to Oracle | [Oracle JDBC Setup](/docs/reference/actions/DB/Oracle_JDBC_Setup) | ## Related - [DB Actions](/docs/reference/actions/DB/DB_Actions) - [Connection Strings](/docs/reference/actions/DB/Connection_Strings) - [Oracle Jdbc Setup](/docs/reference/actions/DB/Oracle_JDBC_Setup) --- # Flutter testing guide Canonical HTML: https://shafthq.github.io/docs/testing/flutter Markdown: https://shafthq.github.io/md/testing/flutter.md # Flutter testing guide Specialized guide for automating Flutter apps with SHAFT Engine and the Appium Flutter Integration driver. For broader mobile setup, see [Mobile and Flutter testing](/docs/testing/mobile). ## Prerequisites Requires JDK 17+, Appium Flutter Integration driver, emulator or BrowserStack, and a debug APK. ## Obtain or build the demo APK Prefer the SHAFT CI pin of AppiumTestDistribution/appium-flutter-server: - Repository: https://github.com/AppiumTestDistribution/appium-flutter-server - Pin: 78ba97a6146091b944335d0db3330f74416f3ac7 - Sparse checkout must include both demo-app and server (gotcha) Clone that pin, build a debug APK from demo-app, then assembleDebug with the Integration Server dart entrypoint under integration_test/appium.dart (same recipe as e2eTests.yml). Copy app-debug.apk to shaft-engine/src/test/resources/testDataFiles/apps/flutter-demo.apk. Release APKs are unsupported and fail with Flutter server is not started. ## Locator reference Prefer SHAFT.GUI.Locator.flutter* over raw AppiumBy.flutter* in new tests: | Factory | Finds by | Example | | --- | --- | --- | | flutterKey | Key / ValueKey | SHAFT.GUI.Locator.flutterKey("LoginButton") | | flutterText | Exact text | SHAFT.GUI.Locator.flutterText("Please Login") | | flutterTextContaining | Partial text | SHAFT.GUI.Locator.flutterTextContaining("Please") | | flutterType | Widget type | SHAFT.GUI.Locator.flutterType("TextField") | | flutterSemanticsLabel | Semantics label | SHAFT.GUI.Locator.flutterSemanticsLabel("login_button") | | flutterDescendant / flutterAncestor | Hierarchy | Pass AppiumBy.FlutterBy args | The Locators enum (XPATH/CSS) is only the relation-builder strategy enum, not a full By catalog. Cucumber ElementSteps accepts mobile/Flutter type strings such as flutterkey and accessibilityid; the Java API remains the primary Flutter path. ## Copy-paste runnable sample Matches testPackage.appium.FlutterTest against the pinned demo-app Login screen. ```java package testPackage.appium; public class FlutterLoginSample { private SHAFT.GUI.WebDriver driver; private static final By PLEASE_LOGIN = SHAFT.GUI.Locator.flutterText("Please Login"); private static final By USERNAME = SHAFT.GUI.Locator.flutterKey("username_text_field"); private static final By PASSWORD = SHAFT.GUI.Locator.flutterKey("password"); private static final By LOGIN = SHAFT.GUI.Locator.flutterKey("LoginButton"); private static final By HOME_TITLE = SHAFT.GUI.Locator.flutterText("Samples List"); @BeforeMethod public void setup() { SHAFT.Properties.platform.set().targetPlatform(Platform.ANDROID.name()); SHAFT.Properties.mobile.set().automationName(AutomationName.FLUTTER_INTEGRATION); SHAFT.Properties.mobile.set().browserName(""); SHAFT.Properties.platform.set().executionAddress("127.0.0.1:4723"); SHAFT.Properties.mobile.set().app("src/test/resources/testDataFiles/apps/flutter-demo.apk"); driver = new SHAFT.GUI.WebDriver(); } @Test public void tapTypeAssertText() { driver.assertThat().element(PLEASE_LOGIN).exists(); driver.element().type(USERNAME, "admin"); driver.element().type(PASSWORD, "1234"); driver.element().assertThat(USERNAME).text().isEqualTo("admin"); driver.element().click(LOGIN); driver.assertThat().element(HOME_TITLE).exists(); driver.element().assertThat(HOME_TITLE).text().contains("Samples"); } @AfterMethod(alwaysRun = true) public void teardown() { if (driver != null) { driver.quit(); } } } ``` ### Run locally 1. Start Appium on 127.0.0.1:4723 with the Flutter Integration driver available. 2. Attach an emulator or device. 3. From the SHAFT_ENGINE checkout, run Maven against FlutterTest with: `-Dshaft.enableFlutterE2E=true` `-DexecutionAddress=127.0.0.1:4723` `-Dmobile_app=src/test/resources/testDataFiles/apps/flutter-demo.apk` Without `-Dshaft.enableFlutterE2E=true`, FlutterTest throws SkipException so PR-gate unit jobs stay green with no emulator. ## Verify - Maven reports `FlutterTest` as run, not skipped. A skipped test means `-Dshaft.enableFlutterE2E=true` was not passed. - The tap, type, and assert steps pass against the debug APK. A release APK fails with `Flutter server is not started`. - If the session doesn't start, check the [common pitfalls](#common-pitfalls) below: driver package, automation name, and APK build type. ## SHAFT CI jobs (after #5755) Scheduled **E2E Tests** no longer boots a local AVD for Flutter. Nightly `FlutterTest` tap/type/text runs on BrowserStack. The local emulator path is automated on **Local E2E Tests**. | Job | Workflow | When it runs | What it proves | | --- | --- | --- | --- | | `Flutter_Demo_App_Build` | E2E Tests | Nightly, and when native or Flutter BrowserStack is selected | Wired debug APK at pin `78ba97a6146091b944335d0db3330f74416f3ac7` | | `Android_Flutter_BrowserStack` | E2E Tests | Nightly (same `jobs=all` selector as `Android_Native_BrowserStack`) | `FlutterTest` with `-Dshaft.enableFlutterE2E=true`, `-DexecutionAddress=browserstack`, `-Dmobile_automationName=FlutterIntegration`, Pixel 7 / 13.0, empty `browserStack.appUrl` | | `Ubuntu_Flutter_Emulator_Local` | Local E2E Tests | Scheduled nightly local suite, or `workflow_dispatch` `jobs=Ubuntu_Flutter_Emulator_Local` (or `all`) | Same `FlutterTest` against Appium on `127.0.0.1:4723` after `scripts/ci/flutter_emulator_preflight.sh` | ### Dispatch commands Nightly Flutter on BrowserStack only: ```text gh workflow run "E2E Tests" --ref main -f jobs=Android_Flutter_BrowserStack ``` Local emulator Flutter (KVM, Android SDK licenses including the Android XR preview license trap, Appium flutter-integration-driver, adb): ```text gh workflow run "Local E2E Tests" --ref main -f jobs=Ubuntu_Flutter_Emulator_Local ``` Preflight is `scripts/ci/flutter_emulator_preflight.sh`. It must pass before `android-emulator-runner`. Do not put the emulator job back on E2E Tests `needs` — AVD boot and SDK licenses must not gate the cloud nightly. ### Properties and locators (AI) - Locator policy: unique author-written id via `SHAFT.GUI.Locator.flutterKey` / `flutterText` / `flutterSemanticsLabel` first; then ARIA/semantics; native relative xpath last and only if Flutter factories cannot name the widget. - Skip unless `-Dshaft.enableFlutterE2E=true`. - Local: `-DexecutionAddress=127.0.0.1:4723` and `-Dmobile_app=.../flutter-demo.apk`. - BrowserStack: omit local Appium address so `FlutterTest` uses `executionAddress=browserstack`, `browserStack.appRelativeFilePath` to the wired APK, empty `appUrl`. - Keep `.*Flutter.*` out of `GLOBAL_TESTING_SCOPE`; dedicated jobs only. ## Common pitfalls | Pitfall | Fix | | --- | --- | | Release APK | Rebuild debug/profile with Integration Server target | | Sparse-checkout missing server | Include both demo-app and server | | Wrong driver package | Use appium-flutter-integration-driver + FlutterIntegration | | Confusing enable flags | `shaft.enableFlutterE2E` unskips FlutterTest; there is no nightly `enableFlutterEmulatorE2E` gate | | Flutter in GLOBAL_TESTING_SCOPE | Dedicated `Android_Flutter_BrowserStack` (nightly) and `Ubuntu_Flutter_Emulator_Local` (local) only | | Old counter-app locators | CI demo-app is the Appium Testing App login flow | | Android XR SDK license hang | Preflight accepts licenses non-interactively before emulator image download | ## Related - Engine sample: shaft-engine/src/test/java/testPackage/appium/FlutterTest.java - Nightly: `.github/workflows/e2eTests.yml` (`Flutter_Demo_App_Build`, `Android_Flutter_BrowserStack`) - Local emulator: `.github/workflows/e2eLocalTests.yml` (`Ubuntu_Flutter_Emulator_Local`) - Locator tips: [Locators and Self-Healing](/docs/reference/actions/GUI/Locators_And_Self_Healing) - Issues: https://github.com/ShaftHQ/SHAFT_ENGINE/issues/5741 https://github.com/ShaftHQ/SHAFT_ENGINE/issues/5743 --- # Mobile and Flutter testing Canonical HTML: https://shafthq.github.io/docs/testing/mobile Markdown: https://shafthq.github.io/md/testing/mobile.md # Mobile and Flutter testing SHAFT uses the same `SHAFT.GUI.WebDriver` facade for browser and Appium sessions. Configure the Appium endpoint, platform, automation name, and app, then create the driver normally. ## Prerequisites - A SHAFT Maven project. [Install SHAFT](/docs/start/installation) walks through generating one. - An Appium endpoint. Use either the SHAFT-managed Android runtime (see [Run a test on the managed Android runtime](#run-a-test-on-the-managed-android-runtime)) or an existing Appium server, device farm, or Grid set through `executionAddress`. - The app under test, and properties for the platform, automation name, and device. See [mobile configuration](/docs/reference/configuration/mobileConfig). ```java SHAFT.GUI.WebDriver driver = new SHAFT.GUI.WebDriver(); driver.element().touch() .tap(SHAFT.GUI.Locator.accessibilityId("Views")) .and() .assertThat(SHAFT.GUI.Locator.accessibilityId("Expandable Lists")) .exists(); driver.quit(); ``` ```mermaid flowchart LR Test --> SHAFT["shaft-engine"] SHAFT --> Appium["Appium server"] Appium --> Android Appium --> iOS Appium --> Flutter SHAFT --> Report["Allure evidence"] ``` ## Verify - The driver session starts and Maven reports the test as run and passed. - The Allure report shows the tap and the existence assertion. - When a touch action fails, `shaft-trace.json` records it as a `touch` event (see [Mobile failure trace evidence](#mobile-failure-trace-evidence)). ## Click and type by control kind Mobile and Windows desktop sessions use the same `element().click` / `element().type` facade as web. SHAFT classifies Appium/UIA controls and routes accordingly (focus → `sendKeys` / `mobile: type`, touch click fallback, checkbox toggle, WinAppDriver Edit/Button/CheckBox/ComboBox recipes). See [Web testing — Click and type by control kind](/docs/testing/web#click-and-type-by-control-kind) for the short cross-surface pointer; no new public API. ## Windows desktop apps Windows desktop automation uses the existing Appium dependency in `shaft-engine`; no optional module is required for locator-based Appium sessions. ```java SHAFT.Properties.platform.set() .targetPlatform("Windows") .executionAddress("http://127.0.0.1:4723"); SHAFT.Properties.web.set() .targetBrowserName("WindowsApp") .headlessExecution(false); SHAFT.Properties.mobile.set() .browserName("") .automationName("Windows") .app("C:\\Windows\\System32\\notepad.exe"); new DriverFactory().getHelper(DriverFactory.DriverType.APPIUM_WINDOWS); ``` Install and start Appium with the Windows driver before running the test. Add `shaft-sikulix` only when the test needs SikuliX image matching instead of Appium locators. ## SHAFT MCP mobile automation `shaft-mcp` can drive mobile sessions when an MCP client needs browser-style automation over Android or iOS targets: - Use `driver_initialize` with `engine=mobile_web` for mobile web checks in a resized desktop browser, or `engine=mobile_native` for Appium-backed native Android or iOS execution. Both take a nested `mobileOptions` request (absorbing the former separate `mobile_initialize_web_emulation`/`mobile_initialize_native` tools) configuring `appiumServerUrl`, `platformName`, `automationName`, `deviceName`, and either `app`, Android `appPackage`/`appActivity`, or iOS `bundleId`. - Use `mobile_get_contexts`, `mobile_switch_context`, `mobile_take_screenshot`, and `mobile_get_accessibility_tree` to inspect the live device screen before deciding what action to take. - Use `element_click`, `element_type`, `element_clear`, `mobile_swipe`, rotation, keyboard, background, and app activation tools to perform actions through SHAFT Engine touch/mobile APIs -- these unified tools dispatch to the active mobile session and absorb the former `mobile_tap`, `mobile_type`, `mobile_clear`, `mobile_double_tap`, `mobile_long_tap`, `mobile_swipe_by_offset`, `mobile_swipe_coordinates`, `mobile_swipe_element_into_view`, and `mobile_swipe_text_into_view` tools. `mobile_tap_coordinates` and `mobile_swipe`'s coordinate escape hatch (`startX`/`startY`/`endX`/`endY`) are fallback-only actions; generated recordings warn that coordinate replay will probably fail when a locator cannot be resolved. - Use `capture_start`, `capture_stop`, `capture_generate_replay`, `capture_code_blocks`, and `capture_record_at_target_code_blocks` to record, replay, and generate Java snippets that can be pasted into a SHAFT test or existing mobile page object -- these dispatch to the active mobile session, absorbing the former `mobile_record_start`/`mobile_record_stop`/`mobile_replay_recording`/`mobile_recording_code_blocks`/`mobile_record_at_target_code_blocks` tools. With `includeSensitiveValues=false` (the safe default), typed values are classified per field using the same deterministic privacy policy as web capture: password/token-like locators are redacted, while ordinary fields such as search boxes keep their values and remain replayable. - Use `mobile_toolchain_status` before Inspector setup when the agent needs exact readiness details. The response keeps the quick availability booleans and adds structured dependency diagnostics with a stable dependency id, detected path or version when available, a failure cause, and repair guidance for Node.js, npm, Appium, the Appium Inspector plugin, Android SDK tools, emulator support, and iOS host constraints. - Use `mobile_inspector_record_start` when the agent should launch a wrapped Appium Inspector recording session -- it now prepares and starts the session in one call, absorbing the former separate `mobile_inspector_record_prepare` tool. It lists connected Android devices from `adb devices -l`, reports cached Android emulators, surfaces the relevant toolchain diagnostic warnings and fixes, returns suggested capabilities, and includes a confirmation token. `mobile_inspector_record_status` returns the live recording status and, with an optional `action` (pause|resume|checkpoint|stop|discard), performs that control first -- absorbing the former separate `mobile_inspector_record_control` tool. Generated mobile snippets use only SHAFT facade syntax: locators are emitted as `SHAFT.GUI.Locator.*`, touch gestures are emitted through `driver.element().touch()`, and assertions are emitted through `driver.element().assertThat(...)`. `capture_code_blocks` returns the replay method plus ranked mobile Page Object handoff blocks: locator inventory, action sequence, and a draft Page Object. `capture_record_at_target_code_blocks` adds focused locator fields and an action snippet for an existing Java source anchor, so agents can merge a recording into the current page object instead of pasting a generated class. The wrapped Appium Inspector recorder reads the current accessibility tree/source and prefers Appium-style locators such as accessibility id, id/resource-id, Android UiAutomator, and XPath before falling back to coordinates. For native execution, either connect a real Appium target or let `mobile_inspector_record_start` guide the agent through local setup. Android proposals now delegate to the shared infrastructure provider and owned lifecycle described in the [local infrastructure guide](/docs/start/local-infrastructure/mobile#install-managed-android-and-appium). MCP does not maintain a second Android downloader, npm project, SDK installer, or emulator process owner. Confirmation is translated into the same reviewed plan, `android-sdk-license` approval, compatible receipt, and lease used by the CLI and Java API. When recording stops, MCP releases its lease and returns the same JSON recording plus replay-code flow used by `capture_stop`. iOS recording attaches to an existing Appium/Xcode-capable target; SHAFT MCP does not create iOS simulators. ## Run a test on the managed Android runtime Install the reviewed `MOBILE_ANDROID` plan before creating the driver. Managed engine bootstrap requires a compatible managed setup receipt and does not install missing tools. Set local Android execution and enable approved runtime startup in your test properties: ```properties title="src/main/resources/properties/custom.properties" executionAddress=local targetOperatingSystem=ANDROID mobile_automationName=UiAutomator2 mobile_deviceName=emulator-5554 infrastructure.profile=MOBILE_ANDROID infrastructure.mode=MANAGED infrastructure.autoStart=true ``` Then create and close the driver normally: ```java SHAFT.GUI.WebDriver driver = new SHAFT.GUI.WebDriver(); driver.element().touch() .tap(SHAFT.GUI.Locator.accessibilityId("Views")); driver.quit(); ``` An explicit remote execution address always wins. When `executionAddress` points to a Grid, device farm, or another non-local Appium endpoint, SHAFT does not inspect, install, start, or stop the local managed Android runtime. For a local managed session, SHAFT passes `APPIUM_HOME`, `ANDROID_HOME`, `ANDROID_SDK_ROOT`, `ANDROID_AVD_HOME`, and Android tool paths only to owned child processes; it does not change the parent process or shell environment. ## Mobile failure trace evidence Failed Appium touch actions are included in `shaft-trace.json` as `touch` events. When available, SHAFT records the action name, locator or text target, gesture parameters, platform, automation name, app package/activity or bundle id, current context, orientation, and window size. If `shaft.trace.includeNativePageSource=true`, failed native actions also include a bounded, redacted native page-source excerpt. ```java SHAFT.Properties.reporting.set() .traceEnabled(true) .traceIncludeNativePageSource(true); driver.element().touch() .swipeElementIntoView("Pay now", "VERTICAL") .rotate("LANDSCAPE"); ``` Context transitions are trace events too. A call such as `driver.browser().setContext("WEBVIEW_checkout")` records the previous context, requested context, and resulting context when the Appium provider supports context inspection. ## Scroll to screenshot or OCR targets The unreleased source-preview OCR runtime downloads missing pinned models on first use when `shaft.ocr.downloadEnabled` is `true`. For CI, run a representative OCR smoke step while network access is available, retain its model cache, then run the test job with downloads disabled. Use this only for source-build evaluation until a containing SHAFT release is published. Every requested model must exist in that cache and pass integrity verification. Use typed targets when native accessibility identifiers are unavailable. SHAFT checks the current screenshot before every gesture and supports vertical and horizontal searches in either direction: ```java var image = ImageTarget.fromPath(Path.of("src/test/resources/pay.png")); var text = OcrTarget.exact("Pay now"); driver.touch() .swipeElementIntoView(image, TouchActions.SwipeDirection.DOWN) .tap(image) .swipeElementIntoView(text, TouchActions.SwipeDirection.LEFT) .tap(text); ``` Pass a container locator first when the target is inside a nested list or carousel: ```java driver.touch().swipeElementIntoView( AppiumBy.id("checkout_carousel"), image, TouchActions.SwipeDirection.RIGHT); ``` Android uses Appium's `mobile: scrollGesture` boundary result. iOS and generic touch sessions stop after the searched screenshot region remains unchanged. SHAFT maps native screenshot pixels directly to touch coordinates and scales mobile-web screenshots to the browser viewport. ## Read Android performance data Use `driver.mobile().performance()` when the live Appium driver exposes its performance-data interface. Check the provider's advertised types, then request one tabular sample for the application package and type you need. ```java var performance = driver.mobile().performance(); if (performance.supportedTypes().contains("memoryinfo")) { var sample = performance.sample("com.example.app", "memoryinfo"); System.out.println(sample.columns()); System.out.println(sample.rows()); } performance.clear(); ``` Each `MobilePerformanceSample` contains its capture time, application ID, data type, columns, and rows. The model copies the provider table and exposes immutable lists. `history()` returns an immutable snapshot of the newest 100 successful samples for that driver identity; `clear()` removes that session's history. :::warning Performance data is available only when the live Appium driver implements the exact performance-data interface. Android drivers normally expose it. iOS, Windows, Mac2, generic, closed, and custom drivers without that interface fail with `UnsupportedOperationException` instead of inferring support from a platform name. ::: When failure tracing is enabled and the call is not under nested trace suppression, SHAFT records one backend-only `mobile/performance` event for each operation. Events include counts, not the application ID, requested type, columns, rows, or provider payload. Submitted and returned values are registered with the failure-trace redactor before later reports are rendered. ## Record the mobile screen Use `driver.mobile().recording()` when the live Appium driver supports screen recording. Start one bounded recording, then stop it for decoded media bytes or publish it to an exact local path. ```java var recording = driver.mobile().recording(); var options = new MobileRecordingOptions( Duration.ofSeconds(30), 32L * 1024 * 1024); recording.start(options); // Exercise the mobile application. Path saved = recording.stopAndSave(Path.of("artifacts", "mobile-recording.mp4")); System.out.println(saved); ``` `start()` uses a three-minute provider limit and a 64 MiB decoded-result limit. Explicit options accept one second through 30 minutes and one byte through 256 MiB. `stop()` returns a fresh byte array. `stopAndSave()` uses SHAFT's safe exact-target publisher and returns the normalized target path. Recording state belongs to the driver session and is shared with SHAFT's automatic failure-video recorder. One owner cannot start over or stop the other owner's recording. A failed provider start returns the session to idle; a provider stop failure keeps ownership active so the same caller can retry. Driver teardown closes the recording state without issuing another provider command. :::warning The Appium Java interface does not guarantee that a remote provider implements the screen-recording command. BrowserStack App Automate rejected the command on the tested Android and iOS real-device paths, including Appium 3.3.0. Keep the positive recording flow for a compatible Appium server; do not substitute a provider's session-video feature for these returned bytes. ::: When failure tracing is enabled and the call is not under nested trace suppression, SHAFT records one backend-only `mobile/recording` event for each public operation. Metadata contains only configured seconds or decoded byte counts. Media, target paths, provider payloads, DOM, and screenshots are not attached; target paths and provider failures are registered with the failure-trace redactor before later reports are rendered. ## Capture a bounded mobile evidence archive Use `driver.mobile().evidence()` to publish one bounded, redaction-aware snapshot of the current live Appium session. Supply the exact ZIP target and keep the returned immutable descriptor for typed log, performance, metadata, omission, and artifact-reference access. ```java var evidence = driver.mobile().evidence(); var bundle = evidence.capture(Path.of("artifacts", "mobile-evidence.zip")); System.out.println(bundle.archive()); for (var artifact : bundle.artifacts()) { System.out.printf("%s: %s%n", artifact.kind(), artifact.omitted() ? "omitted" : artifact.path()); } ``` The archive contains `mobile-evidence.json` plus stable entries for the current screenshot, current-context source, and the latest retained recording. Each artifact reference resolves to content or to an explicit omission marker. The descriptor also exposes immutable, redacted snapshots of SHAFT-owned mobile logs and performance history. `omissions()` distinguishes unsupported, not-started, empty, sensitive, oversized, provider-failed, active, missing, and changed components, including changes during capture and the absence of a retained recording, without copying provider messages. Capture does not switch context, start or clear log collection, clear performance history, or stop an active recording. A recording is eligible only after `stopAndSave()` publishes it successfully. If the context changes during capture, SHAFT discards the inconsistent screenshot and source. Capture and driver teardown share one lifecycle boundary; once teardown wins that boundary, the closed session cannot publish a stale archive. The existing `shaft.trace.maxArtifactMb` setting is one aggregate limit for the manifest and every archive entry. Text is UTF-8 byte-bounded, saved recordings are verified by size and SHA-256 before copying, and the exact target uses SHAFT's recoverable, symlink-safe publisher. Screenshot and source collection also follow the existing sensitive-evidence suppression policy. :::danger Capture can replace the exact target you provide. The ZIP can contain application screenshots, source, logs, performance values, and recording bytes. Store and share it as sensitive test evidence. Use a test-specific artifact path, and treat an omitted artifact as unavailable rather than reading a provider-managed session video or another external file. ::: When failure tracing is enabled and the call is not under nested trace suppression, SHAFT records one backend-only `mobile/evidence` event for each capture. Success metadata contains counts only. Archive paths, screenshot pixels, source, logs, performance values, recording bytes, DOM, URLs, attachments, and provider messages do not enter the event. ## Flutter applications SHAFT Engine supports automated testing of Flutter applications using the Appium Flutter Integration driver. This integration lets you test Flutter apps on both Android and iOS platforms. :::tip Specialized Flutter guide For prerequisites, demo APK build, locator table, pitfalls, and a full tap/type/assert sample using SHAFT.GUI.Locator.flutter*, see the [Flutter testing guide](/docs/testing/flutter). ::: ## Prerequisites ### 1. Install Appium Server First, install Appium with the Flutter driver plugin: ```bash # Install Appium globally npm install -g appium # Install the Flutter driver plugin appium driver install --source npm appium-flutter-integration-driver ``` ### 2. Verify Installation Verify that the Flutter driver is installed: ```bash appium driver list --installed ``` You should see the Flutter integration driver in the list of installed drivers. ### 3. Prepare Your Flutter App Your Flutter app must be built in either **debug** or **profile** mode. The Appium Flutter Driver does **not** support release mode. To enable Flutter driver integration in your app, add the following to your `main.dart`: ```dart void main() { // Enable Flutter Driver extension before calling runApp enableFlutterDriverExtension(); runApp(MyApp()); } ``` Then build your app: ```bash # For Android flutter build apk --debug # For iOS flutter build ios --debug ``` ## Usage in SHAFT Engine ### Basic Setup To test a Flutter app using SHAFT Engine, you need to: 1. Set the automation name to `FlutterIntegration` (this automatically enables Flutter driver support) 2. Specify the app path or URL 3. Set up your Appium server connection ### Example Test Class Use this complete Flutter example with TestNG and java-client's native Flutter locators: ```java package com.example.tests; public class FlutterAppTest { private SHAFT.GUI.WebDriver driver; @BeforeMethod public void setup() { // Set platform and automation name (Flutter driver is automatically enabled) SHAFT.Properties.platform.set().targetPlatform(Platform.ANDROID.name()); SHAFT.Properties.mobile.set().automationName(AutomationName.FLUTTER_INTEGRATION); // Configure Appium server SHAFT.Properties.platform.set().executionAddress("localhost:4723"); // Set app path (local file) SHAFT.Properties.mobile.set().app("path/to/your/app-debug.apk"); // Initialize driver. With automationName set to FLUTTER_INTEGRATION, SHAFT // constructs a FlutterAndroidDriver/FlutterIOSDriver under the hood, so // AppiumBy's native flutter* locators can be used directly via findElement(). driver = new SHAFT.GUI.WebDriver(); } @Test public void testFlutterApp() { driver.element().click(SHAFT.GUI.Locator.flutterKey("LoginButton")); driver.element().assertThat(SHAFT.GUI.Locator.flutterText("Please Login")) .exists(); driver.element().assertThat(SHAFT.GUI.Locator.flutterType("TextField")) .exists(); } @AfterMethod public void teardown() { driver.quit(); } } ``` ### Configuration Properties You can configure Flutter testing using properties file or programmatically: #### Properties File (custom.properties) ```properties # Platform configuration targetOperatingSystem=Android # Automation name - setting this to FlutterIntegration automatically enables Flutter driver mobile_automationName=FlutterIntegration # Appium server executionAddress=localhost:4723 # App configuration mobile_app=src/test/resources/apps/my-flutter-app.apk # Optional: Device configuration mobile_deviceName=Android Emulator mobile_platformVersion=13.0 ``` #### Programmatic Configuration ```java // Platform and automation - setting automationName to FLUTTER_INTEGRATION enables Flutter driver SHAFT.Properties.platform.set().targetPlatform(Platform.ANDROID.name()); SHAFT.Properties.mobile.set().automationName(AutomationName.FLUTTER_INTEGRATION); // Appium server SHAFT.Properties.platform.set().executionAddress("localhost:4723"); // App path SHAFT.Properties.mobile.set().app("path/to/app.apk"); // Optional device settings SHAFT.Properties.mobile.set().deviceName("Android Emulator"); SHAFT.Properties.mobile.set().platformVersion("13.0"); ``` ## Locating Flutter Elements Prefer SHAFT.GUI.Locator.flutter* factories (thin wrappers over java-client AppiumBy.flutter*). Full table and sample: [Flutter testing guide](/docs/testing/flutter). ### Common Flutter Locator Strategies ```java By byKey = SHAFT.GUI.Locator.flutterKey("myButton"); By byText = SHAFT.GUI.Locator.flutterText("Submit"); By byType = SHAFT.GUI.Locator.flutterType("TextField"); By bySemantics = SHAFT.GUI.Locator.flutterSemanticsLabel("Login Button"); By byPartial = SHAFT.GUI.Locator.flutterTextContaining("Sub"); ``` ### Working with Located Elements Once you have a Flutter locator from SHAFT.GUI.Locator.flutter*, use SHAFT fluent element actions (`type`, `click`, `assertThat`) rather than raw findElement-only checks. See the [Flutter testing guide](/docs/testing/flutter) for a full sample. ### Using with SHAFT's Fluent API ```java By username = SHAFT.GUI.Locator.flutterKey("username_text_field"); By loginButton = SHAFT.GUI.Locator.flutterKey("LoginButton"); By homeTitle = SHAFT.GUI.Locator.flutterText("Samples List"); driver.element() .type(username, "admin") .and().click(loginButton) .and().assertThat(homeTitle).text().contains("Samples"); ``` ## Working with Flutter Widgets ### Text Input ```java By usernameField = SHAFT.GUI.Locator.flutterKey("username_text_field"); driver.element().type(usernameField, "testuser"); ``` ### Button Clicks ```java By loginButton = SHAFT.GUI.Locator.flutterKey("LoginButton"); driver.element().click(loginButton); ``` ### Scrolling ```java driver.element().touch().swipeElementIntoView( SHAFT.GUI.Locator.flutterKey("targetWidget"), "DOWN" ); ``` ### Assertions ```java // Text assertion driver.element() .assertThat(SHAFT.GUI.Locator.accessibilityId("statusMessage")) .text() .isEqualTo("Success"); // Visibility assertion driver.element() .assertThat(SHAFT.GUI.Locator.accessibilityId("errorDialog")) .exists(); ``` ## Cloud Execution SHAFT Engine's Flutter integration works with these cloud providers: ### BrowserStack This direct SHAFT Appium path requires only `shaft-engine`. Add `shaft-browserstack` only when the BrowserStack Java SDK must consume `browserstack.yml` for SDK interception or orchestration. ```java SHAFT.Properties.platform.set().executionAddress("browserstack"); SHAFT.Properties.browserStack.set().platformVersion("13.0"); SHAFT.Properties.browserStack.set().deviceName("Google Pixel 7"); SHAFT.Properties.browserStack.set().appRelativeFilePath("path/to/app.apk"); SHAFT.Properties.mobile.set().automationName(AutomationName.FLUTTER_INTEGRATION); ``` ### LambdaTest ```java SHAFT.Properties.platform.set().executionAddress("lambdatest"); SHAFT.Properties.lambdaTest.set().platformVersion("13.0"); SHAFT.Properties.lambdaTest.set().deviceName("Galaxy S21"); SHAFT.Properties.mobile.set().automationName(AutomationName.FLUTTER_INTEGRATION); ``` ## Troubleshooting ### Common Issues 1. **"Could not find Flutter driver"** - Ensure the Flutter driver is installed: `appium driver install --source npm appium-flutter-integration-driver` - Verify with: `appium driver list --installed` 2. **"Flutter driver extension not found"** - Make sure your app includes `enableFlutterDriverExtension()` in `main.dart` - App must be built in debug or profile mode, not release mode 3. **"Cannot find element"** - Ensure Flutter widgets have proper keys or accessibility labels - Use Flutter's `Key` widget: `Key('myButton')` - Add semantics: `Semantics(label: 'Submit Button', child: MyWidget())` 4. **Session creation fails** - Check that Appium server is running: `appium` - Verify the server address matches your configuration - Ensure the app path is correct and accessible ### Debug Mode Enable debug logging to troubleshoot issues: ```properties # In src/main/resources/properties/log4j2.properties logger.app.level=DEBUG ``` `logger.app.level` is a Log4j2 file setting. Keep it in the Log4j2 properties file; it is not a typed `SHAFT.Properties` setting. ## Best Practices 1. **Use Meaningful Keys**: Always add keys to important Flutter widgets for easier element identification: ```dart ElevatedButton( key: Key('submitButton'), onPressed: () {}, child: Text('Submit'), ) ``` 2. **Add Semantics**: Use semantics for better accessibility and test automation: ```dart Semantics( label: 'User Login Form', child: Form(...) ) ``` 3. **Wait for Elements**: SHAFT automatically handles waits, but you can configure timeout: ```java SHAFT.Properties.timeouts.set().defaultElementIdentificationTimeout(30); ``` 4. **Use Fluent API**: SHAFT's fluent API makes tests more readable: ```java driver.element() .type(usernameField, "user") .and().type(passwordField, "pass") .and().click(loginButton) .and().assertThat(dashboard).exists(); ``` 5. **Clean Up Resources**: Always quit the driver in teardown: ```java @AfterMethod(alwaysRun = true) public void teardown() { driver.quit(); } ``` ## Example Test Suite Complete example with multiple tests using native `AppiumBy` `flutter*` locators: ```java package com.example.tests; public class FlutterAppTestSuite { private static SHAFT.GUI.WebDriver driver; @BeforeClass public void setupClass() { // Configure Flutter testing (automationName automatically enables Flutter driver) SHAFT.Properties.platform.set().targetPlatform(Platform.ANDROID.name()); SHAFT.Properties.mobile.set().automationName(AutomationName.FLUTTER_INTEGRATION); SHAFT.Properties.platform.set().executionAddress("localhost:4723"); SHAFT.Properties.mobile.set().app("src/test/resources/apps/flutter-demo.apk"); } @BeforeMethod public void setup() { driver = new SHAFT.GUI.WebDriver(); } @Test(description = "Verify successful login with valid credentials") public void testValidLogin() { // Find and interact with Flutter widgets using native flutter* locators driver.element() .type(AppiumBy.flutterKey("usernameField"), "testuser") .type(AppiumBy.flutterKey("passwordField"), "testpass") .click(AppiumBy.flutterKey("loginButton")); driver.element().assertThat(AppiumBy.flutterText("Dashboard")) .exists(); } @Test(description = "Verify error message with invalid credentials") public void testInvalidLogin() { driver.element() .type(AppiumBy.flutterKey("usernameField"), "wronguser") .type(AppiumBy.flutterKey("passwordField"), "wrongpass") .click(AppiumBy.flutterKey("loginButton")); driver.element().assertThat(AppiumBy.flutterText("Invalid credentials")) .exists(); } @Test(description = "Verify counter increment functionality") public void testCounterIncrement() { // Find the increment button by its semantics label (Flutter's Tooltip // widget registers its message as a semantics label) driver.element().click(AppiumBy.flutterSemanticsLabel("Increment")); driver.element().assertThat(AppiumBy.flutterText("1")) .exists(); } @AfterMethod(alwaysRun = true) public void teardown() { if (driver != null) { driver.quit(); } } } ``` ## Mobile device, gesture, context, file, log and biometric actions `driver.mobile()` groups native mobile controls: | Accessor | Methods | | --- | --- | | `device()` | `battery()`, `clipboard()`, `keyboard()`, `isLocked()`, `lock()`, `lock(Duration)`, `unlock()`, `orientation()` and `orientation(ScreenOrientation)`. | | `gestures()` | `swipe()` (swipe and scroll), `drag()`, and `zoom()` (pinch zoom). | | `context()` | `handles()` lists native and web contexts, `nativeApp()` switches to the native context, `webView()` switches to the first web view. | | `files()` | `push(String devicePath, byte[] content)`, `pushText(String devicePath, String content)`, `pushFrom(String devicePath, Path localSource)`, `pull(String devicePath)`, `pullText(String devicePath)`, `pullTo(String devicePath, Path localTarget)`, and `pullFolder(String devicePath)` (ZIP bytes). | | `logs()` | `messages()` returns captured device-log messages and `errors()` returns listener or provider errors. | | `biometrics()` | `fingerprint()` for Android emulators and `touchId()` for the iOS Simulator. | ```java driver.mobile().device().orientation(ScreenOrientation.LANDSCAPE); driver.mobile().files().pushText("/sdcard/Download/note.txt", "hello"); String note = driver.mobile().files().pullText("/sdcard/Download/note.txt"); driver.mobile().context().webView(); ``` ### Mobile value assertions `driver.assertThat().mobileValues()` and `driver.verifyThat().mobileValues()` read a live mobile value and return a validation builder for it: | Method | Value checked | | --- | --- | | `currentContextValue()`, `contextCountValue()` | Current context name and number of contexts. | | `appInstalledValue(String appId)`, `appStateValue(String appId)` | Whether an app is installed and its lifecycle state. | | `deviceLockedValue()`, `deviceOrientationValue()`, `deviceTimeValue()`, `batteryValue()` | Device lock state, orientation, time and battery reading. | | `logMessageCountValue()`, `logErrorCountValue()` | Number of captured device-log messages and errors. | | `performanceSampleCountValue()` | Number of retained performance samples. | | `recordingInProgressValue()`, `retainedRecordingAvailableValue()`, `retainedRecordingSizeValue()` | Screen-recording state and retained recording. | | `evidenceArtifactCountValue(MobileEvidenceBundle)`, `evidenceOmissionCountValue(MobileEvidenceBundle)` | Artifact and omission counts in a mobile evidence bundle. | ```java driver.assertThat().mobileValues().deviceLockedValue().isFalse(); ``` ## Related - [Testing overview](/docs/start/overview) - [Features](/docs/features/modules) - [Appium Flutter Driver documentation](https://github.com/AppiumTestDistribution/appium-flutter-integration-driver) - [Flutter testing guide](https://flutter.dev/docs/testing) - [Appium Java client](https://github.com/appium/java-client) for native `AppiumBy.flutter*` locators - [SHAFT Engine issues](https://github.com/ShaftHQ/SHAFT_ENGINE/issues) - [SHAFT community Slack](https://join.slack.com/t/shaft-engine/shared_invite/zt-oii5i2gg-0ZGnih_Y34NjK7QqDn01Dw) --- # UI and API contract replay Canonical HTML: https://shafthq.github.io/docs/testing/contracts Markdown: https://shafthq.github.io/md/testing/contracts.md # UI and API contract replay Use contract replay when a journey crosses the browser and backend and you want repeatable evidence without hand-writing every mock. SHAFT records matching browser traffic and `SHAFT.API` calls into a deterministic JSON contract, masks sensitive fields, and can later replay or validate the same exchanges. Contract mode is opt-in. Existing browser interception, API calls, and reporting behave the same until a test starts recording, replay, assert, or verify mode. ## Prerequisites - A working [web test](/docs/testing/web) (`SHAFT.GUI.WebDriver`) or [API test](/docs/testing/api) (`SHAFT.API`) for the journey you want to keep. - A writable fixtures path for the contract JSON, for example `src/test/resources/contracts/`. - Agreement that the target environment allows its traffic to be kept as test evidence. See [Redaction and normalization](#redaction-and-normalization). ## Record a contract Start recording before the browser navigation or API calls that should be kept. Use URL filters to avoid analytics, static assets, or unrelated backend calls. ```java title="RecordCheckoutContract.java" public class CheckoutContractTest { @Test public void recordCheckoutContract() { SHAFT.GUI.WebDriver driver = new SHAFT.GUI.WebDriver(); SHAFT.API api = new SHAFT.API("https://shop.example.test"); driver.browser().startContractRecording( "src/test/resources/contracts/checkout.json", "/api/"); driver.browser().navigateToURL("https://shop.example.test/checkout"); api.get("/api/cart"); SHAFT.Contracts.stopRecording(); driver.quit(); } } ``` For API-only flows, use the facade directly: ```java title="RecordApiContract.java" SHAFT.Contracts.startRecording( "src/test/resources/contracts/catalog.json", "/api/catalog"); new SHAFT.API("https://shop.example.test") .get("/api/catalog"); SHAFT.Contracts.stopRecording(); ``` ## Replay browser responses Replay mode loads the recorded HTTP interactions as browser network mocks. Matching uses the request method, path, query parameters, and normalized request body when a body was captured. ```java title="ReplayBrowserContract.java" SHAFT.GUI.WebDriver driver = new SHAFT.GUI.WebDriver(); driver.browser().replayContract("src/test/resources/contracts/checkout.json"); driver.browser().navigateToURL("https://shop.example.test/checkout"); driver.assertThat(By.id("order-summary")).exists(); driver.quit(); ``` ## Assert or verify live traffic Use assert mode when a contract mismatch should fail the test immediately. Use verify mode when the test should continue and collect all mismatches as report evidence. ```java title="AssertLiveContract.java" driver.browser().assertContract( "src/test/resources/contracts/checkout.json", "/api/"); driver.browser().navigateToURL("https://shop.example.test/checkout"); new SHAFT.API("https://shop.example.test").get("/api/cart"); SHAFT.Contracts.stopValidation(); ``` ```java title="VerifyLiveContract.java" driver.browser().verifyContract( "src/test/resources/contracts/checkout.json", "/api/"); driver.browser().navigateToURL("https://shop.example.test/checkout"); SHAFT.Contracts.stopValidation(); ``` When live traffic differs from the contract, SHAFT attaches a readable `HTTP Contract Diff - METHOD path` artifact under . If the request belongs to a recorded trace action, the contract entry includes the trace action id so the mismatch can be correlated with `shaft-trace.json`. ## Redaction and normalization Contracts redact sensitive request and response headers, query parameters, and JSON body fields before writing to disk. Volatile values such as timestamps, request ids, trace ids, UUIDs, and configured volatile keys are normalized so stable behavior produces stable JSON. ```java title="ContractPrivacy.java" SHAFT.Properties.api.set() .contractSensitiveKeys("authorization,cookie,password,token,api-key") .contractVolatileKeys("requestId,traceId,timestamp,etag"); ``` Keep recorded contracts in test resources or a dedicated fixtures directory. Do not commit production secrets, session-specific personal data, or contracts captured from environments that do not allow test evidence retention. ## Verify - After a recording run, the contract JSON exists at the path you passed. Sensitive headers, query parameters, and body fields are redacted, and volatile values are normalized. - A replay run passes with the recorded responses served as browser network mocks. - In assert or verify mode, change the live behavior (or edit one recorded value) and rerun. The report attaches an `HTTP Contract Diff - METHOD path` artifact. Assert mode fails immediately, and verify mode collects every mismatch. ## Related - [Web testing](/docs/testing/web) - [API testing](/docs/testing/api) - [Network Mocking](/docs/reference/actions/GUI/Infrastructure_Network_And_Visual#network-mocking) - [Browser Actions](/docs/reference/actions/GUI/Browser_Actions) --- # Web testing Canonical HTML: https://shafthq.github.io/docs/testing/web Markdown: https://shafthq.github.io/md/testing/web.md # Web testing Start here when you want one browser test that opens a page, performs an action, asserts a result, and leaves screenshot/report evidence. ## Prerequisites - A SHAFT Maven project. [Install SHAFT](/docs/start/installation) walks through generating one, and lists the required Java version under [Requirements](/docs/start/installation#requirements). - The browser named by `targetBrowserName` installed on the machine, or the generated headless defaults. See [web configuration](/docs/reference/configuration/webConfig). - Network access to the page under test (the sample opens `duckduckgo.com`). ```java public class SearchTest { private SHAFT.GUI.WebDriver driver; @BeforeMethod public void openBrowser() { driver = new SHAFT.GUI.WebDriver(); } @Test public void search() { driver.browser().navigateToURL("https://duckduckgo.com/") .and().element().type(By.name("q"), "SHAFT Engine") .and().assertThat().title().contains("DuckDuckGo"); } @AfterMethod(alwaysRun = true) public void closeBrowser() { driver.quit(); } } ``` ```mermaid sequenceDiagram participant Test participant SHAFT participant Browser participant Allure Test->>SHAFT: navigate / act / assert SHAFT->>Browser: synchronized WebDriver commands Browser-->>SHAFT: page and element state SHAFT->>Allure: steps, logs, screenshots, result ``` Use the [GUI actions reference](/docs/reference/actions/GUI/Browser_Actions) for locators, browser actions, elements, waits, validations, accessibility, and network mocking. For browser traffic that should be captured and replayed later, see [UI and API contract replay](/docs/testing/contracts). ## Run and inspect evidence Run the generated or copied test from the project root: The report includes browser steps, screenshots, logs, and assertion results under . If the browser never opens, check Java/Maven first, then browser installation, then `targetBrowserName` and `headlessExecution`. ## Verify - Maven reports the test as run and passed. A passing build that reports `Total: 0` tests means the test runner provider is missing. See [Install SHAFT](/docs/start/installation#adding-shaft-to-an-existing-maven-project). - The Allure report shows the navigate, type, and title-assertion steps with their logs. - To prove the assertion is live, change the expected title to a wrong value and rerun. The test fails, and with the default `FAILURE_ONLY` [evidence level](/docs/reference/reporting#evidence-level-profiles) the report attaches a screenshot and page source for the failure. ## Locator strategy Stop at the first unique match. Generated and repository web code uses this ladder: 1. A unique, author-written `id` through the SHAFT locator builder: `SHAFT.GUI.Locator.hasAnyTagName().hasId("checkout-submit").build()`. Never a framework-recycled id such as `:r1:`, `mat-input-3`, `cdk-overlay-0`, `ember1234`, `j_idt42`, `ctl00_...`, or `sc-bdVaJa`. 2. The same builder's ARIA role, chained until unique: `SHAFT.GUI.Locator.hasRole(Role.BUTTON).hasNormalizedText("Create Account").build()`. 3. Native relative `By.xpath(...)` only when the element has neither an eligible id nor a usable role. Never generate `SHAFT.GUI.Locator.xpath(...)`, the raw `SHAFT.GUI.Locator.id/name/cssSelector/className/tagName(...)` factories, or a Smart Locator (`inputField` / `clickableField`) in generated or repository code. Smart Locators stay legitimate only for a human's throwaway exploration snippet. See [Locators and self-healing](/docs/reference/actions/GUI/Locators_And_Self_Healing#generated-locator-policy). Keep waits and retries as evidence-backed safety nets, not as a substitute for a stable locator. Use [SHAFT Heal](/docs/agentic/heal) only after deterministic locator strategies cannot survive expected UI changes. ## Playwright backend Use `SHAFT.GUI.Playwright` when a test should run through Microsoft Playwright instead of Selenium/Appium WebDriver. Both backends implement `SHAFT.GUI.Driver`, so setup code can choose the backend per test class. ```java private SHAFT.GUI.Driver driver; @BeforeMethod public void openBrowser() { driver = new SHAFT.GUI.Playwright(); } ``` See the [Playwright Backend](/docs/reference/actions/GUI/Playwright_Backend) reference for configuration, native Playwright access, tracing, and the WebDriver-to-Playwright mapping tree. ## Click and type by control kind `element().click(...)` and `element().type(...)` keep the same fluent API, but SHAFT now classifies the target control and picks a safer recipe automatically (Selenium, Playwright, Appium mobile, and Windows/UIA desktop). You do not opt in. Public calls stay unchanged. Typical routing: - Text-like inputs and textareas: clear strategy (see `clearBeforeTypingMode`) then type; Playwright prefers `fill`, and uses sequential keys when masks/debounce need key events - Checkbox / radio / switch: click or toggle, never type - Select / listbox / combobox: option or expand → filter → confirm — not blind `sendKeys` of the label alone - File inputs: set files via the file path API - Overlay / interception: scroll and retry before optional JS click (`clickUsingJavascriptWhenWebDriverClickFails`) - Mobile: focus → `sendKeys` / `mobile: type` / hide-keyboard helpers; Compose/Flutter need focus (and tags/semantics) before type - Windows desktop (WinAppDriver): UIA control-type map for Edit, Button, CheckBox, ComboBox, and related kinds Flags such as `attemptToClickBeforeTyping`, `clearBeforeTypingMode`, and `clickUsingJavascriptWhenWebDriverClickFails` still apply when you need to tune the defaults. Maintainer detail lives in `com.shaft.gui.element.internal.interaction` (`ElementClassifier`, `ClickStrategies`, `TypeStrategies`) from epic [#5732](https://github.com/ShaftHQ/SHAFT_ENGINE/issues/5732). ## Related - [Browser Actions](/docs/reference/actions/GUI/Browser_Actions) - [Element Actions](/docs/reference/actions/GUI/Element_Actions) - [Playwright Backend](/docs/reference/actions/GUI/Playwright_Backend) - [UI and API contract replay](/docs/testing/contracts) - [Validations](/docs/reference/actions/Validations) --- # Add optional ChaosEngine skills Canonical HTML: https://shafthq.github.io/docs/agentic/chaos-engine-addons Markdown: https://shafthq.github.io/md/agentic/chaos-engine-addons.md # Add optional ChaosEngine skills A plain ChaosEngine install gives you the lean core only. Extra skill sets ship as **add-ons**. None is installed by default, and none is picked for you from your project files. You choose them with a flag, and your choice is kept on every upgrade until you remove it. ## Choose your add-ons | Add-on | Install it when | Shell flag | PowerShell switch | | --- | --- | --- | --- | | `design-skills` | You design web pages, images, motion graphics, or videos and want checked results | `--with-design-skills` | `-WithDesignSkills` | | `shaft-engine-users` | Your test project uses SHAFT and you want the SHAFT test-writing skills | `--with-shaft-engine-users` | `-WithShaftEngineUsers` | | `shaft-core-developers` | You change the SHAFT engine itself (it also adds `shaft-engine-users`) | `--with-shaft-core-developers` | `-WithShaftCoreDevelopers` | Remove an add-on with `--without- ` or `-Without `. You can pass several flags in one command. ## Install with add-ons Run the command from your project folder. On macOS or Linux, add the flags after the script URL: ```bash curl -fsSL https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/chaos-engine/install.sh | bash -s -- https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/chaos-engine/install.sh --with-shaft-engine-users --with-design-skills ``` On Windows PowerShell, the short `irm ... | iex` form cannot take switches. Use the script-block form: ```powershell & ([scriptblock]::Create((irm https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/chaos-engine/install.ps1))) -WithShaftEngineUsers -WithDesignSkills ``` Or set `CHAOS_ENGINE_ADDONS` and keep the short form. This works with every installer: ```powershell $env:CHAOS_ENGINE_ADDONS = "shaft-engine-users,design-skills"; irm https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/chaos-engine/install.ps1 | iex ``` ## Check what is installed ```bash python3 .chaos-engine/install.py addons --project . ``` Each line shows the add-on name, whether it is `installed` or `available`, its flag, and when to use it. Add `--json` for scripts. On Windows, use `py -3` instead of `python3`. Installed add-ons live in `.chaos-engine/addons/ /`. The `shaft-core-developers` add-on switches ChaosEngine to the SHAFT repository profile instead of adding files there. ## Upgrade and remove - Run the same install command without flags to upgrade. Your add-ons stay. - Pass `--without- ` (or `-Without `) to remove one. - A misspelled name stops the install and lists the valid names. - ChaosEngine refuses to remove an add-on that another chosen add-on needs. For example, `shaft-engine-users` stays while `shaft-core-developers` is installed. :::note Change for SHAFT projects Earlier versions picked the SHAFT contributor profile automatically when a project's `pom.xml` mentioned SHAFT. That also happened in projects that only use SHAFT. Now nothing is picked automatically: the installer prints a tip, and you choose `shaft-engine-users` or `shaft-core-developers`. Installs that already use the contributor profile keep it. ::: ## What the design add-on gives your agent The `design-skills` add-on is a short router plus 20 focused cards. Your agent loads one card for the step it is on, and each card ends with checks it must pass. The checks run in a script, `design_qc.py`, that uses no AI model. A check whose tool is missing reports `skipped`, never `pass`. | Area | Cards | | --- | --- | | Planning and brand | brief and storyboard, brand system | | Web and type | visual direction, interface audit, typography and layout, color and contrast | | Images | posters, link cards, thumbnails, and diagrams | | Motion | motion principles, HTML motion graphics, technical animation | | Video | screen capture, edit assembly, cutting and pacing, color grading | | Audio | mix and loudness, noise removal, voice-over | | Finishing | captions, restoration and upscaling, delivery QC | Examples of objective checks: WCAG contrast ratios, caption line length and reading speed, −16 LUFS web loudness with a true peak of −1 dBTP or lower, BT.709 color tags and legal levels, flash rate, and encode settings. The cards are written for ChaosEngine and credit their sources. Design rules from openly licensed projects are rewritten with attribution, and every source is pinned to a commit in the add-on's `INVENTORY.md`. Web work still follows ChaosEngine's UI delivery rules: tests that fail first, measured layout, and screenshots at five viewport sizes in every theme. ## Related - [Install and operate ChaosEngine](/docs/agentic/chaos-engine) - [ChaosEngine install guide](/docs/agentic/chaos-engine-install-guide) - [Install SHAFT agent skills](/docs/agentic/skills) - [Agent tooling runbook](/docs/maintainers/agent-tooling) --- # ChaosEngine install guide Canonical HTML: https://shafthq.github.io/docs/agentic/chaos-engine-install-guide Markdown: https://shafthq.github.io/md/agentic/chaos-engine-install-guide.md # ChaosEngine install guide Each clip is a real install on a clean Linux account (ChaosEngine `main` `3a02d90`), re-timed with labelled speed-ups. With reduced motion enabled, the page shows a still frame and the text instead of the loop. Prefer the full narrated walkthrough on [Install and operate ChaosEngine](/docs/agentic/chaos-engine). Add-on flags are on [Add optional ChaosEngine skills](/docs/agentic/chaos-engine-addons). ## Step 1 — Run the one-liner in your project From the project folder (it needs at least one commit), run: ```bash url="https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/chaos-engine/install.sh" curl -fsSL "$url" | bash -s -- "$url" ``` Six steps: resolve, download, install core, provision tools, verify, activate clients. ## Step 2 — Check health with doctor ```bash python3 .chaos-engine/install.py doctor --project . ``` Anything flagged comes with the exact repair --component command. ## Step 3 — Add optional add-ons ```bash curl -fsSL "$url" | bash -s -- "$url" --with-design-skills --with-shaft-engine-users ``` Never installed by default; the selection is remembered on upgrade. ## Step 4 — See what is installed ```bash python3 .chaos-engine/install.py addons --project . ``` Files land under .chaos-engine/addons/<name>/ . ## Step 5 — Remove one ```bash curl -fsSL "$url" | bash -s -- "$url" --without-shaft-engine-users ``` Only design-skills remains. ## Related - [Install and operate ChaosEngine](/docs/agentic/chaos-engine) - [Add optional ChaosEngine skills](/docs/agentic/chaos-engine-addons) - [ChaosEngine install video release](https://github.com/ShaftHQ/SHAFT_ENGINE/releases/tag/chaosengine-install-video-20261004) --- # Connect shaft-mcp Canonical HTML: https://shafthq.github.io/docs/agentic/mcp Markdown: https://shafthq.github.io/md/agentic/mcp.md # Connect shaft-mcp `shaft-mcp` is the Model Context Protocol server for SHAFT browser, mobile, Capture, coding-partner planning, Doctor, and healer automation. Web GUI tools default to SHAFT WebDriver; pass `engine=playwright` to `driver_initialize` when the project already uses `SHAFT.GUI.Playwright` or the user explicitly asks for Playwright code, and the same unified `element_*`, `browser_*`, `capture_*`, `doctor_*`, and `healer_*` tool names keep dispatching to whichever engine (WebDriver, Playwright, or mobile) is active in the session instead of requiring separate `playwright_*` tool names. The local Capture/Doctor CLI commands are listed below. It is published to Maven Central as the thin `io.github.shafthq:shaft-mcp` JAR, built in the SHAFT reactor, and depends on the canonical `shaft-engine` module. It does not add any dependency to ordinary `shaft-engine` consumers. :::warning Breaking change: 89-tool surface (was 164) The tool architecture sweep ([ShaftHQ/SHAFT_ENGINE#3866](https://github.com/ShaftHQ/SHAFT_ENGINE/issues/3866)) trimmed `shaft-mcp` from 164 to **89 tools**, with no back-compat shim or deprecation warning: calling a deleted tool name now fails immediately. - `natural_act` and `mobile_natural_act` are deleted outright. The deterministic `SHAFT.GUI.WebDriver.act(...)` Java API these tools wrapped is unaffected; see [Natural Language Actions](/docs/reference/actions/GUI/Natural_Language_Actions). - Every `playwright_*` tool name is removed. `driver_initialize` now takes an optional `engine` (`web` default | `playwright` | `mobile_native` | `mobile_web`), and unified tools such as `element_click`, `element_type`, `browser_get_page_dom`, `capture_start`, `doctor_analyze_failed_allure`, and `healer_run_failed_test` dispatch to whichever engine is active (`doctor_*`/`healer_*` also take an explicit `backend` parameter). - Granular mobile tools are absorbed the same way: `element_click`, `element_type`, and `element_clear` dispatch to an active mobile session (replacing `mobile_tap`, `mobile_type`, `mobile_clear`, `mobile_double_tap`, `mobile_long_tap`); `mobile_swipe` replaces the four offset/coordinate/element/text swipe tools; `capture_start`/`capture_stop`/ `capture_status`/`capture_code_blocks`/`capture_record_at_target_code_blocks` now dispatch mobile and Playwright recording (replacing `mobile_record_start`/`mobile_record_stop`/`mobile_record_status`/ `mobile_recording_code_blocks`/`mobile_record_at_target_code_blocks`/ `mobile_replay_recording` and their `playwright_*` twins); `capture_api_start` absorbs the standalone mobile API proxy from `mobile_api_record_start`; `mobile_inspector_record_start` absorbs `mobile_inspector_record_prepare`, and `mobile_inspector_record_status` absorbs `mobile_inspector_record_control`. `browser_delete_cookies` absorbs `browser_delete_cookie`/ `browser_delete_all_cookies`, and `element_upload_file` absorbs `element_drop_file_to_upload`. - New: `capture_step_delete`/`capture_step_reorder` edit a recorded step by its stable `stepId` on the active Playwright or mobile recording. `driver_initialize` gained an optional nested `mobileOptions` request carrying every former `mobile_initialize_native`/ `mobile_initialize_web_emulation` parameter, and `capture_start`'s optional `codegenOptions` absorbs the former `capture_start_codegen` tool. Full list of removed tool names `natural_act`, `mobile_natural_act`, `element_click_js`, `element_double_click`, `element_click_and_hold`, `element_append_text`, `element_set_value_js`, `element_drag_and_drop_by_offset`, `element_drop_file_to_upload`, `browser_maximize_window`, `browser_fullscreen_window`, `browser_delete_all_cookies`, `browser_delete_cookie`, `browser_network_request`, `capture_start_codegen`, `mobile_clear`, `mobile_tap`, `mobile_type`, `mobile_double_tap`, `mobile_long_tap`, `mobile_swipe_by_offset`, `mobile_swipe_coordinates`, `mobile_swipe_element_into_view`, `mobile_swipe_text_into_view`, `mobile_initialize_native`, `mobile_initialize_web_emulation`, `mobile_record_start`, `mobile_record_stop`, `mobile_record_status`, `mobile_recording_code_blocks`, `mobile_record_at_target_code_blocks`, `mobile_replay_recording`, `mobile_api_record_start`, `mobile_api_record_status`, `mobile_api_record_stop`, `mobile_api_record_transactions`, `mobile_inspector_record_prepare`, `mobile_inspector_record_control`, `mobile_step_delete`, `mobile_step_reorder`, and all 34 `playwright_*` tools (`playwright_initialize`, `playwright_quit`, `playwright_browser_navigate`, `playwright_browser_navigate_back`, `playwright_browser_navigate_forward`, `playwright_browser_new_window`, `playwright_browser_refresh`, `playwright_browser_set_window_size`, `playwright_browser_get_current_url`, `playwright_browser_get_title`, `playwright_browser_get_page_dom`, `playwright_browser_take_screenshot`, `playwright_browser_storage_state_save`, `playwright_browser_storage_state_load`, `playwright_element_click`, `playwright_element_click_js`, `playwright_element_double_click`, `playwright_element_type`, `playwright_element_clear`, `playwright_element_append_text`, `playwright_element_set_value_js`, `playwright_element_hover`, `playwright_element_drag_and_drop`, `playwright_element_upload_file`, `playwright_element_is_displayed`, `playwright_element_is_enabled`, `playwright_record_start`, `playwright_record_stop`, `playwright_record_status`, `playwright_recording_code_blocks`, `playwright_replay_recording`, `playwright_capture_code_blocks`, `playwright_capture_generate_replay`, `playwright_doctor_analyze_failed_allure`, `playwright_doctor_suggest_fix`, and `playwright_healer_run_failed_test`. ::: ## Count locator matches `element_count` returns how many elements a locator matches on the live page, using the active engine. It's read-only: ```json {"tool": "element_count", "arguments": {"locatorStrategy": "css", "locatorValue": "button.submit"}} ``` ## Applications Choose your MCP client and copy its command. The page detects your operating system after it loads and hides desktop applications that are unavailable there. Codex CLI / IDE (windows): `powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/scripts/mcp/install-shaft-agentic-tools.ps1 | iex; Install-ShaftMcp -Client codex --install-shaft-skills"` Codex CLI / IDE (macos): `curl -fsSL https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/scripts/mcp/install-shaft-agentic-tools.sh | sh -s -- --codex --install-shaft-skills` Codex CLI / IDE (linux): `curl -fsSL https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/scripts/mcp/install-shaft-agentic-tools.sh | sh -s -- --codex --install-shaft-skills` Claude Code (windows): `powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/scripts/mcp/install-shaft-agentic-tools.ps1 | iex; Install-ShaftMcp -Client claude --install-shaft-skills"` Claude Code (macos): `curl -fsSL https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/scripts/mcp/install-shaft-agentic-tools.sh | sh -s -- --claude --install-shaft-skills` Claude Code (linux): `curl -fsSL https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/scripts/mcp/install-shaft-agentic-tools.sh | sh -s -- --claude --install-shaft-skills` Claude Desktop (windows): `powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/scripts/mcp/install-shaft-agentic-tools.ps1 | iex; Install-ShaftMcp -Client claude-desktop --install-shaft-skills"` Claude Desktop (macos): `curl -fsSL https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/scripts/mcp/install-shaft-agentic-tools.sh | sh -s -- --claude-desktop --install-shaft-skills` GitHub Copilot CLI (windows): `powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/scripts/mcp/install-shaft-agentic-tools.ps1 | iex; Install-ShaftMcp -Client copilot --install-shaft-skills"` GitHub Copilot CLI (macos): `curl -fsSL https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/scripts/mcp/install-shaft-agentic-tools.sh | sh -s -- --copilot --install-shaft-skills` GitHub Copilot CLI (linux): `curl -fsSL https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/scripts/mcp/install-shaft-agentic-tools.sh | sh -s -- --copilot --install-shaft-skills` GitHub Copilot in IntelliJ IDEA (windows): `powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/scripts/mcp/install-shaft-agentic-tools.ps1 | iex; Install-ShaftMcp -Client copilot-intellij --install-shaft-skills"` GitHub Copilot in IntelliJ IDEA (macos): `curl -fsSL https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/scripts/mcp/install-shaft-agentic-tools.sh | sh -s -- --copilot-intellij --install-shaft-skills` GitHub Copilot in IntelliJ IDEA (linux): `curl -fsSL https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/scripts/mcp/install-shaft-agentic-tools.sh | sh -s -- --copilot-intellij --install-shaft-skills` Grok CLI (windows): `powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/scripts/mcp/install-shaft-agentic-tools.ps1 | iex; Install-ShaftMcp -Client grok --install-shaft-skills"` Grok CLI (macos): `curl -fsSL https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/scripts/mcp/install-shaft-agentic-tools.sh | sh -s -- --grok --install-shaft-skills` Grok CLI (linux): `curl -fsSL https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/scripts/mcp/install-shaft-agentic-tools.sh | sh -s -- --grok --install-shaft-skills` SHAFT IntelliJ IDEA plugin (windows): `powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/scripts/mcp/install-shaft-agentic-tools.ps1 | iex; Install-ShaftMcp -Client intellij-plugin --install-shaft-skills"` SHAFT IntelliJ IDEA plugin (macos): `curl -fsSL https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/scripts/mcp/install-shaft-agentic-tools.sh | sh -s -- --intellij-plugin --install-shaft-skills` SHAFT IntelliJ IDEA plugin (linux): `curl -fsSL https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/scripts/mcp/install-shaft-agentic-tools.sh | sh -s -- --intellij-plugin --install-shaft-skills` The command downloads a repository-hosted bootstrap script. The bootstrapper uses Python 3.9+ when available, or installs a checksum-verified portable Python runtime in the per-user SHAFT cache when Python is missing. The shared Python installer prints the SHAFT Agentic Tools banner, shows the selected client, resolves `io.github.shafthq:shaft-mcp:LATEST` from Maven Central without requiring a local project, verifies checksums, shows progress for long downloads, downloads the thin JAR to versioned per-user application data, resolves its runtime dependencies into the **local Maven repository** (`~/.m2/repository`, honoring a `localRepository` configured in `~/.m2/settings.xml` or a `SHAFT_MCP_MAVEN_LOCAL_REPOSITORY` override) so future SHAFT projects reuse the same artifacts, writes a Java argfile, verifies it over stdio, and updates the selected user configuration without creating a second `shaft-mcp` entry. Everything already present with a matching checksum is skipped and reported — a reinstall only downloads what is actually new (Java, the JAR, and each dependency log their skips, and a summary reports how many dependencies were downloaded versus already up to date). Run the same command later to install a newer release. If you run the script without a target, it prompts you to choose a supported client. After MCP setup succeeds, the installer can also add SHAFT's agent skills and their generated tool catalogs. See [Install SHAFT agent skills](/docs/agentic/skills) for attended and unattended behavior, client-native destinations, flags, alternative installation routes, and verification. The resulting local stdio process lets SHAFT launch the browser on your desktop so you can see and interact with the automation session. Do not use the Docker image for interactive local browser work: its browser runs inside the container and is not displayed on your desktop. Then ask your client: > Use SHAFT to open `https://example.com`, verify the page title, and summarize > the generated test evidence. The model provider remains owned by the client. `shaft-mcp` does not request, store, or proxy the client's model credentials. For an IDE-native experience in Java projects, start with the [IntelliJ IDEA plugin](/docs/agentic/intellij). The plugin is the cohesive front door for Assistant, Coding Partner, Recorder, Doctor, Healer, Inspector, Projects, and Guide search, while still calling the same MCP tools and keeping engine, local agent, and provider behavior outside plugin code. It does not run installer scripts inside the IDE; its first-run setup lets you choose the target agent or SHAFT IntelliJ plugin (starting from an unselected placeholder, never a Codex default), open the matching Agentic Tools installer in IntelliJ Terminal, then verify setup and store the local stdio command automatically. ## Runtime workspace `shaft-mcp` scopes every path a tool reads or writes to one workspace root, and that root is your project whenever the client can say so: 1. An explicit `-Dshaft.mcp.workspaceRoot= ` JVM property or a `SHAFT_MCP_WORKSPACE_ROOT` environment variable always wins. The SHAFT IntelliJ IDEA plugin sets this per project automatically. 2. Otherwise the directory the MCP client launched the server from becomes the workspace. Agent clients such as Claude Code, Codex, and Copilot CLI launch stdio servers from the open project, so recordings, generated tests, and Doctor analyses land in the project you are working on. 3. The launch directory is skipped when it cannot be a real project: protected system locations, unwritable directories, and the bare user home. There the installer-provided `-Dshaft.mcp.fallbackWorkspaceRoot` (or the computed per-user application-data directory) takes over: - Windows: `%LOCALAPPDATA%\ShaftHQ\shaft-mcp\work` - macOS: `~/Library/Application Support/ShaftHQ/shaft-mcp/work` - Linux: `${XDG_DATA_HOME:-~/.local/share}/shafthq/shaft-mcp/work` During MCP runs, relative SHAFT artifact paths are resolved under the runtime root. This includes generated Allure results and reports, downloaded properties, downloads, videos, services files, dynamic object repository files, test data, Extent reports, execution summaries, and performance reports. Explicit absolute path overrides remain absolute. Use `element_click` and `element_type` with a locator strategy and locator value; these same tool names dispatch to whichever engine (WebDriver, Playwright, or mobile) is active in the session -- there is no separate `playwright_element_click`/`playwright_element_type` family. Type tools keep generated recording snippets redacted unless the recording flow explicitly includes sensitive values. ## Guide search for agents Use `shaft_guide_search` before asking an agent to write or repair SHAFT code. The tool searches the live official SHAFT guide index and returns cited guide sections, short excerpts, nearby Java examples, and rules the agent must follow while generating code. Good queries are direct and task-shaped: - `page object model locators SHAFT.GUI.Locator` - `API request builder response validation` - `CLI terminal actions files` - `mobile Appium native locators` - `Allure failed test troubleshooting` The guide search covers public docs for GUI, API, CLI, mobile, reporting, configuration, locators, Page Object Model, Capture, Doctor, Heal, and troubleshooting. Agents should use the returned URLs as source citations, copy SHAFT method names only from the guide examples, prefer `SHAFT.GUI.WebDriver` and `SHAFT.GUI.Locator.*` factory methods for GUI code, use `driver.element()` or `driver.browser()` scopes for generated web snippets, and say when the guide does not contain enough evidence for an answer. ## Project creation and upgrade Use `shaft_project_create` to create a new SHAFT Maven project from the same example templates, runner/platform choices, optional module rules, GitHub Actions workflow templates, and Dependabot template used by the guide project generator. `shaft_project_create` now accepts optional `shaftVersion`. Non-blank values override the generated ` ` directly. Blank or null values default to the latest published stable `shaft-engine` from Maven Central. Bundled examples inside the SHAFT repository remain reactor-versioned; generated project POMs are rewritten during creation. ```json {"tool": "shaft_project_create", "arguments": {"outputDirectory": "demo-web", "runner": "TestNG", "platform": "web", "shaftVersion": "10.3.0"}} ``` ```json {"tool": "shaft_project_create", "arguments": {"outputDirectory": "demo-web", "runner": "TestNG", "platform": "web", "shaftVersion": ""}} ``` Use `shaft_project_upgrade` on an existing Java/Maven project to run the modular SHAFT upgrader script. Start with `dryRun: true`; applying changes requires `dryRun: false` and `approve: true`. Use `shaft_project_init_agents` to scaffold SHAFT agent/skill bridges into an existing test repository for a coding-agent loop. Pass `loop: "claude"` to generate a `.claude/skills/ /SKILL.md` bridge file for every bundled SHAFT skill plus a root `SHAFT-AGENTS.md` guidance file, or `loop: "codex"` for the same layout under `.codex/skills/`. Every bridge is generated at call time from the bundled `shaft-skills/ /SKILL.md` sources shipped inside the `shaft-mcp` jar, so the generated set always tracks the shaft-mcp build instead of a hand-duplicated copy. Existing files are left untouched unless `overwrite: true`; a skipped file is reported as a warning instead of failing the call, since `targetDirectory` is expected to be an existing repository that may already carry its own agent configuration. `loop: "grok"` writes the `AGENTS.md` managed block and uses `.agents/skills/` (Grok already scans that tree; SHAFT does not create a `.grok/skills` policy copy). `opencode` uses the same layout under `.opencode/skills/`, and `vscode` writes `.github/instructions/ .instructions.md` files instead. Skills marked `distribution: full` in their frontmatter (such as the `act-as-shaft-dev` methodology skill) are scaffolded as their complete SKILL.md text rather than a thin bridge, so a project with no prior agent instructions gets the full ways-of-working document. ```json {"tool": "shaft_project_init_agents", "arguments": {"loop": "claude", "targetDirectory": ".", "overwrite": false}} ``` ## Planning test coverage Use `test_plan_explore` to discover testable flows before recording or writing test code. It breadth-first crawls same-origin pages from an active SHAFT browser session starting at `targetUrl` (`maxDepth` clamped to 1..3, `maxPages` clamped to 1..50, default 10), inspects forms, primary navigation actions, and links with no AI calls, then writes one deterministic Markdown test plan per discovered flow into `specs/` inside the MCP workspace. Each plan lists numbered steps, candidate SHAFT locators ranked by the same pipeline `browser_open_intent` uses, seed data, and a verify section. An optional `goal` only orders the written plans by relevance and is never sent to a model provider. ```json {"tool": "test_plan_explore", "arguments": {"targetUrl": "https://example.test", "goal": "checkout happy path", "maxDepth": 2, "maxPages": 10}} ``` Or from the command line once a `shaft-cli` session is running: ```bash shaft-cli call test_plan_explore targetUrl=https://example.test goal="checkout happy path" maxDepth=2 ``` `test_plan_explore` needs an active session started with `driver_initialize`. Review the generated `specs/` files with the user before recording or generating code — the `planning-shaft-tests` skill documents the full explore, review, codegen, and verify chain (`capture_start` with `codegenOptions` for UI flows, `capture_api_start`/`capture_api_generate` for API flows, or `shaft_coding_partner_plan`/`shaft_coding_partner_diff` to insert into an existing repository), finishing with a scoped `verify_run_focused` run. ## Test automation scenario catalog Use `test_automation_scenarios` when the user asks an agent to create, refactor, or troubleshoot a SHAFT test suite and the agent needs a concrete flow to follow. The tool returns scenario ids, sample prompts, the MCP tools to call, the repository pattern to use, guardrails, and completion criteria. Common catalog entries include: | Scenario | Agent action | Flow concludes when | |---|---|---| | `api-openapi-contract-suite` | Read the Swagger/OpenAPI contract, group operations by tag/resource, write reusable request builders and response validators under `src/main/java`, then write TestNG scenarios under `src/test/java`. | Focused API tests compile, run, and validate against the configured OpenAPI contract. | | `web-pom-fluent-test` | Inspect the page with browser DOM/screenshot tools, create or extend Page Object classes, and write fluent SHAFT actions and assertions. | The headless GUI test passes and page object methods are reusable. | | `web-locator-refactor` | Replace brittle locators with a unique author-written id through `hasAnyTagName().hasId(...)`, then an ARIA role through the same builder, then native relative `By.xpath(...)` only. | Affected tests pass without `@FindBy`, PageFactory, Smart Locators, raw locator factories, or absolute XPath. | | `web-capture-to-pom` | Record with Capture, generate WebDriver or Playwright replay snippets, then use the returned deterministic POM guidance to move stable locators and actions into existing page objects. | Replay-derived code is integrated instead of pasted as a generic generated class. | | `web-playwright-pom-fluent-test` | Initialize `driver_initialize` with `engine=playwright`, use the same `browser_*`/`element_*` tools to inspect the page, then write fluent `SHAFT.GUI.Playwright` page objects and tests. | The Playwright test compiles and follows the existing project backend. | | `web-playwright-record-replay` | Record Playwright MCP actions, generate replay snippets, review the returned Capture report/review metadata, and move stable actions into Playwright page methods. | The replay code is reviewed, guarded, and inserted into Playwright classes. | | `mobile-native-appium` | Inspect contexts and accessibility trees, prefer accessibility ids through `SHAFT.GUI.Locator.*`, and create mobile page objects plus TestNG tests. | The native mobile test compiles; any unavailable device/cloud run is reported. | | `mobile-record-replay` | Record mobile actions through MCP, generate replay, ranked locator inventory, Page Object draft, and record-at-target blocks, then move them into mobile page methods. | The replay code is reviewed, guarded, and inserted into the right classes. | | `failure-trace-first-analysis` | Read `target/shaft-traces` with `trace_latest`, summarize the failed structured action, and use `doctor_analyze_trace` before opening raw Allure evidence. | The failed action, locator, exception, source context, and next MCP tool or code-change category are reported. | | `failure-doctor-analysis` | Analyze Allure results with Doctor and separate product defects, test defects, and infrastructure issues. | The first actionable failure and next validation step are reported. | | `failure-healer-loop` | Rerun a guarded failing Selenium or Playwright test, analyze fresh evidence, and propose review-only fixes. | The failure is fixed by validation or reported as blocked/product behavior. | | `report-generate-and-summarize` | Generate the report and summarize SHAFT/Allure evidence. | The report path and high-signal pass/fail summary are returned. | | `ci-local-validation` | Load validation gotchas, run affected checks first, then escalate only when needed. | The agent reports exact commands, results, and remaining risk. | Before finalizing generated Java, call `test_code_guardrails_check`. It is a lexical check, so agents still need compile or runtime validation afterwards. The tool fails code that contains `Thread.sleep(...)`, absolute XPath such as `By.xpath("/html/body/...")`, or obvious hard-coded secrets in headers, tokens, passwords, and API keys. It warns on Selenium implicit waits, direct `driver.findElement`/`findElements` calls, hard-coded headed browser setup, direct `System.getProperty()`, and PageFactory usage. Prefer SHAFT waits/actions/assertions and the [generated locator policy](/docs/reference/actions/GUI/Locators_And_Self_Healing#generated-locator-policy): unique author-written id via `hasAnyTagName().hasId(...)`, then ARIA role, then native relative `By.xpath(...)` only. Generated code should not use `SHAFT.GUI.Locator.xpath(...)`, the raw `SHAFT.GUI.Locator.id/name/cssSelector/className/tagName(...)` factories, or Smart Locators (`inputField` / `clickableField`). `test_code_guardrails_check` flags those as `SMART_LOCATOR` and `NON_ARIA_LOCATOR`. ## Playwright MCP and CLI coverage SHAFT can work beside official Playwright MCP and `playwright-cli`; it does not need to replace them. Use SHAFT tools when the final output is a Java SHAFT project, Page Object, Capture session, Doctor report, or Heal proposal. Use official Playwright as a sidecar when the agent needs Playwright-native browser exploration, then bring the evidence back into SHAFT. ```mermaid flowchart LR Need[User asks for web automation] --> Target{Final code target} Target -->|SHAFT Java| Shaft[shaft_coding_partner_plan + Capture/codegen] Target -->|Playwright exploration| Official[Official Playwright MCP or playwright-cli] Official --> Evidence[Snapshots, locators, screenshots, network, storage, console, trace] Evidence --> Shaft Shaft --> Guardrails[test_code_guardrails_check] Guardrails --> Verify[Focused Maven or Gradle validation] ``` | Official Playwright capability | SHAFT coverage path | |---|---| | MCP accessibility snapshots and browser actions | `browser_get_page_dom`, `browser_open_intent`, `browser_aria_snapshot` (whole-page or single-element YAML aria snapshot), `browser_accessibility_audit` (non-asserting axe-core WCAG audit), and `element_*` click/type tools -- the same tool names dispatch to a Playwright session once `driver_initialize` was called with `engine=playwright`. | | Screenshots, navigation, tabs, uploads, drag, hover, and form input | The unified `browser_*`/`element_*` tool family, engine-dispatched, plus Capture action recording and replay. | | Codegen options for test id, viewport, device, color scheme, geolocation, language, timezone, storage state, proxy, HAR, user agent, and user data directory | `capture_start`'s optional `codegenOptions`, `capture_codegen_features`, `capture_code_blocks`, and `capture generate --backend playwright`. `codegenOptions` also accepts `saveHarGlob` (URL glob filter) and `saveHarContent=full` (complete redacted request/response bodies instead of truncated previews). | | Storage-state save/load, network request inspection, and mock routes | `browser_storage_state_save`/`browser_storage_state_load` (same JSON on WebDriver or Playwright sessions); `browser_network_requests` for observed traffic, with an optional `id` narrowing to one transaction's full detail (not supported on the Playwright engine); `browser_route`/`browser_unroute` for mock responses. | | Playwright CLI console, trace, video, PDF, and named-session commands | Use official `playwright-cli` as delegated evidence gathering; attach paths or summaries to `shaft_coding_partner_plan`, Doctor, Capture, or the final PR evidence. | | Playwright Test Agents planner/generator/healer | Use `npx playwright init-agents --loop=codex` for Playwright Test projects; for SHAFT Java projects, map the planner output into `test_automation_scenarios`, Capture, Doctor, Heal, and reviewed Java source edits. | Do not paste Playwright TypeScript generated tests into a Java Maven project. Treat `browser_run_code_unsafe`, `playwright-cli run-code`, and `eval` as trusted-client-only steps; report the command and evidence path instead of embedding arbitrary code in SHAFT source. ## Coding partner plan Use `shaft_coding_partner_plan` before generating or repairing repository code. It is a read-only planning tool for IntelliJ, Codex, Claude, Copilot, and other MCP clients that have SHAFT skills installed. ```json { "tool": "shaft_coding_partner_plan", "arguments": { "repositoryPath": ".", "intent": "Add a login scenario and reuse the existing LoginPage", "backend": "WebDriver", "currentSourcePath": "src/test/java/pages/LoginPage.java", "selectedText": "", "artifactPaths": ["target/shaft-traces/latest"], "maxResults": 10 } } ``` The schema `1.1` response includes a working-set summary, ranked `reuseMatches`, existing locator/action summaries, a deterministic `stepPlan`, `recommendedTargetSourcePath`, `recommendedInsertionAnchor`, `missingCodeItems`, suggested MCP proof calls, a focused verification command, evidence paths, and approval warnings. The tool does not edit source. After review, use Capture record-at-target tools or the calling agent's normal patch workflow to apply only the missing code. ## Recording web API traffic Use the API capture tools to record HTTP request and response traffic during browser-based test execution. These tools extend the standard Capture lifecycle with automatic API event collection, body externalization, and sensitive header redaction. ### `capture_api_start` Start a managed Chrome or Edge browser session and begin recording API traffic alongside browser interactions. This tool requires browser support for Chrome DevTools Protocol (CDP); Firefox and Safari are not supported. **Parameters:** - `targetUrl` (string, required): Initial http, https, or file URL for the managed browser - `browser` (string, optional): Chrome or Edge; blank selects Chrome - `headless` (string, optional): Whether to launch without a visible window; blank selects repo policy (headless) - `recordNetworkActivity` (boolean, required): Whether to capture network transactions - `excludeAssetTypes` (string, optional): Comma-separated asset types to exclude - `includeOnlyDomains` (string, optional): Comma-separated domains to include; empty includes all - `excludePatterns` (string, optional): Comma-separated URL patterns to exclude - `sanitizeHeaders` (boolean, required): Whether to redact sensitive header values **Returns:** - Safe recorder status with network transaction count - Session state (`ACTIVE`, `COMPLETED`, `INCOMPLETE`) - Event count and network metrics ### `capture_api_status` Query the status of the current active API capture recording. No parameters required. **Returns:** - `state`: Recording state (`ACTIVE`, `COMPLETED`, `INCOMPLETE`) - `eventCount`: Number of captured browser interactions and API events - `readiness`: Recording readiness state - `warnings`: List of deterministic warnings (unsupported interactions, network errors, etc.) - `outputPath`: Absolute path to the active capture session file ### `capture_api_stop` Stop the active API capture recording. **Parameters:** - `stop` (boolean, required): Must be `true` for safety confirmation - `discard` (boolean, optional): Whether to delete capture artifacts after shutdown **Returns:** - Final recorder status with network transaction counts - Session file path ### `capture_api_transactions` Query and retrieve the list of captured API transactions (HTTP request/response pairs) from the active recording. Returns a structured list of all network events recorded so far, including request details, response metadata, and capture timestamps. **Parameters:** - `limit` (integer, required): Maximum transactions to return (0 = all) - `includeAssets` (boolean, required): Whether to include asset requests (images, fonts, stylesheets) **Returns:** - Ordered list of network transaction summaries, each containing: - `transactionId`: Unique transaction identifier - `method`: HTTP method (GET, POST, PUT, DELETE, etc.) - `sanitizedUrl`: Redacted URL without credentials or sensitive query params - `statusCode`: HTTP response status code (0 if failed/blocked) - `resourceKind`: Inferred resource type (document, xhr, fetch, stylesheet, image, font, other) - `durationMs`: Transaction duration in milliseconds - `requestSizeBytes`: Request body size in bytes (0 if no body) - `responseSizeBytes`: Response body size in bytes (0 if unavailable) - `failureReason`: Network or security error reason (empty if successful) - `responseHeaders`: Redacted response headers (excludes sensitive values) - `correlatedUiSequence`: List of correlated UI event IDs if any - `bodyRefId`: Safe reference to stored body content if present - `timestamp`: When the transaction started ## API capture privacy and safety - **No secrets in session (by default):** When `capture.api.storeSecretsLocally=false` (default), authorization headers and sensitive data are stored via `bodyRefId` reference instead of inline values, and the SHAFT privacy classifier prevents typed secrets and credentials from entering the persistent session JSON - **Body externalization and reference handling:** Request/response bodies are stored via `bodyRefId` references in captured transactions for privacy preservation; the real body content is stored separately and referenced safely - **Secret storage control:** Set `capture.api.storeSecretsLocally=true` to store authorization headers inline for testing purposes (not recommended for production) - **Request/response body handling:** Bodies captured during recording are limited by `capture.api.maxBodyBytes=1048576` (1 MB default); bodies exceeding this limit are truncated - **First-party filtering:** Use `capture.api.firstPartyOnly=true` (default) to record only API calls from the primary domain, or set to false to include all cross-origin requests - **Asset filtering:** Set `capture.api.includeAssets=false` (default) to exclude image, CSS, font, and other static assets, or set to true to include them in the capture - **Safe review:** The generated workbench HTML and capture review summary omit secrets and report only counts and rule names, never exposed values ## Trace-first failure workflow When a SHAFT WebDriver run writes persisted trace artifacts under `target/shaft-traces`, agents can diagnose the latest failure without manually opening Allure attachments: ```json {"tool": "trace_latest", "arguments": {"maxResults": 3}} {"tool": "trace_summarize", "arguments": {"tracePath": "target/shaft-traces/CheckoutTest.pay/index.json"}} {"tool": "doctor_analyze_trace", "arguments": {"tracePath": "target/shaft-traces/CheckoutTest.pay/index.json", "backend": "webdriver"}} ``` Use `trace_read` only when the summary is not enough. Its output is redacted, bounded by `maxCharacters`, and reports whether truncation occurred: ```json {"tool": "trace_read", "arguments": {"tracePath": "target/shaft-traces/CheckoutTest.pay/index.json", "maxCharacters": 12000}} ``` If you see an `AccessDeniedException` under `C:\Windows\system32`, the MCP host launched from a protected Windows directory. Upgrade to a release with the app-data fallback, or set `SHAFT_MCP_WORKSPACE_ROOT` to a writable directory. ## Build from source Run from the repository root: ```bash mvn -pl shaft-mcp -am test mvn -pl shaft-mcp -am install -DskipTests -Dgpg.skip mvn -pl shaft-mcp dependency:copy-dependencies -DincludeScope=runtime \ '-DoutputDirectory=${maven.multiModuleProjectDirectory}/shaft-mcp/target/dependency' \ -DskipTests -Dgpg.skip python3 scripts/ci/validate_shaft_mcp_transports.py ``` The packaged server supports: - stdio by default, with protocol messages only on stdout and logs on stderr; - Streamable HTTP with `--spring.profiles.active=http`, at `/mcp`; - managed recording through `capture_start`, `capture_status`, and `capture_stop`, with no AI provider required; - web API traffic capture through `capture_api_start`, `capture_api_status`, `capture_api_stop`, `capture_api_transactions`, and `capture_api_response_leaves` (classified stable/volatile/sensitive body leaves for a pin-this-path picker before `capture_api_generate`) for recordings that include HTTP request/response events, with automatic secret-reference handling, size limiting, first-party/asset filtering, and privacy enforcement; Chrome/Edge browsers with DevTools Protocol support only; - live recorder authoring mode through `capture_set_mode` (`record` default or `inspect` for locator picking without appending ordinary steps) plus `capture_checkpoint` for USER_MARKER / ASSERTION / PAGE_TRANSITION / RECOVERY / FLOW_START / FLOW_END markers; - deterministic WebDriver TestNG generation through `capture_generate_replay`, `capture_code_blocks`, and `capture_record_at_target_code_blocks`, with compile, optional replay, local deterministic code-review warnings, existing-suite target candidates, focused source-anchor insertion snippets, preview-only patch blocks, setup/assertion/control-flow review blocks, locator-confidence queues, validation back-links, selected and alternative locator expressions, intent-derived default names, generated review headers, compact fallback locator helpers, the `capture-page-object-draft` block, backend comparison summaries through `capture_backend_comparison`, local review manifests through `capture_evidence_pack`, and review-only AI enrichment phases; - deterministic Playwright TestNG generation through the same `capture_generate_replay` and `capture_code_blocks` tools (optional `backend` parameter, defaulting to the active engine; absorbs the former `playwright_capture_generate_replay`/`playwright_capture_code_blocks`/ `playwright_replay_recording`) when the target repository uses `SHAFT.GUI.Playwright`, plus local Capture CLI generation through `capture generate --backend playwright`; - explicit Playwright browser, element, screenshot, DOM, action recording, playback, and copy-paste replay through the same unified `browser_*`, `element_*`, and `capture_*` tool names used for WebDriver -- there is no separate `playwright_*` tool family. `capture_code_blocks` returns Capture-compatible `captureSessionPath`, generated `sourcePath`, `testDataPath`, `reportPath`, `reviewPath`, and report metadata when the recorded actions map to the Capture schema; - deterministic diagnosis through `doctor_analyze_failed_allure`, with an optional `backend` (web|playwright, defaults to web) selecting the generated remediation snippets' engine (absorbs the former `playwright_doctor_analyze_failed_allure`), restricted to explicit input paths and allowed roots, with an optional separately identified provider advisory when Pilot AI is explicitly enabled; - review-only Heal locator proposals through `doctor_propose_healed_locator` (verified recovery) and `doctor_propose_advisory_locator` (low-trust comment), gated by `healing.sourcePatch.enabled` and explicit `sourcePatchConsent`; full-file `doctor propose-fix` / `doctor publish-draft-pr` remain CLI-only; - managed local AI readiness through `setup_status` and `setup_verify`, which print `target readiness version detail` (version empty unless READY) and report readiness/version/action; `doctor_managed_local_ai_status` reports enablement, eligibility, and the DISABLED snapshot, not the reviewed pin table. Defaults stay off, consent is local-only, and the pin table on the setup page is the documented inventory at [managed local AI inventory](/docs/start/local-infrastructure/previews#inventory-defaults-and-troubleshooting); - persisted trace discovery and deterministic trace analysis through `trace_latest`, `trace_read`, `trace_summarize`, `trace_open_viewer`, and `doctor_analyze_trace`, restricted to the MCP workspace and bounded for raw trace reads; - cross-shard Allure merge through `report_merge_shards` (same `ShardMerger` path as `ShardMergeCli`; see [Sharded Execution](/docs/reference/guides/Sharded_Execution)); - guarded test healing through `healer_run_failed_test`, with an optional `backend` (web|playwright, defaults to web) selecting the generated snippets' engine (absorbs the former `playwright_healer_run_failed_test`), which reruns tokenized Maven validation commands, analyzes fresh Allure evidence through Doctor, and returns review-only fixes plus an agent replay handoff; - explicit browser element actions through `element_click` and `element_type`, using locator strategy and locator value arguments, dispatching to whichever engine (WebDriver, Playwright, or mobile) is active in the session; - storage-state save/load through `browser_storage_state_save`/ `browser_storage_state_load`, writing and reading the same JSON schema regardless of the active engine, so a file saved from one backend loads on the other; - observed network transaction listing and detail through `browser_network_requests` (method, URL, status, sizes, and a truncated redacted body preview; full bodies require a Capture session with `saveHarContent=full`), whose optional `id` parameter narrows to one transaction's full detail (absorbs the former `browser_network_request` tool; not supported on the Playwright engine), plus mock network routes through `browser_route` and `browser_unroute`; - official guide search through `shaft_guide_search`, so agents can retrieve cited SHAFT best practices, API/GUI/CLI examples, locators, Page Object Model guidance, and troubleshooting steps before writing code; - structured test automation playbooks through `test_automation_scenarios`, repository-aware coding partner plans through `shaft_coding_partner_plan`, plus generated-code guardrail checks through `test_code_guardrails_check`; - deterministic, no-AI-call test planning through `test_plan_explore`, which crawls the active session and drafts Markdown flow plans into `specs/`, and agent/skill scaffolding for existing test repositories through `shaft_project_init_agents`; - mobile web emulation and native Android/iOS execution through the same `driver_initialize` tool, passing `engine=mobile_web`/`mobile_native` plus a nested `mobileOptions` request carrying every former `mobile_initialize_web_emulation`/`mobile_initialize_native` parameter; web-emulation results include a `deviceProfile` object with resolved viewport, user agent, device scale factor, and touch/mobile flags when SHAFT can determine them; - mobile inspection through `mobile_take_screenshot`, `mobile_get_accessibility_tree`, context switching, and the same `element_click`/`element_type`/`element_clear` and `mobile_swipe` action tools used for locator-based tapping, typing, and swiping, plus rotation, keyboard, backgrounding, app activation, recording, playback, and copy-pasteable Java action blocks through `capture_start`/`capture_stop`/ `capture_code_blocks`; - wrapped Appium Inspector mobile recording through `mobile_toolchain_status`, `mobile_inspector_record_start` (absorbs the former `mobile_inspector_record_prepare`), `mobile_inspector_record_status` (absorbs the former `mobile_inspector_record_control`), and `mobile_inspector_record_stop`. `mobile_toolchain_status` returns structured dependency diagnostics with availability, detected path or version, failure cause, and repair guidance. `mobile_inspector_record_start` reports `adb devices -l`, cached Android AVDs, relevant diagnostic warnings, suggested capabilities, and any Android emulator creation proposal before the user confirms startup. Recorded Inspector gestures are matched against the current accessibility tree/source and converted to locator actions where possible; coordinate actions are kept only as warned fallbacks; - editing a recorded step by its stable `stepId` on the active Playwright or mobile recording through `capture_step_delete`/`capture_step_reorder`; - legacy SSE endpoint properties as configuration aliases during migration. The intent-based `natural_act`/`mobile_natural_act` tools were deleted outright (no deprecation shim): trust-gated natural-language execution through MCP is gone, though the underlying deterministic `SHAFT.GUI.WebDriver.act(...)` Java API is unaffected -- see [Natural Language Actions](/docs/reference/actions/GUI/Natural_Language_Actions). The HTTP port defaults to `8081` and can be overridden with `PORT` or `--server.port`. ## MCP command reference The same main class also provides local SHAFT Capture and Doctor CLIs. Use `;` instead of `:` in `MCP_CP` on Windows. The `capture generate` subcommand shown below is a separate, local-only CLI command, distinct from the MCP tool `capture_generate_replay` described above: ```bash MCP_CP="shaft-mcp/target/shaft-mcp- .jar:shaft-mcp/target/dependency/*" MCP_MAIN="com.shaft.mcp.ShaftMcpApplication" java -cp "$MCP_CP" "$MCP_MAIN" capture start \ --url https://example.test --browser chrome --output recordings/example.json \ --session-goal "record checkout happy path" java -cp "$MCP_CP" "$MCP_MAIN" capture start \ --url https://example.test --browser chrome --output recordings/mobile.json \ --device "Pixel 7" --geolocation "30.0444,31.2357" \ --timezone Africa/Cairo --color-scheme dark --save-har target/capture.har java -cp "$MCP_CP" "$MCP_MAIN" capture status java -cp "$MCP_CP" "$MCP_MAIN" capture checkpoint \ --kind FLOW_START --description "login as admin" java -cp "$MCP_CP" "$MCP_MAIN" capture checkpoint \ --kind FLOW_END --description "login as admin" java -cp "$MCP_CP" "$MCP_MAIN" capture stop java -cp "$MCP_CP" "$MCP_MAIN" capture generate \ --session recordings/example.json --output-dir generated-tests \ --enable-fallback-locators java -cp "$MCP_CP" "$MCP_MAIN" capture generate \ --session recordings/example.json --backend playwright \ --output-dir generated-playwright java -cp "$MCP_CP" "$MCP_MAIN" capture generate \ --session recordings/example.json --output-dir generated-tests \ --control-flow-preview java -cp "$MCP_CP" "$MCP_MAIN" capture generate \ --session recordings/example.json --output-dir generated-tests \ --apply-control-flow-preview generated-tests/target/shaft-capture/control-flow-preview.json java -cp "$MCP_CP" "$MCP_MAIN" capture generate \ --session recordings/example.json --output-dir generated-tests \ --target-source src/test/java/com/acme/CheckoutTest.java \ --insert-after replayCheckout java -cp "$MCP_CP" "$MCP_MAIN" doctor analyze \ --input allure-results --allowed-root "$PWD" \ --output-dir target/shaft-doctor java -cp "$MCP_CP" "$MCP_MAIN" doctor propose-fix \ --repository "$PWD" \ --base-sha \ --diagnosis target/shaft-doctor/doctor-report.json \ --evidence-bundle target/shaft-doctor/doctor-evidence.json \ --issue \ --allowed-path src/test/java/example/CheckoutTest.java \ --repair-input repair-input.json \ --output-dir target/shaft-doctor/repairs java -cp "$MCP_CP" "$MCP_MAIN" doctor publish-draft-pr \ --manifest target/shaft-doctor/repairs/repair-proposal- .json \ --approval-token \ --approve ``` See [SHAFT Capture](/docs/agentic/capture) for checkpoint, runtime-directory, privacy, generation, replay, enrichment approval, browser-support, and recovery details. See [SHAFT Doctor](/docs/agentic/doctor) for evidence categories, allowlisted path handling, deterministic rules, redaction, offline bundle analysis, optional provider advisories, and safe fallback behavior. ## Healing failed Selenium or Playwright tests `healer_run_failed_test` is the Selenium/SHAFT counterpart to test-agent healer loops: it reruns the failing test, inspects the fresh SHAFT Allure evidence, asks Doctor for the latest diagnosis, and stops when the guarded run passes or when guardrails block more attempts. If the target repository has no SHAFT dependency at all, the guardrail stops before running any Maven build, since such a repository can never produce SHAFT Allure evidence. Pass `backend=playwright` for tests built around `SHAFT.GUI.Playwright`. It uses the same guardrails and evidence handling, but returns Playwright-oriented remediation snippets and directs the agent to the same `element_*`/`browser_*` replay and inspection tools (dispatched to the active Playwright engine). The tool accepts a repository root, a tokenized Maven command, an output directory, optional source-path allowlists, and explicit approvals for screenshots, page snapshots, network validation, and configured SHAFT provider advisories. Test-running commands are forced headless and run offline by default. Shell syntax, release/deploy goals, external Maven settings, and worktree-escape paths are rejected. When the healer returns a failed diagnosis, the calling MCP agent may use its own LLM and the matching SHAFT MCP browser, DOM, screenshot, element, and replay tools to inspect the current UI. The same tool names cover Selenium and Playwright tests, dispatching to whichever engine the session's `driver_initialize` call selected. This agent-side reasoning does not require a SHAFT provider API key. The healer never edits source or publishes a change by itself. Locator, wait, test-data, assertion, and product-bug recommendations remain review-only until the user confirms the fix. If Doctor classifies the failure as product behavior, the result is reported as a suspected product bug instead of a test patch. See [Mobile and Flutter testing](/docs/testing/mobile) for Appium setup, mobile web emulation, native device configuration, MCP screenshots, accessibility trees, and record/playback usage. See [Natural Language Actions](/docs/reference/actions/GUI/Natural_Language_Actions) for deterministic `driver.act(...)` patterns, trust thresholds, allowed-action filters, and optional planner fallback behavior. See [SHAFT Pilot](/docs/agentic/pilot) for installation, provider configuration, Capture and Doctor examples, privacy defaults, and client-specific MCP setup. Maintainers should use the [SHAFT Pilot release runbook](/docs/maintainers/pilot-release) for the deterministic release gate and standalone-repository archival order. ## Authentication boundary `shaft-mcp` does not require or store OpenAI, Anthropic, Google, Microsoft, or Ollama credentials. The MCP client authenticates its own model/provider session. Configure only browser/Grid settings required by the SHAFT tools, such as `REMOTE_DRIVER_ADDRESS`. Browser, filesystem, and GitHub mutations should remain approval-gated in the client. Start with a tool allowlist when the client supports one. The `natural_act`/`mobile_natural_act` tools that used to temporarily enable natural-language execution for a call were deleted outright in the tool architecture sweep, with no replacement tool. Argument values are still never written to SHAFT logs for any MCP tool call. ## What the installer configures The bootstrap script uses Python 3.9+ when available. If Python is missing, it installs a checksum-verified portable Python runtime under the per-user SHAFT cache. The shared Python installer then uses the local Java 25 when available. If Java 25 is missing, it installs a per-user cached Java 25 runtime during the same command. It downloads the released thin `shaft-mcp` JAR and runtime dependencies directly from Maven Central, verifies checksums, shows progress for large downloads, writes a Java argfile, probes the server over stdio, and writes the selected client configuration itself. The script creates a local stdio server named `shaft-mcp`. It records the absolute Java 25 executable and the absolute versioned argfile path so GUI clients do not depend on their inherited `PATH`. The argfile carries only `-Dshaft.mcp.fallbackWorkspaceRoot= `: the directory your agent launches the server from stays the workspace (so tools operate on your open project), and the fallback applies only when that launch directory is a protected system location or the bare user home. `claude` configures Claude Code. `claude-desktop` configures Claude Desktop on Windows or macOS and requires restarting Claude Desktop. GitHub Copilot for IntelliJ IDEA uses the JetBrains Copilot `mcp.json` configuration and may need the IDE to reload after the file changes. Configuration is per-user only. If the current project already defines a `shaft-mcp` server that would override the user entry, the installer stops without editing the project. Every user configuration mutation is backed up, verified, and rolled back on failure. At the end of a successful install, the script prints the installed `shaft-mcp` version, the configured client, the restart or new-session action, the SHAFT skills install path or skip decision, and this user guide link. The MCP client remains responsible for tool approvals and model authentication. ## Manual configuration Use manual configuration when the IntelliJ IDEA plugin cannot infer an installed stdio command, or when the installer cannot update your chosen target. You need Java 25, the selected client or plugin target, the thin `shaft-mcp` JAR, its runtime dependencies, a writable runtime workspace, and absolute paths for both the Java executable and an argfile. Create `/absolute/path/to/shaft-mcp.args`: ```text "-Dshaft.mcp.fallbackWorkspaceRoot=/absolute/path/to/shaft-mcp-work" -cp "/absolute/path/to/shaft-mcp.jar:/absolute/path/to/lib/*" com.shaft.mcp.ShaftMcpApplication ``` To pin the workspace to one project regardless of the launch directory, add `"-Dshaft.mcp.workspaceRoot=/absolute/path/to/your-project"` as the first argfile line (or set `SHAFT_MCP_WORKSPACE_ROOT` in the server environment). Use `;` instead of `:` in the classpath on Windows. The JVM property paths may use forward slashes on every operating system. Connect Codex to shaft-mcp with this local stdio configuration: Follow the [official Codex MCP documentation](https://developers.openai.com/codex/mcp) for client-specific settings and approval policy. ```toml [mcp_servers.shaft-mcp] command = "/absolute/path/to/java" args = ["@/absolute/path/to/shaft-mcp.args"] ``` Claude Code user-scope configuration: ```bash claude mcp remove shaft-mcp -s user claude mcp add -s user shaft-mcp -- "/absolute/path/to/java" "@/absolute/path/to/shaft-mcp.args" ``` Claude Desktop configuration: ```json { "mcpServers": { "shaft-mcp": { "command": "/absolute/path/to/java", "args": ["@/absolute/path/to/shaft-mcp.args"] } } } ``` GitHub Copilot local configuration: ```json { "mcpServers": { "shaft-mcp": { "type": "local", "command": "/absolute/path/to/java", "args": ["@/absolute/path/to/shaft-mcp.args"], "tools": ["*"] } } } ``` Codex also supports direct Streamable HTTP: ```toml [mcp_servers.shaft-mcp] url = "https://your-shaft-mcp.example/mcp" default_tools_approval_mode = "prompt" ``` ## Remote clients Containers and Streamable HTTP are intended for remote, shared, or headless deployments where a visible desktop browser is not required. Start the server locally for testing: ```bash java -cp "$MCP_CP" "$MCP_MAIN" --spring.profiles.active=http ``` Use the HTTPS `/mcp` URL for ChatGPT developer mode/apps, the Claude API MCP connector, Copilot cloud agents, Codex, and other Streamable HTTP clients. ChatGPT and cloud-agent clients require a reachable HTTPS deployment; they cannot launch a JAR on the user's workstation. Current client references: - [Codex MCP](https://developers.openai.com/codex/mcp) - [Connect an MCP server to ChatGPT](https://developers.openai.com/apps-sdk/deploy/connect-chatgpt) - [Claude MCP](https://docs.anthropic.com/en/docs/claude-code/mcp) - [Gemini CLI MCP](https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md) - [GitHub Copilot MCP](https://docs.github.com/copilot/how-tos/copilot-on-github/customize-copilot/configure-mcp-servers) [Doctor invocations](/examples/shaft-pilot/mcp/doctor-analyze-invocations.json) cover ChatGPT, Codex, Claude, Gemini, and GitHub Copilot calls to the same `doctor_analyze_failed_allure` tool. Client authentication remains outside SHAFT; Copilot is not treated as a direct generic API-key provider. ## Distribution identity The distribution uses: - Maven Central: `io.github.shafthq:shaft-mcp` - MCP server name: `shaft-mcp` - Canonical OCI image: `ghcr.io/shafthq/shaft-engine-mcp` - MCP registry name: `io.github.ShaftHQ/shaft-mcp` - Java package and existing tool names `shaft-mcp/server.json` is a release template. The publication workflow replaces `@project.version@` from the root reactor version before registry validation and publication. ## Related - [Overview](/docs/agentic/overview) - [Install SHAFT agent skills](/docs/agentic/skills) - [Pilot](/docs/agentic/pilot) - [Doctor](/docs/agentic/doctor) - [Providers](/docs/agentic/providers) --- # Diagnose failures with Doctor Canonical HTML: https://shafthq.github.io/docs/agentic/doctor Markdown: https://shafthq.github.io/md/agentic/doctor.md # Diagnose failures with Doctor `io.github.shafthq:shaft-doctor` collects explicitly selected local test evidence into a portable, redacted bundle and applies ordered deterministic rules. It does not require `shaft-ai`, a provider credential, or network access. The complete baseline works with `pilot.ai.enabled=false`. Optional provider analysis is advisory only. It is disabled unless explicitly requested, receives only minimized already-redacted evidence, and never replaces the deterministic diagnosis, findings, confidence, or remediation. ## Outputs Each analysis writes: - `doctor-evidence.json`: versioned `EvidenceBundle` with checksums, provenance, relative paths, size-limit decisions, and a redaction summary; - `doctor-report.json`: the bundle plus versioned `Diagnosis`, cited `Finding` records, confidence, uncertainty, and `Remediation` actions, plus a separately identified advisory when provider analysis is requested; - `doctor-report.md`: a portable human-readable report and evidence index, including a **Ranked Root Causes** section listing each candidate cause with its trust percentage, rationale, and a copy/paste-ready fix prompt; - `doctor-triage.json` and `doctor-triage.md`: deterministic counts for failing attempts, retry-hidden failures, recurring signatures, primary signature, summary, and cited evidence IDs; - `execution-intelligence.json` and `execution-intelligence.md`: a compact execution trend summary with primary cause, confidence, hidden retry count, and recurring failure count; - `artifacts/`: approved binary evidence such as screenshots. Reports contain no original absolute machine paths. Evidence IDs and bundle IDs are content-derived, and JSON formatting uses LF line endings so repeated analysis of identical inputs is byte stable. ## Run Doctor Use the Doctor command on [Connect shaft-mcp](/docs/agentic/mcp#mcp-command-reference). The canonical `doctor analyze` command and its `--allowed-root` option live there. The command writes `doctor-evidence.json`, `doctor-report.json`, `doctor-report.md`, `doctor-triage.*`, and `execution-intelligence.*` under `target/shaft-doctor`. Doctor input is flexible: point it at an `allure-results` directory, an individual populated `*-result.json` file, or a SHAFT single-file Allure HTML report (`AllureReport.html` or a timestamped variant such as `AllureReport-20260713-101500.html`) -- Doctor extracts the embedded results from the HTML report the same way it reads a results directory. Naming no path at all auto-discovers the newest evidence in the workspace: the most recently populated `allure-results` directory, or otherwise the newest `AllureReport.html`. The SHAFT Assistant routes natural-language phrasing such as "diagnose my last run" or "why did my tests fail" through the same auto-discovery. From an MCP chat: > Use `doctor_analyze_failed_allure` on the `allure-results` directory. Allow only the current > project root, write results to `target/shaft-doctor`, and do not collect > screenshots. ## CLI reference The MCP main class exposes Doctor as a local command. Keep runnable MCP commands on the [MCP command reference](/docs/agentic/mcp#mcp-command-reference) so classpath, Windows separator, and local/remote setup guidance stay in one place. Every readable input must resolve under an explicit allowed root. Symlink targets are resolved before collection. The output directory must also be inside a declared root. Add repeated `--history` options to correlate recurring signatures from older `doctor-evidence.json` files. Screenshots and page snapshots are excluded by default. Retain them only with explicit approval through the Doctor command options documented on [Connect shaft-mcp](/docs/agentic/mcp#mcp-command-reference). Use `--max-item-bytes` and `--max-bundle-bytes` to lower the conservative retention limits. Use `--minimum-results` when the expected run size is known; an empty, malformed, truncated, or unexpectedly small Allure run is reported as incomplete and is never interpreted as successful. ## Optional provider advisory Add `shaft-ai` when invoking `DoctorAnalyzer.analyzeWithAi(...)` directly. The `shaft-mcp` runtime classpath includes the OpenAI, Anthropic, Gemini, and Ollama adapters. CLI provider analysis also requires `--ai`; Pilot properties must independently enable the provider, processing location, model, and every submitted evidence category. The managed local runtime follows the same Doctor command path. Set `managedLocalAi.enabled=true` only after reviewing and installing the managed runtime and model. This selects the `managed-local` provider for an explicitly requested advisory; it does not make Doctor analysis automatic or replace the offline result. The property defaults to `false` and grants local-processing consent only. Inspect enablement, eligibility, and the DISABLED snapshot with `doctor local-ai-status` or `doctor_managed_local_ai_status`. They do not list the reviewed pin table. Use `setup status` or `setup verify` for `target readiness version detail` (version is empty unless READY; readiness/version/action). The pin table on the setup page is the documented inventory. See [managed local AI setup](/docs/start/local-infrastructure/previews#inventory-defaults-and-troubleshooting). Local Ollama remains available through the Pilot provider properties. Ollama defaults to `http://127.0.0.1:11434/api/chat`. Changing the endpoint does not weaken consent, redaction, minimization, schema validation, or evidence-ID checks. For OpenAI, Anthropic, or Gemini, select the provider and model, approve remote processing, and approve the same evidence categories. Credentials remain in the provider-specific environment variable documented in [optional provider controls](/docs/agentic/providers); they are never Doctor arguments or report fields. Doctor submits the deterministic diagnosis, its explicit uncertainty, and only the textual evidence cited by deterministic findings. Unknown cases may submit the smallest available textual evidence set. Provider output must match the versioned `shaft-doctor-advisory-2.0` schema and may contain cited observations, hypotheses with confidence, missing evidence, typed recommended actions, and limitations. Every observation, hypothesis, and action must cite at least one submitted evidence ID. Missing, empty, or invented citations reject the entire advisory. Recommended actions contain an allowlisted operation and its matching target, not provider-authored instructions. SHAFT owns the displayed title and action text. The accepted pairs cover evidence collection, configuration review, locator review, wait-condition review, test-data review, and infrastructure rechecks. An unknown operation, unknown target, or mismatched pair rejects the response, so generated prose cannot become a repair command. When a successful provider call returns invalid JSON, citations, enums, or an invalid action pair, Doctor sends one corrective schema request. It stops after that single correction. Provider failures such as a timeout or unavailable runtime do not trigger the correction. Each execution records its attempt number, provider, model, status, and duration through the existing safe audit event without recording prompts, evidence, or raw responses. Timeout, rate limit, invalid credentials, unavailable provider, malformed JSON, schema violation, oversized output, invented evidence, and budget exhaustion produce an explicit fallback advisory while retaining the complete deterministic report. Reports contain provider/model/configuration identifiers, duration, usage when available, cache state, and a safe fallback reason. They never contain credentials, raw provider responses, or hidden reasoning. Use `--ai-cache` to explicitly cache successful safe structured advisories under the output directory. Cache keys include the evidence bundle checksum, deterministic diagnosis checksum, and a non-secret provider/configuration checksum. Failures and raw evidence are never cached. ## MCP `doctor_analyze_failed_allure` accepts explicit input paths, historical bundle paths, allowed roots, an output directory, screenshot/page-snapshot approvals, and the minimum expected Allure result count. The tool calls the same `DoctorAnalyzer` used by the CLI. It remains deterministic when Pilot AI is disabled; when the MCP server is explicitly started with an enabled provider, the same separate advisory and fallback rules apply. `doctor_analyze_failed_allure` and `doctor_suggest_fix` default to Selenium/ WebDriver remediation snippets. Pass `backend=playwright` to either tool when the failed test is written with `SHAFT.GUI.Playwright` (absorbing the former `playwright_doctor_analyze_failed_allure`/`playwright_doctor_suggest_fix` tool names); the evidence model is the same, but the returned code blocks use Playwright assertions and actions. `doctor_suggest_fix` caps its remediation output at the top 5 ranked causes and tags each block with its category and trust score, for example `LOCATOR (trust 82%)`, so an MCP client (including the IntelliJ Assistant's `/doctor` command) can present causes in trust order without re-deriving the ranking itself. ChatGPT, Codex, Claude, Gemini, and GitHub Copilot can invoke `doctor_analyze_failed_allure` as external MCP clients. Their model authentication stays in the client and is not ingested by SHAFT. Copilot is MCP interoperability, not a generic Copilot API-key adapter. Download the credential-free [representative invocations](/examples/shaft-pilot/mcp/doctor-analyze-invocations.json). ## MCP healer loop `healer_run_failed_test` builds on Doctor for failing Selenium tests. It reruns an allowlisted Maven test command under guardrails, snapshots the populated Allure results that changed during each attempt, and sends the fresh failing evidence to `doctor_analyze_failed_allure` before returning repair suggestions. Pass `backend=playwright` for `SHAFT.GUI.Playwright` tests (absorbing the former `playwright_healer_run_failed_test` tool name); it uses the same execution guardrails and sends evidence through the same Doctor tool surface. The healer gives the MCP agent an explicit replay handoff: the agent may use its own LLM plus the same SHAFT MCP browser, DOM, screenshot, and element inspection/replay tools to inspect either WebDriver or Playwright failures, dispatching to whichever engine the session's `driver_initialize` call selected, even when no SHAFT provider API key is configured. Configured SHAFT provider advisories remain optional and require the same Pilot consent as Doctor. The boundary is still review-only. The healer can suggest locator, wait, test-data, assertion, or setup fixes, or report a suspected product bug, but it does not edit files, skip tests, quarantine tests, publish branches, or bypass user confirmation. ## Reviewed repair proposals Doctor repair is a separate, approval-gated workflow. `propose-fix` requires an exact 40-character base commit SHA, explicit repository-relative file allowlists, structured full-file patches, and tokenized Maven validation commands. It creates `codex/doctor- - ` in a temporary Git worktree, applies changes only there, and returns a persisted manifest with the complete unified diff, patch checksums, diagnosis/evidence references, exact validation commands, populated Allure counts, residual risk, rollback guidance, and a one-proposal approval token. Example `repair-input.json`: ```json { "patches": [ { "path": "src/test/java/example/CheckoutTest.java", "operation": "REPLACE", "content": "package example;\n\nfinal class CheckoutTest {}\n", "rationale": "Apply the reviewed diagnosis.", "evidenceIds": ["allure-result-1"] } ], "validationCommands": [ ["mvn", "-pl", "shaft-engine", "-am", "test", "-Dtest=CheckoutTest"], ["mvn", "-pl", "shaft-engine", "-am", "compile", "-DskipTests"] ] } ``` Run the reviewed repair command from [Connect shaft-mcp](/docs/agentic/mcp#mcp-command-reference) after preparing the reviewed input. Only Maven compile, test, package, install, verification, Surefire/Failsafe, and JavaDoc goals are accepted. Commands are executed as argument arrays without a shell. Release, deployment, SCM, Versions Plugin, shell metacharacter, and arbitrary executable input is rejected. Test-running commands are forced to include `-DheadlessExecution=true`; a zero process exit alone is insufficient when populated passing Allure results are expected. Maven runs offline by default. CLI users must add `--approve-network-validation`, or MCP clients must set `networkValidationApproved=true`, before validation may access the network. `--ai` can request an optional provider-generated patch. The provider receives only the deterministic diagnosis and exact approved regular source files under explicit `TEXT` and `SOURCE` consent. Output must match the versioned repair patch schema. Invented paths, commands, symlinks, binary or oversized content, protected workflows, generated paths, and secret-like material are rejected. Provider, consent, timeout, or schema failure returns no patch and does not create a worktree. Publishing is always a later explicit action using the matching command from [Connect shaft-mcp](/docs/agentic/mcp#mcp-command-reference). Failed validation blocks publication by default. An explicit `--override-failed-validation` also requires `--override-rationale`, which is recorded in the manifest and pull-request body. Publication stages only the manifested files, creates a Doctor-identified commit, pushes the dedicated branch, and creates or reuses an open draft pull request through authenticated `gh`. It never marks a PR ready, merges, releases, deploys, resets, cleans, or switches the user's current worktree. The temporary worktree is removed after publication or explicit cancellation; the published branch remains. Full-file repair proposal and draft publication stay on the local Doctor CLI (`doctor propose-fix`, `doctor publish-draft-pr` via the MCP main class). They are not MCP tools — `doctor_publish_draft_pr` is deliberately absent from the packaged tool catalog. Publication still requires the separate `--approve` flag and the exact proposal token from the reviewed manifest. For locator-only proposals built from a SHAFT Heal report, use the MCP tools `doctor_propose_healed_locator` (verified recovery) and `doctor_propose_advisory_locator` (low-trust advisory comment). Both require `healing.sourcePatch.enabled=true` plus explicit `sourcePatchConsent`, write under `target/shaft-doctor/healing-proposals` by default, and never edit or publish source. See [Recover locators with Heal](/docs/agentic/heal#reviewed-locator-proposals-doctor-mcp). ## Evidence The collector recognizes populated Allure `*-result.json`, normalized exception chains, SHAFT logs/action history, environment metadata, dependency/build metadata, configuration summaries, `shaft-diagnostics.zip` attachments, screenshots, and page snapshots. When a failed SHAFT test attaches `shaft-diagnostics.zip`, Doctor reads only `diagnostics.json` from the archive and treats it as sanitized SHAFT log evidence for deterministic rules and MCP handoff. Text and structured JSON are redacted before retention or hashing. Password fields, authorization and cookie headers, tokens, private keys, common credential fields, and configured sensitive names are replaced without retaining their original values. Allure attempts are grouped by history ID and ordered by their recorded start time. Non-final failed, broken, and skipped attempts remain visible even when the final attempt passes. Historical bundles are optional and can be copied with their relative `artifacts/` directory for offline analysis. ## Diagnosis The ordered rule engine classifies primary and contributing causes as: - `PRODUCT` - `TEST` - `LOCATOR` - `DATA` - `TIMING_SYNCHRONIZATION` - `ENVIRONMENT_CONFIGURATION` - `INFRASTRUCTURE` - `UNKNOWN` Rules cover locator-not-found, duplicate, stale, hidden/covered/interactable, frame/window context, assertion and test-data mismatches, timeout symptoms, driver/browser startup, Grid/Appium/network/filesystem/resource failures, setup/cleanup failures, parallel shared-state symptoms, retry-hidden failures, and recurring historical signatures. Every inference cites evidence IDs and is kept separate from observations. Unknown and contradictory cases remain unknown and list the missing evidence needed to narrow them. Each ranked cause additionally carries a deterministic **trust percentage** (5-95%) from a factor model: a confidence band for the matched rule, the evidence citations backing it, rule precedence when multiple rules match the same signature, minus a contradiction penalty when another finding disagrees. The same evidence always yields the same trust score. `doctor-report.md`'s **Ranked Root Causes** section lists causes highest-trust first, each with its rationale and a fenced, copy/paste-ready fix prompt that can be sent to any AI assistant or agent to apply the fix. ## Sharing Review `doctor-evidence.json`, `doctor-report.json`, and any approved `artifacts/` before sharing. Screenshots and page source can contain personal or confidential data even after deterministic redaction and therefore remain opt-in. Doctor never uploads evidence automatically. ## Validation Run from the repository root: ```bash mvn -pl shaft-doctor,shaft-ai,shaft-mcp -am test mvn -pl shaft-doctor -am javadoc:javadoc ``` ## Related - [Overview](/docs/agentic/overview) - [MCP](/docs/agentic/mcp) - [Pilot](/docs/agentic/pilot) - [Providers](/docs/agentic/providers) --- # Install and operate ChaosEngine Canonical HTML: https://shafthq.github.io/docs/agentic/chaos-engine Markdown: https://shafthq.github.io/md/agentic/chaos-engine.md # Install and operate ChaosEngine ChaosEngine is SHAFT's project-local agent harness. It makes one canonical skill the entrypoint for supported coding agents, installs the matching host adapters, and provisions required tools without relying on manual machine machine setup. It installs supported upstream tools for your user account and records the exact executables used by generated hooks and MCP servers. ## Watch the install This walkthrough is a real ChaosEngine install on a clean Linux account (ChaosEngine `main` `3a02d90`), with synthetic narration. Captions are in the file; prefer them when you watch without sound. ### Chapters - **0:00** — Introduction - **0:10** — What ChaosEngine is - **0:25** — What the installer does - **0:45** — Core and add-ons - **1:10** — Installer flags - **1:23** — Hands-on: core install - **2:14** — Verify with doctor - **2:25** — Optional add-ons - **3:07** — Remove an add-on - **3:20** — Windows - **3:32** — Wrap-up Step-by-step silent loops live on the [ChaosEngine install guide](/docs/agentic/chaos-engine-install-guide). Optional add-on flags are on [Add optional ChaosEngine skills](/docs/agentic/chaos-engine-addons). Full-quality masters are in the [ChaosEngine install video release](https://github.com/ShaftHQ/SHAFT_ENGINE/releases/tag/chaosengine-install-video-20261004). Transcript Install ChaosEngine in one command. Everything you are about to see is a real install, recorded on a clean Linux account. ChaosEngine is a portable working contract for software agents. It gives Codex, Claude Code, Grok, Gemini and GitHub Copilot the same skills, the same hooks and the same health checks. One command runs six steps. It resolves the latest source and downloads only the ChaosEngine folder. It installs the core, then provisions the tools it needs inside your home folder. Finally, it verifies the result with doctor and activates your agent clients. By default, you get the lean core and nothing else. Three add-ons are optional, and never installed unless you ask. Design skills, for web, image, motion and video work. SHAFT engine users, for projects that test with SHAFT. And SHAFT core developers, for contributors to SHAFT itself. It brings the users add-on with it. Each add-on has its own flag. Each add-on has a with flag to add it, and a without flag to remove it. Or list them in the CHAOS_ENGINE_ADDONS environment variable. Now let's do it for real. This is a fresh Linux account with an empty project: one commit and a README. Set the installer URL, then pipe it to bash. The core lands in seconds. Provisioning takes longer. It fetches uv, Python, Node and Java into your home folder, so nothing touches the system. Installation successful. The installer ran doctor itself: fifteen of fifteen components are healthy. It also prints a first-session brief, and the agent hosts it found on this machine. You can run doctor yourself at any time. It flags graphify as degraded and prints the repair command. Now add two add-ons: design skills and SHAFT engine users. Same one-liner, two extra flags. This run re-checks and re-provisions the tools, so it takes a few minutes. We've sped it up. The addons command lists what is installed, with the flag for each one. The files live under .chaos-engine/addons. Here are the design cards. Changed your mind? The without flag removes an add-on. Removing it takes seconds. Only design skills is left, and later upgrades keep what you selected. On Windows, use PowerShell. To pass switches, use the script block form. Or set the environment variable and keep the classic one-liner. One command. A lean, healthy core. Add-ons only when you want them. The full guide is in the SHAFT user guide. This narration uses a synthetic voice. ## Install from a terminal Run the command for your operating system from the project root. `curl | bash` does not change directory; it installs into the shell's current working directory and prints that absolute install root. The Python reporter owns the brand and live checklist so the wrapper cannot double-paint them. Standard error carries progress. The final machine-readable JSON result remains the only standard-output record. On macOS or Linux, run the default non-interactive install: ```bash curl -fsSL https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/chaos-engine/install.sh | bash -s -- https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/chaos-engine/install.sh ``` Add `--interactive` to approve each bootstrap, source, runtime, package, Maven Tools, and activation operation before it starts: ```bash curl -fsSL https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/chaos-engine/install.sh | bash -s -- https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/chaos-engine/install.sh --interactive ``` On Windows PowerShell, run the default unattended pipeline install: ```powershell irm https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/chaos-engine/install.ps1 | iex ``` To approve each operation in PowerShell, invoke the downloaded script with its explicit switch: ```powershell irm https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/chaos-engine/install.ps1 -OutFile install.ps1 ./install.ps1 -Interactive ``` The `CHAOS_ENGINE_INTERACTIVE` environment variable does not enable prompts. Only `--interactive` or `-Interactive` opts in. Interactive mode accepts only `y` or `yes`, ignoring letter case. Any other answer, or Ctrl+C, cancels before the named operation. If no controlling terminal is available, the installer fails before its first network download. A cancelled or failed operation never replaces the last verified installation. The header is the Quantum Mandate mark: an open C, three gates, an offset spine, and a cybernetic-red core (`#FF3B4D`). The wordmark is `ChaosEngine`. When `COLUMNS` is below 28, the reporter prints the narrow fallback `/C|*|E/`. When standard error is a terminal, the checklist refreshes once per second on a dedicated ticker thread. Completed stages are green, the running stage is cyan, and the metrics line uses the same cyan style for elapsed time, download speed when bytes are moving, and remaining time **only** during a sized download. There is no phase ETA. A bounded Trace list records retries, byte counts, and doctor reasons. Set `NO_COLOR` to any value to disable colors. `TERM=dumb`, an incompatible encoding, or unavailable Windows virtual-terminal support selects a safe plain-text fallback. A root `pom.xml` installs Maven Tools MCP automatically. Native mode requires the latest stable Temurin 25 update from `PATH`, `JAVA_HOME`, or `CHAOSENGINE_JAVA`. `--skip-tools` still skips it. In CI or a redirected stream, each `START` and `DONE` transition is a durable newline. Redirected output has no cursor movement, animation, or timer spam. The reporter stops its ticker on success, cancellation, rollback, and errors. ![ChaosEngine terminal installer showing the Quantum Mandate mark, stages, elapsed time, and trace.](/img/agentic/chaos-engine-installer.png) ## Install offline from a bundle On a machine with network access, build a checksum-verified bundle: ```bash python3 chaos-engine/install.py bundle --source chaos-engine --commit --output ce.zip ``` Then install it with no network access: ```bash python3 install.py install --project . --from-bundle ce.zip ``` Every file is checked against the bundle's sha256 manifest before anything is extracted, and any mismatch aborts the install. Runtime downloads are cached per user and keyed by sha256, so a second project reuses them. Inspect or empty that cache with `install.py cache status --component downloads` or `install.py cache purge --component downloads`. ## Installer errors Installer failures exit with status `1`. After the reporter closes and a blank line, the installer prints a stable `CE-*` code, a Help URL to this section, and the `status` and `doctor` commands **only when** `.chaos-engine/install.py` is on disk. First-install verify failures keep that generation so those commands exist. Unexpected failures use `CE-INSTALL-FAILED` and an explicit "click this link" GitHub issue form URL that prefills `chaos-engine-installer.yml`. The installer prints a traceback only when `CHAOS_ENGINE_DEBUG=1`. Use the code to choose the next action: | Code | Meaning | Action | |---|---|---| | `CE-CLAUDE-MARKETPLACE-CONFLICT` | An existing Claude plugin uses a ChaosEngine, Caveman, or Ponytail name with a different source or identity. | Inspect `.claude-plugin/marketplace.json`. Rename or remove only the conflicting entry, then run the installer again. | | `CE-INTERACTIVE-TERMINAL` | Explicit interactive mode cannot open a controlling terminal. | Run from a terminal, or omit the interactive flag for unattended installation. | | `CE-INSTALL-CANCELLED` | Ctrl+C or a declined interactive confirm. | The last verified generation is kept. Rerun the same install command. | | `CE-INSTALL-CHECKSUM` | An artifact checksum failed. | The last verified generation is kept. Rerun the same install command. | | `CE-INSTALL-UNSUPPORTED-PLATFORM` | The OS or architecture is not in the supported matrix (Windows, Linux, and macOS on x64 or arm64). | Run on a supported OS and architecture. | | `CE-INSTALL-PROBE-FAILED` | A dependency entrypoint probe failed. | Run `status` and `doctor`. Memory probes dispatch owned Node plus package JavaScript (`dist/cli/main.js`, `dist/mcp/server.js`), never npm's extensionless POSIX `.bin` shim. | | `CE-INSTALL-FAILED` | Another install, download, validation, or activation step failed. | Read the original detail, click the printed GitHub issue form link if the cause is unexpected, run status and doctor when those files exist, then retry after fixing that cause. | An example cancelled install record: ```text CE-INSTALL-CANCELLED: installation interrupted Last verified generation was kept. Rerun the same install command to continue. Help: https://shafthq.github.io/docs/agentic/chaos-engine#installer-errors Status: python3 .chaos-engine/install.py status Doctor: python3 .chaos-engine/install.py doctor ``` Set `CHAOS_ENGINE_DEBUG=1` before the install command to print a traceback after that record: ```bash CHAOS_ENGINE_DEBUG=1 curl -fsSL https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/chaos-engine/install.sh | bash -s -- https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/chaos-engine/install.sh ``` On Windows PowerShell: ```powershell $env:CHAOS_ENGINE_DEBUG=1 irm https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/chaos-engine/install.ps1 | iex ``` ChaosEngine appends only its missing entries to any valid Claude marketplace. It preserves the marketplace name, owner, description, custom fields, plugin order, and unrelated plugins. Uninstall restores the original marketplace bytes. Malformed JSON or a same-name ownership conflict fails before mutation. If Claude is absent, installation still writes portable project configuration and skips Claude activation without failing. Check health and recovery state from the project root: ```shell python3 .chaos-engine/install.py status --project . --json python3 .chaos-engine/install.py doctor --project . --json python3 .chaos-engine/install.py rollback --project . ``` On Windows, replace `python3` with `py -3`. Installer and doctor Memory probes dispatch the owned `node.exe` against `npm/node_modules/@aictx/memory/dist/cli/main.js` and `dist/mcp/server.js`. They never run the extensionless `node_modules/.bin` POSIX shim as a Node script. Retry installation after `status` or `doctor` identifies the repaired prerequisite. ## Give the install command to your agent Open the SHAFT project that should receive the harness and give this command to Codex, Claude, Grok, Gemini, or another coding agent: ```text Install or upgrade ChaosEngine in this project from the latest commit of ShaftHQ/SHAFT_ENGINE main. Fetch and inspect https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/chaos-engine/bootstrap.py, run it with Python 3, --project ., --repository ShaftHQ/SHAFT_ENGINE, and --branch main; then run Python 3 with .chaos-engine/install.py status --project . Do not stop until status reports the resolved 40-character commit and healthy core, host adapters, and local tools. Treat the installed ChaosEngine skill as the canonical harness and route existing agent guidance through it without deleting unrelated user content. ``` The agent chooses the available Python 3 command on Windows, macOS, or Linux. It must inspect the bootstrap before running it and report any network or permission boundary instead of bypassing it. ## Verify and upgrade Run the operator commands from the project root. `status --json` and `explain --json` emit deterministic, secret-free schema v2 objects. Use `explain` to inspect one normalized host event without executing it. Re-run the same agent install command to upgrade. The bootstrap resolves `main` to an immutable commit, validates the downloaded archive, and records its repository, branch, and commit provenance. If resolution, download, validation, or tool setup fails, ChaosEngine preserves the last verified installation. The project may be a SHAFT Git checkout, another Git checkout, or a non-Git folder. The bootstrap uses the configured SHAFT upstream and never infers it from the consumer project's Git remote. ## Read health and ownership Both `status` and `doctor` report each component's health with four capability fields: | Field | Values | Meaning | |---|---|---| | `owner` | `installer`, `project`, `user` | Who may change or remove the component | | `scope` | `project`, `repository`, `user` | Where the component is shared | | `lifecycle` | `receipt-owned`, `persistent-data`, `derived-single-writer`, `user-managed-cache` | How the component is maintained | | `taskImpact` | `required`, `advisory`, `optional` | Whether its health affects ordinary task work | Memory, MemPalace, and Graphify have advisory task impact. An ordinary task can continue when any of them is missing, stale, corrupt, timed out, or inaccessible. Use one scoped query only when it answers a concrete question, verify any returned path against live files, and use targeted repository search before deciding impact. Do not retry, repair, refresh, poll, or watch a store as part of task completion. Installation, upgrade, explicit maintenance, `status`, and `doctor` remain strict. An unhealthy selected component makes requested `status` or `doctor` health `recovery-required`. Advisory task impact does not weaken those operator checks. ## Understand startup and retrieval Session startup loads compact tracked locators, not full companion skill bodies. Each SessionStart payload stays at or below 4 KiB, while aggregate always-loaded guidance stays at or below 16 KiB. Startup never calls Memory, MemPalace, or Graphify synchronously. For ordinary work, ask Memory, MemPalace, or Graphify one bounded question only when it can shorten the task. Verify every useful result against current files. A missing, stale, timed-out, or inaccessible store becomes a non-blocking degraded result; do not repair, refresh, poll, or retry it during the task. ## Follow the installed topology All five host adapters normalize their native lifecycle events into one provider-neutral kernel. Codex, Claude, Gemini, Grok, and GitHub Copilot keep their native configuration shapes and preserve foreign handlers. Copilot CLI hooks run live; Copilot cloud and IDE configuration receive static validation. ```mermaid flowchart LR Codex[Codex] --> Kernel[ChaosEngine kernel] Claude[Claude] --> Kernel Gemini[Gemini] --> Kernel Grok[Grok] --> Kernel Copilot[GitHub Copilot] --> Kernel Kernel --> Tools[Resolved account tools] Tools --> Memory[Memory] Tools --> MemPalace[MemPalace] Tools --> Graphify[Graphify] ``` The installer resolves every required version from its official stable channel before activation. It reuses a healthy latest release, upgrades an older release, repairs a broken release, or stops when latest-version verification is unavailable. This rule includes Python: uv installs the exact latest stable Python release for the account instead of accepting an older ambient runtime. ChaosEngine uses upstream account installation paths for uv, MemPalace, Graphify, Memory, and the Context7 CLI. It installs Node through the platform provider and uses the official remote Context7 MCP endpoint. Generated host configuration contains absolute executable paths, so an IDE does not depend on an older shell `PATH`. Foreign files and host handlers stay outside installer ownership. ## Use the final-batch lifecycle Complete implementation as one coherent batch. Do not run tests, reviews, commits, or hooks after every edit. After the final scope commit, clear CI failures, annotations, bot findings, and review comments in batches, then run no more than two selected terminal adversarial reviews and consolidated tests. Do not run final holistic acceptance until the exact pull request head is fully green and no bot finding, comment, annotation, or review thread remains unresolved. Map that acceptance to the approved plan and every closing ticket, then arm merge-commit auto-merge immediately. A changed head or new remote feedback makes the acceptance stale; unchanged state never triggers another acceptance run. ## Respond to reflection checkpoints ChaosEngine pauses implementation mutation after two attempted failures in one task. Different failure fingerprints require task-level reflection; the same fingerprint twice requires deep reflection. Read-only diagnosis, tracker updates, and a changed diagnostic check remain available, but an unchanged retry or third speculative fix does not. Copy each hash after `Sanitized fingerprints:` in the checkpoint message. Use the session token printed by the lifecycle hook to append the bounded receipt: On Windows, use `py -3` instead of `python3` and quote the JSON for your shell. The receipt accepts bounded conclusions, not raw logs, prompts, credentials, or host paths. Knowledge stores and GitHub remain optional for this local control flow. After more than one hour, finish implementation and delivery first. Then record the terminal receipt and use these exact labels in the final summary: - `elapsed estimate` - `main time consumer` - `repeated failures or corrections` - `changed assumption or approach` - `successful proof` - `remaining risk or follow-up` - `learning loop disposition` Later work or failure invalidates an earlier terminal receipt. ## What gets installed ChaosEngine installs and owns these project-local surfaces: - the verified core and canonical skill under `.chaos-engine/`; - native discovery adapters for Codex, Claude, Gemini, Copilot, and compatible agents; - account-scoped latest stable uv, Python, Node, Temurin, Memory, MemPalace, Graphify, and Context7 commands installed through their upstream providers; - receipts and transaction state used by status, repair, rollback, and uninstall. It preserves unrelated project instructions, skills, configuration, and tool entries. Its self-learning flow queues only privacy-screened, confirmed lessons and contributes them upstream as reviewable GitHub issues, never as an automatic pull request. Maven Tools MCP installs automatically when the project has a root `pom.xml`. Native mode builds the latest stable upstream tag with its Maven wrapper and launches the verified JAR with Temurin 25. Multiple projects can reuse the immutable user-managed cache, and project uninstall never removes it. If you already operate Docker, select it explicitly: ```shell curl -fsSL https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/chaos-engine/install.sh | bash -s -- https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/chaos-engine/install.sh --with-maven-tools --maven-tools-mode=docker ``` Docker mode pins the resolved stable image. ChaosEngine never installs or starts Docker. See the [agent tooling cache runbook](/docs/maintainers/agent-tooling#maven-tools-mcp-cache) for status, purge, and manual population commands. ## Related - [Add optional ChaosEngine skills](/docs/agentic/chaos-engine-addons) - [Install SHAFT agent skills](/docs/agentic/skills) - [Agentic testing overview](/docs/agentic/overview) - [Connect SHAFT MCP](/docs/agentic/mcp) - [SHAFT CLI](/docs/agentic/cli) --- # Install SHAFT agent skills Canonical HTML: https://shafthq.github.io/docs/agentic/skills Markdown: https://shafthq.github.io/md/agentic/skills.md # Install SHAFT agent skills SHAFT ships 30 focused skills for planning, writing, running, diagnosing, and reporting tests. Start a SHAFT task with `$shaft-developer`; its router selects one lifecycle, implementation, or MCP/CLI specialist for the immediate job. The pack contains guidance, not a model or hosted agent. It uses the tools and permissions of the agent client where you install it. ## Choose an installation route | Route | Best for | What it installs | |---|---|---| | SHAFT installer | Most projects and MCP users | All skills plus generated MCP and CLI catalogs in the client-native directory | | Claude Code marketplace | Claude Code projects | The complete repository skill package and shared references | | Skills CLI | Installing individual skill directories across supported agents | Skill directories only; shared generated catalogs are omitted | | `shaft_project_init_agents` | An existing project connected to SHAFT MCP | Host-native bridges or full skill files while preserving existing files | ## Install with the SHAFT installer Run from the project that should receive the skills. When the installer has a client target, it writes to that client's native directory: `.agents/skills` for Codex and Grok, `.claude/skills` for Claude clients, or `.github/skills` for Copilot clients. The IntelliJ plugin target installs all three layouts for project portability. A skills-only run with no client target also installs all three layouts. Re-run the same command to upgrade. SHAFT replaces its own installed files and leaves unrelated user skills untouched. It refuses linked or junctioned native skill directories on Windows so installation cannot escape the project. With no arguments in an interactive terminal, the installer asks which components to install; skills default to yes. A non-interactive run must pass a component selector. Use `--install-shaft-skills` to install skills without a prompt or `--skip-shaft-skills` to decline them in an interactive selection. When also configuring MCP, select one client with `--codex`, `--claude`, `--claude-desktop`, `--copilot`, `--copilot-intellij`, `--grok`, or `--intellij-plugin`. A client selector alone configures MCP but does not install skills, so combine it with `--install-shaft-skills` when both are required. ## Install from the Claude Code marketplace Claude Code can load the repository's bundled marketplace manifest directly: ```shell claude plugin marketplace add ShaftHQ/SHAFT_ENGINE --scope project claude plugin install shaft-skills@shafthq --scope project ``` This route includes shared references and generated MCP/CLI catalogs. For Codex, use the SHAFT installer above; the repository-root marketplace manifest is Claude-specific. ## Install with the Skills CLI Use the Skills CLI when direct skill-directory installation is preferred: ```shell npx skills add https://github.com/ShaftHQ/SHAFT_ENGINE/tree/main/shaft-skills --skill '*' --agent '*' -y ``` This route installs each skill but omits the pack's shared generated `shaft-mcp-tools.md` and `shaft-cli-commands.md` catalogs. Prefer the SHAFT installer or plugin marketplace when an agent needs exact offline tool and command lookup. ## Initialize skills through SHAFT MCP For a project already connected to SHAFT MCP, initialize every supported host layout without overwriting existing files: ```json {"tool": "shaft_project_init_agents", "arguments": {"loop": "all", "targetDirectory": ".", "overwrite": false}} ``` Use a single loop such as `codex`, `claude`, `grok`, `opencode`, or `vscode` when the project targets only one host. Review the returned skipped-file warnings before using `overwrite: true`. ## Verify the installation Start a fresh agent session in the project and ask it to load `$shaft-developer`. For an MCP-connected project, search the official guide before generating code: ```json {"tool": "shaft_guide_search", "arguments": {"query": "page object model locators", "maxResults": 3}} ``` ## Related - [Add SHAFT skills to ChaosEngine as an add-on](/docs/agentic/chaos-engine-addons) - [Connect shaft-mcp](/docs/agentic/mcp) - [SHAFT CLI](/docs/agentic/cli) - [IntelliJ IDEA plugin](/docs/agentic/intellij) - [Agentic testing overview](/docs/agentic/overview) --- # IntelliJ IDEA plugin Canonical HTML: https://shafthq.github.io/docs/agentic/intellij Markdown: https://shafthq.github.io/md/agentic/intellij.md # IntelliJ IDEA plugin The SHAFT IntelliJ IDEA plugin is the cohesive coding-partner front door for Java test projects. Use it to ask questions, plan repository-aware changes, record browser or mobile flows, refactor Selenium/Appium code toward SHAFT, reuse existing Page Objects, locators, and actions, diagnose failures, and prepare reviewable repairs from the same tool window. The plugin is a thin IDE shell over `shaft-mcp`: SHAFT engine behavior, local CLI agent routing, direct provider adapters, Doctor, Healer, Capture, and Inspector logic stay in the engine modules. Install the plugin from JetBrains Marketplace when it is published, then open **Tools | SHAFT | Open SHAFT**. If you install a plugin ZIP from disk, restart IntelliJ IDEA when the IDE prompts for restart so the SHAFT tool window and actions are fully registered. The core Assistant tool window can load without IntelliJ's Java plugin; Java-specific actions are registered only when Java support is available. First run shows a five-step wizard inside the tool window. SHAFT tools are required; connecting an AI agent is optional. 1. **This project** shows the detected JDK, build tool, operating system, and whether an MCP command is already present. The installer target defaults to the IntelliJ plugin. Continue. 2. **Prerequisites** lists only a missing JDK, Maven 3.9+, or Git. Tools that are already present collapse to one **Ready** line. Each missing tool has one command you can copy. 3. **Agent** recommends one agent and says why. Other agents stay behind **Use a different agent**. An API key field appears only for an agent that needs one. The plugin stores a pasted key in IntelliJ Password Safe. 4. **Install and check** shows the installer command. **Copy** puts it on the clipboard and pre-types it in the IDE terminal. Review the command and press Enter yourself. The plugin does not execute the installer. **Check** is a separate action. The step says **Waiting**, **Checking**, **Verified**, or **Needs attention**. **Verified** appears only after you press **Check** and the check passes. A failed check keeps the command visible, names the problem in one sentence, and offers **Retry**. 5. **First success** finishes setup when you choose **Open the Assistant** or **Record a sample flow**. **Skip** says that agent tools stay unavailable until setup is finished. It does not delete chat or saved keys. The tool window can open setup again. People who already have a verified MCP install are not shown the wizard. A plugin update drops a stale MCP command, keeps that verified install off the wizard, and shows **Updated to** the new version once. On macOS and Linux the copied command downloads and runs the `main` branch installer for the IntelliJ plugin target. You run it. The plugin does not. ```bash tmp="${TMPDIR:-/tmp}/install-shaft-agentic-tools.sh" curl -fL https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/scripts/mcp/install-shaft-agentic-tools.sh -o "$tmp" && sh "$tmp" --intellij-plugin --install-shaft-skills ``` Project upgrade is not a wizard step. See the [Upgrade guide](/docs/start/upgrade). **Back** returns to the previous step and leaves saved chat and keys in place. When a prerequisite is missing, **Continue anyway** is an explicit second choice. Codex CLI is the recommendation when no agent is saved yet, because it runs locally and does not need an API key. **Use a different agent** lists the other agents. An API key field appears only for Gemini. A pasted key is stored in IntelliJ Password Safe. **Advanced**, on the agent step and the install step, can change the installer target or reset setup after a confirmation. The default target is the IntelliJ plugin. ![SHAFT IntelliJ first-run wizard on the project step, with the detected JDK, build tool, and operating system, and Continue under those facts](/img/agentic/intellij-plugin-first-run-wizard.png) ![SHAFT IntelliJ first-run wizard in a narrow dark tool window, still on the project step, with Continue directly under the step](/img/agentic/intellij-plugin-first-run-wizard-narrow-dark.png) The install step says **Waiting** until you press **Check**. **Checking** is the in-progress state. **Verified** appears only after **Check** passes and SHAFT can resolve a local MCP command. A blank command stays on **Needs attention** even when the probe would otherwise pass. **Needs attention** keeps the command on screen, shows "The check needs attention. Copy the command, run it yourself, then try again.", and changes the primary action to **Retry**. **Report this problem** is available in that failure state. **Allow and file** stays off until you choose it, and that choice is remembered. The report is a public GitHub issue on [ShaftHQ/SHAFT_ENGINE](https://github.com/ShaftHQ/SHAFT_ENGINE/issues). When `gh` is signed in, SHAFT files the issue. Otherwise it opens a prefilled browser form. The issue keeps the plugin version, IDE build, operating system, wizard step, exception class, the top `com.shaft` frame, and the plain-language sentence. It removes API keys, `ghp_` and `sk-` tokens, Bearer secrets, email addresses, and absolute home or project paths. **Not now** leaves the failure on screen and files nothing. **Settings | SHAFT** can turn the same reports on later with **File setup problem reports as public GitHub issues**. The last step can store two local ratings, ease and usefulness, on this machine. Those ratings stay in plugin settings. **Open the Assistant** or **Record a sample flow** writes the resolved MCP command, marks setup complete, and opens the Assistant. Until that action, the tool window stays on the wizard. On Windows the copied command is: ```powershell powershell -NoProfile -ExecutionPolicy Bypass -Command '$installer=Join-Path $env:TEMP "install-shaft-agentic-tools.ps1"; Invoke-WebRequest -UseBasicParsing "https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/scripts/mcp/install-shaft-agentic-tools.ps1" -OutFile $installer; & $installer -Client intellij-plugin --install-shaft-skills' ``` After setup, the plugin starts the configured stdio command on the first tool invocation and keeps that MCP server process alive across tool calls, so session-based tools (a running Capture recording, an initialized live driver) keep running between commands; the process is restarted transparently when it dies or the configured command changes. The plugin does not embed the SHAFT engine or manage provider model traffic itself. The plugin also caches the connected server's MCP tool list per connection instead of re-listing tools on every call, populating the catalog automatically the first time it is needed after a (re)connect. A request that names a tool missing from the cached catalog fails fast with a suggested nearest-match tool name instead of silently doing nothing. ## Tool window Open **Tools | SHAFT | Open SHAFT** to show the tool window in a detected SHAFT project. The SHAFT action and tool window stay hidden in unrelated projects. The plugin opens on the **Assistant** — the only view regular users see — and the Assistant understands what you need in plain language. There are no commands to learn: describe the outcome, and the Assistant routes the request to the right SHAFT workflow. Typing `/` in the composer also opens a slash menu with quick shortcuts for the same core workflows -- see [Assistant](#assistant) below for the command list. - "Record my browser actions on https://your-app.example" starts a privacy-safe web recording session. - "Record my mobile actions on the Android emulator" starts a mobile recording with an attached emulator session. - "Generate a SHAFT test from recordings/checkout.json" converts a saved recording directly into compile-validated SHAFT code without a live capture session. Use `/codegen ` when AutoBot should record the journey, propose reuse, generate the code after approval, replay it, and heal one failure. See [Record and generate a new scenario](#record-and-generate-a-new-scenario). - "Diagnose my last failed test run" triages the most recent Allure evidence in the project automatically — no report path required. - "Upgrade this project to the latest SHAFT" has the agent preview, apply, and verify the upgrade (with Agent mode and source edits approved). As the Assistant routes a request to a tool, the running status names it in plain language — "Running: `capture_start` …" — so it is always clear which tool was chosen. When a tool fails, the result leads with a short headline ("` ` couldn't finish"), the humanized error, and exactly one next action, so a failure is easy to scan and act on rather than a wall of exception text. An empty chat starts with three outcome actions: **Plan a test from a scenario**, **Record a sample flow**, and **Diagnose a failure**. Each action only pre-fills the composer; review or edit the request before you send it. The Assistant also shows a dismissible first-run coach: "Finish setup → Record a sample → Review code" with a **Got it** button that hides it permanently. The composer placeholder invites a plain-language request (record, generate a test, diagnose failures, upgrade) and wraps to the panel width so it is always fully readable. **Run settings** stays collapsed as a chip until you expand it to change the agent, effort, **Allow source edits**, or Verbose. Until setup selects a route, that chip does not name Codex and does not invent a default agent. The Assistant still opens in **Agent** mode with **Allow source edits** checked, so a first request like "generate a test" can actually land code in the project; uncheck it for suggestion-only runs. Recording requests such as "Record my browser actions on https://..." always run on the plugin's own long-lived SHAFT MCP session -- a recording must never belong to a one-shot local agent turn, whose MCP process (and with it the recording browser) ends seconds after the reply. Enabling **Settings | SHAFT | Enable advanced workflows and provider options** (Expert mode) reveals the **Workflow** selector with every specialist surface: **Guided**, **Recorder**, **Inspector**, **Triage**, **Visual Baselines**, **Evidence**, **Projects**, and **Advanced**. These panels expose raw MCP requests and are aimed at users who already know the tool catalog; everything they do is reachable through plain Assistant requests. The Guided tab's **Try SHAFT on a sample page** button extracts a bundled local bookstore page (nothing leaves your machine) and walks a complete record-review-insert loop in about 90 seconds. Use the plugin as the default front door when you are already in IntelliJ: - Record web journeys, then review WebDriver or Playwright code blocks before inserting them into the existing test structure. - Record mobile/Appium flows, then reuse generated locator and action blocks in the existing mobile Page Objects. - Ask for a Selenium-to-SHAFT conversion (pasting native Selenium into the composer also offers a one-click conversion) so the plan searches existing Page Objects, locator fields, and action methods before suggesting new code. - For generated GUI code, reuse existing project code first. If a needed action or locator is missing, record the complete flow, then insert only the missing locators/actions into the planned source anchor. Use a unique author-written id through `SHAFT.GUI.Locator.hasAnyTagName().hasId(...)`, then an ARIA role through the same builder, then native relative `By.xpath(...)` only. Do not use `SHAFT.GUI.Locator.xpath(...)`, the raw `SHAFT.GUI.Locator.id/name/cssSelector/className/tagName(...)` factories, or Smart Locators (`inputField` / `clickableField`) in generated or repository code. - Ask to diagnose or heal failed runs; proposed fixes stay review-only until you apply and verify them. - Keep WebDriver as the default backend unless the project already uses `SHAFT.GUI.Playwright` or the prompt explicitly asks for Playwright. ## Agentic E2E workflows Use these flows as chat contracts. The plugin is the front door, `shaft-mcp` does repository-aware planning and evidence capture, and the selected local agent applies source edits only after you enable **Allow source edits** for that request. ### Upgrade a Selenium Maven project In Agent mode, ask the assistant to inspect the current Selenium/JUnit Maven project and return the upgrader command first. The command should be in its own fenced block, and the agent should wait for you to run it before source edits. Use the [Upgrade guide](/docs/start/upgrade) as the canonical source for the copyable command; this page documents the IDE workflow around that command. The **Projects** workflow can preview or apply the same upgrade after setup. ```mermaid flowchart TD Project[Selenium WebDriver + JUnit Maven project] --> Setup[IntelliJ plugin setup: chosen agent + Agentic Tools] Setup --> AskUpgrade[Agent mode: ask to upgrade project to SHAFT] AskUpgrade --> Command[Agent returns copyable upgrader command from Upgrade guide] Command --> UserRuns[User runs command in terminal] UserRuns --> Compile[Agent reads result and runs focused compile] Compile --> Review[User reviews upgraded project] ``` Use `basic` when you only want the POM updated, `session` when the agent should also migrate supported Selenium session setup, and `full` only after reviewing the higher-risk action rewrites. ### Record and generate a new scenario Select the Codex local CLI route and finish the first-run wizard before you start. The protected free-text workflow needs both the local CLI and a verified `shaft-mcp` connection. Other Assistant chat and persisted-recording codegen remain available through their supported routes. Send the scenario from the Assistant composer: ```text /codegen Navigate to https://example.test/login, sign in as a valid user, and verify the account page. ``` AutoBot resolves the target URL from the scenario first. If the scenario has no URL, it reads `baseURL` from `src/main/resources/properties/custom.properties`: ```properties baseURL=https://example.test/login ``` If neither source provides a URL, AutoBot asks one URL question before recording. A missing URL after that question cancels the workflow instead of guessing a target. AutoBot then runs these phases: 1. **RECORD** keeps project source read-only. AutoBot opens the managed browser, performs the described actions through the negotiated SHAFT browser and Capture tools, stops the session, and saves `recordings/intellij-capture.json`. 2. **AWAITING_EDIT_CONFIRMATION** shows the saved recording and a proposal that names reusable test classes and Page Objects. No source edit or replay runs until you send `approve edits`. Send `deny`, `cancel`, or `stop` to finish without source changes. 3. **GENERATE** applies the approved plan. Reuse existing test classes, Page Objects, locator fields, and action methods before creating anything. Create a class only when no suitable owner exists. For a new locator, prefer a stable unique ID with `SHAFT.GUI.Locator.hasAnyTagName().hasId(...)`, then follow the project's existing locator conventions and the [generated locator policy](/docs/reference/actions/GUI/Locators_And_Self_Healing#generated-locator-policy). 4. **REPLAY** runs the generated scenario once. If it fails, **HEAL** analyzes the failed locator or action, applies one repair, and replays once more by default. Request a larger limit explicitly in the original scenario, for example `allow 3 heal retries`. AutoBot never weakens assertions to pass. The terminal response lists each phase outcome, every changed or created class, and the final scenario status. When the run produced a real `allure-report/AllureReport.html`, the response links that single-file report; it does not invent a link when the file is absent. Cancellation stops the active workflow and prevents later output from changing the panel. Unsupported CLI routes and custom agent commands fail closed before RECORD because they cannot enforce the source-read-only boundary. This failure only blocks protected free-text `/codegen`; it does not disable other chat. Checkpoint notes are review intent only. Generated assertions must be real SHAFT builder calls such as `driver.assertThat().browser()...` or `driver.element().assertThat(...)`, not raw JUnit/TestNG assertions. ### Delegate browser exploration to Playwright When a task needs token-efficient snapshots, console output, tracing, video, PDF, or official Playwright Test Agent planning, let the local agent use official Playwright CLI or Playwright MCP as a sidecar. The final Java change still returns through SHAFT planning and guardrails. Storage-state save/load and observed-network inspection no longer need a sidecar for the common case: use `browser_storage_state_save`/`browser_storage_state_load` and `browser_network_requests` directly -- the same tool names work against whichever engine (WebDriver or Playwright) is active in the session. ```mermaid flowchart LR Intent[User intent] --> NeedSidecar{Need Playwright-specific exploration?} NeedSidecar -->|No| ShaftTools[SHAFT WebDriver or Playwright MCP tools] NeedSidecar -->|Yes| PlaywrightSidecar[Official Playwright CLI or MCP] PlaywrightSidecar --> Evidence[Snapshots, locators, screenshots, storage, HAR, traces] ShaftTools --> Plan[shaft_coding_partner_plan] Evidence --> Plan Plan --> Java[SHAFT Page Objects and tests] Java --> Verify[Guardrails and focused validation] ``` Do not paste Playwright TypeScript output into a Java project. Treat Playwright CLI/MCP output as evidence, then translate the proven behavior into `SHAFT.GUI.WebDriver` or `SHAFT.GUI.Playwright` syntax based on the project backend. ### Diagnose and heal a failed test ```mermaid flowchart TD Failure[Failed SHAFT test] --> Trace[trace_latest and trace_summarize] Failure --> Doctor[doctor_analyze_failed_allure] Trace --> Cause[Failure category and source context] Doctor --> Cause Cause --> Heal{Locator recovery eligible?} Heal -->|Yes| Healer[healer_run_failed_test, WebDriver or Playwright backend] Heal -->|No| FixPlan[shaft_coding_partner_plan for reviewed fix] Healer --> FixPlan FixPlan --> Patch[Approved source edit] Patch --> Validate[Focused rerun] ``` ## Assistant The **Assistant** workflow is a chat-style view with Ask, Plan, and Agent modes in the bottom composer. Local CLI prompts call the MCP `autobot_local_agent_run` tool, which delegates to the engine-side local agent service in `shaft-pilot-core`. Cloud Ask and Plan prompts call `autobot_provider_chat` with the selected provider and model. Readiness helpers are `autobot_local_agent_clients` (installed local CLIs), `autobot_provider_status` (configured cloud provider, model, API-key presence without the value, structured-output support), and `autobot_provider_models` (catalog for the selected provider). Supported local routes are: | Client | Default local command | API key required by SHAFT | | --- | --- | --- | | Codex CLI | `codex --ask-for-approval never exec --sandbox read-only -` for Ask/Plan and no-source Agent; project-scoped `workspace-write` with `Allow source edits` | No | | Claude Code | `claude --print`; Plan uses `--permission-mode plan`; no-source Agent asks per tool call via a local approval bridge (see [Tool approval](#tool-approval)); source-edit Agent uses `acceptEdits` for file edits and keeps the same approval bridge for shell and third-party MCP tool calls, which `acceptEdits` alone would silently deny in `--print` mode; SHAFT's own MCP tools are pre-approved via `--allowedTools mcp__shaft-mcp` in both Agent variants | No | | Copilot CLI | `copilot ask`, `copilot plan`; source-edit Agent uses `copilot agent` | No | The composer shows a **model** selector and a reasoning **effort** selector for the active route in both the basic and advanced UI. Local routes list the models reported by the connected agent CLI (`codex debug models`, `claude config list-models`, `copilot models`); cloud routes list a curated catalog per provider, for example `gemini-3.5-flash`/`gemini-2.5-flash` for Gemini and `claude-fable-5`/`claude-opus-4-8`/`claude-sonnet-5` for Anthropic. The cloud selector is restricted to its curated choices, while the local selector is editable so newer local model names can be typed in. The selected model is passed as `--model` to the local CLIs and as the `model` argument to `autobot_provider_chat`. Effort levels are Default, Low, Medium, and High: Codex receives the level as its `model_reasoning_effort` config flag, while Claude, Copilot, and cloud providers receive a one-line reasoning-effort preference at the top of the prompt. The local model row always includes **CLI default**. Choose it to omit the `--model` flag and let the selected CLI use its configured default. A status row distinguishes checking, available models, an empty catalog, an unavailable CLI, and a failed refresh, so an empty selector is never the only diagnostic. The **Agent health** row checks the project-local SHAFT skills required by the selected family. Use **Recheck** after changing files, or **Repair skills** to run `shaft_project_init_agents` for the selected family. A local-agent prompt is blocked when the required `shaft-developer` or `shaft-recording-codegen` skill is missing; the repair action rechecks the files before it resends that prompt. Cloud providers are OpenAI, Anthropic, Gemini, and GitHub Models. For each provider, select a detected environment variable or store a key in IntelliJ Password Safe. The plugin recognizes `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GOOGLE_API_KEY` then `GEMINI_API_KEY`, and `GH_TOKEN` then `GITHUB_TOKEN` in the stated precedence order. It stores only the selected variable name and passes only that provider credential to the MCP process. Cloud `AGENT` mode is disabled because direct provider chat cannot mutate the local workspace. A cloud route selected during first-run setup (such as Gemini) stays active in the basic UI; switching providers ad hoc remains an advanced-mode control. Use `Ctrl+Enter` or `Command+Enter` to send a prompt. Newly sent prompts scroll into view immediately, so the chat shows visible feedback before a long-running response finishes. Press `Escape` to cancel a running request. The selected local agent appears as compact text such as `Codex CLI`; hover it for the full route, for example `Agent: Local / Codex / CLI`. Compact Assistant controls keep JetBrains-style glyphs, including Copy all, Clear, and Rerun transcript actions. All controls retain accessible names, status metadata, and tooltips. Code blocks use a light editor-style palette in light mode and a distinct dark surface in dark mode. The header keeps a trust summary visible while you work: `Agent · Status · Mode · Access · Context`. It reports **Not configured** when no route is selected, distinguishes connected, authentication-needed, and unavailable provider states, identifies read-only, source-edits-with-approval, or unrestricted access, and counts attached context. Narrow tool windows keep model, effort, Verbose, and Auto-compact controls inside **Run settings**. While a prompt runs, the submit icon becomes an animated spinner; hovering it changes the same square control into cancel. If you cancel, the request ends with a dedicated final transcript entry and no capture-generated output is finalized. While the stop is in flight, the compact status strip shows **Cancelling…** (or **Killing…** when the underlying process needs a hard stop). The strip collapses when the run finishes; the transcript keeps the substantive result, cancellation, or failure response without adding a duplicate terminal milestone. Duration appears only when the underlying tool or agent reports a real duration. A token-usage line appears under the response only when the wrapped CLI reports real usage metadata (input/output token counts); SHAFT never fabricates or estimates a count when the CLI stays silent about usage. A **Verbose** checkbox, available on every route, forwards the unfiltered picture into the chat as it happens instead of only showing the final result. For local agent CLI runs that means the agent's own stream: extended-thinking/reasoning blocks, each tool call (with a short summary of its input when one is available), and each tool call's result or failure once it completes. For direct SHAFT MCP tool runs (for example recording code generation), Verbose echoes the exact tool request being sent and the raw tool response alongside the formatted answer. Toggling Verbose mid-run is safe in either direction -- the transcript never ends up showing a stale in-progress bubble or losing an unrelated message. With Verbose off, a brief "running" bubble still appears while the agent works and is replaced by the final answer. Tool-result messages that carry raw evidence (for example a Doctor analysis or a direct MCP tool response) also show a collapsed **Show raw output** toggle directly under that message, independent of the Verbose setting -- expand it to inspect that one result's raw payload without turning on Verbose for the whole session. Like Verbose's raw-response echo, this raw evidence is transient view state only; it is never written to persisted chat history (see below). Assistant output is grouped into labeled run records such as **Run · Result**, **Run · Action and evidence**, **Run · Raw activity**, and **Run · Failed**. Action and raw-activity details start collapsed; use **Show run details** and **Hide run details** to inspect them. Evidence image previews are buttons, so you can reach and open them with the keyboard. Raw evidence uses the visible **Show raw output** and **Hide raw output** controls instead of a tooltip-only state. Local Agent mode is blocked from source mutation until the user explicitly approves it for that request. For browser-only tasks, leave `Allow source edits` off; enable it when the request requires applying code or source edits. If an Agent-mode continuation such as "try again" follows an earlier source-edit request, the Assistant still requires `Allow source edits` before launching the local agent. A custom local agent command can be supplied for non-standard CLI installations; broad Ask, Plan, and Agent prompts keep using the selected local route. :::danger Unrestricted Codex recovery Keep **Run Codex without sandbox** off for normal work. Enable this separate, default-off recovery option only when the Codex Windows project sandbox cannot launch. It skips Codex confirmation prompts and sandboxing, so Codex commands and file edits may access paths outside the open project. ::: Assistant chats are persisted per IntelliJ project. Use the chat selector to reopen recent contexts, the New chat icon to start a separate context, and the Clear icon to clear only the active chat. Active chat messages are included as bounded context for local and cloud Assistant prompts until you click Clear; New chat starts a separate context. Persisted chats keep rendered messages only; raw MCP payloads and common token/key values are not stored. Typing `/` in the composer opens a slash menu. By default it shows five core commands: `/record` (canonical for starting a web recording session -- `/record-web`, `/rec`, and `/capture` are aliases that route to the same command), `/record-mobile`, `/codegen`, `/doctor`, and `/upgrade`. Enabling [Expert mode](#expert-mode) reveals the rest of the MCP tool catalog in the same menu for users who already know the tool names; regular users never need it, because the five defaults plus plain language cover the common workflows. The Assistant understands feature intent directly from the chat box: "start mobile recording" maps to `capture_start` against an active mobile session, "record my browser actions on https://..." starts a web capture session, and "diagnose my last failed test run" triages the most recent Allure results in the project. Browser control defaults to WebDriver; say `playwright` in the prompt when that backend is required. Direct `/codegen` slash-command results show a persistent **review strip** with actions (Approve / Create test class / Insert / Dismiss) just like record-flow codegen. `/codegen recordings/checkout.json` (or asking in plain language, for example "Generate a SHAFT test from recordings/checkout.json") generates the SHAFT test, compiles it, and **re-executes the recording**, so the returned code blocks are verified against the live flow rather than only statically generated — and it works from the persisted recording file alone, with no live capture session required. Every backend goes through the same `capture_generate_replay` tool (optional `backend` selecting web/playwright/ mobile, defaulting to the active engine): WebDriver Capture recordings generate, compile, then a headless replay gated on populated, passing Allure results; mobile and Playwright recordings replay against their respective active session — generated locators are always validated live before the Assistant returns them, for every backend. Before the run starts, the Assistant explains the phases (generate, compile, replay) and warns that a browser window may open for the replay (it starts on `about:blank` before the test navigates). The result is a step-by-step story — which file was generated where, whether it compiled, whether the replay passed with per-step failure diagnostics when it did not, the report/review artifact paths, and the generated code with next-step guidance — never a bare confirmation. When only the replay step fails, the generated and compiling code blocks are still returned together with the replay diagnostics, so a replay hiccup never turns into an empty "no code" response. Repeating the request regenerates the deterministic output in place instead of failing because the class already exists. `/codegen ` uses the consent-gated AutoBot workflow described in [Record and generate a new scenario](#record-and-generate-a-new-scenario). The plugin coordinates progress, cancellation, confirmation, and result presentation; AutoBot owns recording, source planning, generation, replay, and healing. "Upgrade this project to the latest SHAFT" in **Agent** mode with **Allow source edits** enabled performs the project upgrade itself: the agent states the project's current SHAFT setup, previews the change with the `shaft_project_upgrade` dry run, runs the official upgrader non-interactively, verifies the project still compiles (repairing upgrade-induced breakage with SHAFT syntax when needed), and reports the old and new versions plus every file it touched and why. Outside Agent mode — or on cloud/non-CLI routes that cannot edit local files — the Assistant explains exactly how to authorize the agent-run upgrade and still offers the manual copy-paste command. Use `review recording` or `review recording recordings/ .json` to generate the same reviewed Capture code blocks from chat. After capture approval, the local Agent run shows completion feedback in the final transcript so you can confirm generation status, outputs, and next workflow step before continuing. When the run created no files (for example because its tool calls were denied), the reviewed Capture code blocks stay available so you can approve again or copy the generated class manually, and the transcript says so explicitly instead of ending on a bare confirmation. For local agent CLI runs, the **Verbose** toggle streams everything the wrapped CLI reports while it works: recognized events are shown as human-readable progress lines (thinking, tool calls, tool results), and any event with no human-readable mapping is shared as-is in its native format (raw JSON) instead of being hidden. CLIs with no structured stream forward their raw output after a one-time notice. With Verbose off, only the parsed final response is shown. Independent of the toggle, every Agent-mode answer ends with a factual **Local agent activity** footer whenever the run created or edited files or lost tool calls to permission denials, listing the touched paths and the denied tools with per-tool counts. The run status strip and action controls stay hidden until a request is active. Substantive tool selections and progress milestones appear as transcript run records; approval requests appear inline when a decision is needed. Type `@` for workflow starters (plain-language prefills such as "Record my browser actions on https://") and `#` for the current file or known project artifacts; the dropdown filters live as you keep typing. The former "+" context button was removed in favor of these typed triggers. Pasting raw Selenium/Appium Java into the composer proactively offers a one-click **"Selenium detected — convert to SHAFT + guardrails"** action that wraps the code in a convert-to-SHAFT request and runs the guardrail check on the converted result. After a recording stops and its review is generated, the review bar offers the whole Record → Review → Insert loop in one place: **Create test class** writes the reviewed class into `src/test/java` (never overwriting) and opens it, **Insert into open class** regenerates the steps anchored to the file open in the editor (`capture_record_at_target_code_blocks`), **Open review file** jumps to the generated review artifact, **Evidence pack** returns a shareable manifest of source/report/review artifacts with validation commands, and **Compare backends** generates the same recording as both WebDriver and Playwright SHAFT code side by side. The generation report's readiness findings (flaky steps, unsupported events, required inputs, fallback locators) also surface as file-level IDE annotations directly on the generated class. ![SHAFT IntelliJ Assistant chat composer showing a generated WikipediaSearchTest class under Confirmed target: https://en.wikipedia.org/wiki/Main_Page, with copy, download, delete, and rerun buttons above the prompt box, and a compact Run settings chip before an agent route is selected](/img/agentic/intellij-plugin-assistant.png) ![SHAFT IntelliJ Assistant empty state with a visible trust summary and three outcome actions that prefill the composer](/img/agentic/intellij-plugin-assistant-empty.png) ![SHAFT IntelliJ Assistant narrow dark view showing active work, progress, and cancellation controls](/img/agentic/intellij-plugin-assistant-active-status-narrow.png) ![SHAFT IntelliJ Assistant run result with the raw output disclosure expanded](/img/agentic/intellij-plugin-assistant-tool-result-raw-output.png) The Assistant routes plain-language intent to the right MCP tools: | Intent | Say something like | Primary MCP tools | | --- | --- | --- | | Browser control and inspection | "open https://example.com and sign in" | `driver_initialize` (optional `engine=playwright`), `browser_open_intent`, `browser_get_page_dom`, `browser_take_screenshot`, `browser_aria_snapshot`, `browser_accessibility_audit` | | Web recording and codegen | "Record my browser actions on https://example.com", "Generate a SHAFT test from recordings/checkout.json" | `capture_start` (optional `codegenOptions`), `capture_codegen_features`, `capture_stop`, `capture_status`, `capture_code_blocks`, `capture_generate_replay`, `capture_target_candidates`, `capture_record_at_target_code_blocks`, `capture_backend_comparison`, `capture_evidence_pack`, `capture_step_delete`, `capture_step_reorder` (the same tools dispatch to the active Playwright engine) | | Mobile control and inspection | "check the Android toolchain", "inspect the current mobile screen" | `mobile_toolchain_status`, `driver_initialize` (`engine=mobile_native`/`mobile_web` with a nested `mobileOptions` request), `mobile_get_accessibility_tree`, `mobile_take_screenshot` | | Mobile recording and codegen | "Record my mobile actions on the Android emulator", "generate mobile code from recordings/mobile.json" | `capture_start`, `capture_stop`, `capture_code_blocks`, `capture_record_at_target_code_blocks`, `capture_generate_replay` (dispatching to the active mobile session), `mobile_inspector_record_start` | | Failure analysis and healing | "Diagnose my last failed test run", "analyze target/allure-results" | `doctor_analyze_failed_allure` (optional `backend=playwright`), `doctor_suggest_fix`, `doctor_analyze_trace` | | Managed local AI status | "show managed local AI status" | `setup_status` and `setup_verify` print `target readiness version detail` (version empty unless READY) and report readiness/version/action; `doctor_managed_local_ai_status` reports enablement, eligibility, and the DISABLED snapshot. Defaults stay off. Enablement is local-processing consent only; it is not remote consent or tool approval. See [managed local AI inventory](/docs/start/local-infrastructure/previews#inventory-defaults-and-troubleshooting) | | Reuse planning and guide search | "plan a login test that reuses our page objects", "how do SHAFT locators work?" | `shaft_coding_partner_plan`, `shaft_guide_search`, `test_automation_scenarios`, `test_code_guardrails_check` | | Project upgrade | "Upgrade this project to the latest SHAFT" | `shaft_project_upgrade` preview + agent-performed upgrade | Type `/help` to list every registered Assistant command, SHAFT MCP tool, and `shaft-cli` command. Add a topic to filter across word and separator boundaries: ```text /help browser navigate ``` `/commands`, `/mcp-help`, and `/shaft-help` are aliases for the same local catalog. Questions such as "What tools can you use?" also open it without calling the selected agent. When SHAFT MCP is connected, the MCP section comes from its live `tools/list` response, including valid empty catalogs and newly added server tools. If discovery is unavailable or malformed, the Assistant labels and shows its bundled MCP catalog instead. Cancelling help does not cancel another MCP tool call that shares the project connection. If MCP is not configured, the Assistant shows the SHAFT MCP setup prompt before it runs a feature request. Natural-language Ask/Plan prompts that need MCP tool access tell you to switch to Agent mode instead of launching a local agent from the wrong mode. Project creation from chat returns a review instruction; run **Create SHAFT Project** from the expert-mode Projects or Guided workflow so the confirmed workflow gate is used before files are written. `/doctor` (and "diagnose my last failed test run" and similar natural phrasing) accepts an `allure-results` directory, an individual `*-result.json` file, or a SHAFT single-file Allure HTML report (`AllureReport.html` or a timestamped variant) directly; naming no path at all auto-discovers the newest evidence in the project -- the most recently populated `allure-results` directory, or otherwise the newest `AllureReport.html`. Slash commands beyond the five core defaults (for example typing `/heal` or `/guide` directly) still route to their MCP tool even while Expert mode is off and the slash menu does not list them, preserving backward compatibility for typed muscle memory. Responses render as Markdown. Known SHAFT responses, including local agent runs, provider chat, local client discovery, MCP `content[].text` envelopes, JSON payloads, and Java snippets, are parsed into readable sections, tables, or fenced code blocks. When a browser or mobile recording stops successfully, the Assistant shows the next code-generation request (`Generate a SHAFT test from recordings/....json`) in its own fenced block, ready to send. Unknown structured responses are formatted through the selected Assistant route when possible; if no formatter is available, the plugin falls back to a local Markdown-safe JSON/code rendering. Use the copy actions for rendered Markdown, raw support diagnostics, or the full transcript plus current-session tool evidence when exporting for issue review. ## Onboarding recording notes Use this preferred launch path for the recording workflow from a clean, disposable IntelliJ sandbox/profile so onboarding state stays isolated: ```powershell $env:JAVA_HOME = ' ' shaft-intellij\gradlew.bat -p shaft-intellij runIde --args C:/path/to/SHAFT_ENGINE ``` Use JDK 25 for the checked-in Gradle 9.3 wrapper. The plugin still targets Java 17 bytecode for IntelliJ compatibility. Run the repository's `tools/intellij-plugin-recording/record-onboarding.ps1` helper explicitly when you need its Windows-specific preflight diagnostics; the direct wrapper command above relies on Gradle's normal runtime diagnostics. Use the same onboarding MCP flow: CODEX + CLI, Route = LOCAL, and Mode = AGENT. Ask/Plan browser-control prompts should be resent in Agent mode when MCP tools are required. `Allow source edits` stays off for DuckDuckGo/browser flow and is enabled when the run must change source files. If the step is expressed as "open the first result," use the scoped 1-indexed XPath (`(//article[@data-testid='result'])[1]//a[@data-testid='result-title-a']`) for the first result. For deterministic verification, finish with a final page title and page-specific text check after opening that result before approving generated capture output. Use `discard recording` or `re-record` when a focus or click mistake pollutes the capture; the Assistant stops the current Capture session with `discard=true` before restarting. For recordings, dismiss sandbox-only low-memory or script-launcher warning balloons without suppressing normal production IDE warnings. IntelliJ Trust Project may preselect Windows Defender exclusions; leave them unchecked unless exclusions are explicitly required for that environment. ## Workflows The workflow selector exposes curated MCP requests for common automation jobs: - Recorder: Capture start, status, checkpoints, stop, reviewed code blocks, target discovery, record-at-target patch previews, backend comparison, evidence packs, Playwright recording controls, and replay code generation. - Inspector: browser and Playwright DOM snapshots, screenshots, mobile toolchain status, wrapped Appium Inspector recording, mobile screenshots, and accessibility trees. - Visual Baselines: scans the visual-baseline folder for pending `*_diff.png` comparisons left by mismatched `matchesScreenshot()` runs, shows the baseline and diff side by side, and lets you **Accept** (removes the stale baseline so the next run records a fresh one) or **Reject** (clears the diff marker, keeping the current baseline) each pending comparison. - Evidence: failed Allure analysis, trace discovery, trace analysis, trace summarization, report remediation, guarded reruns, and review-only locator proposals. - Projects: create new SHAFT example projects and preview or apply the modular SHAFT upgrader against the open Java project. Ask the Assistant to upgrade the project, or use this workflow after setup. - Guided: displays only Target URL, a prominent live status, and the recorder controls by default; all other fields and the Coding Partner/Locator sections are behind an **Advanced options** toggle (auto-expanded when expert mode is on). The full view includes a Coding Partner section for planning repository-aware work from intent, current Java source, selected text, and evidence paths; starter templates for recording a browser flow and generating Page Object code, starting a mobile web-emulation session for recording, analyzing failed Allure results, converting Selenium snippets to SHAFT syntax, creating a new SHAFT project, and inspecting current page locators. The recorder **Backend** selector routes the recording controls to WebDriver, Playwright, or Mobile (web emulation) recording tools, and fields the selected backend's start request do not gray out with explanatory tooltips. A **Headless browser** toggle (off by default so you can interact with the recorded browser) controls whether recording sessions launch a visible window — check it for agent-driven or CI recordings; the preference persists across sessions and is also honored by the assistant web and mobile recording flows. The **Intent** field flows into `capture_start` as `sessionGoal`, so generated tests are named after the journey ("Log in as a valid user" yields `logInAsAValidUser()`). A live **Status** strip shows "Recording · N steps · Ready · \ ", allowing you to monitor session state, steps count (including pending debounced input), and current URL in the IDE panel; stopping from here or in the browser overlay saves the same recording session, so headless recordings stay observable and controllable in-panel. On the Mobile backend, Start recording chains `driver_initialize` (`engine=mobile_web`) and `capture_start` as one action, gating the recorder start on the emulated session succeeding. The guided recorder action says **Review code** because it prepares reviewed SHAFT code blocks, setup notes, assertion suggestions, locator alternatives, and control-flow review output. Templates prefill MCP arguments only; apart from the chained mobile recorder start, they do not run tools or write source by themselves. When a test run fails and Allure evidence exists, the plugin raises a notification with **Diagnose with SHAFT Doctor** and **Heal failed test** actions, prefilling the deterministic failed-Allure analysis or a `healer_run_failed_test` run (AI flags off by default) in one click. - Advanced Tools: WebDriver, Playwright, and mobile playback flows, scenario catalog prompts, generated-code guardrail checks, local Assistant client discovery, recorder evidence manifests, backend comparison, and official SHAFT guide search. Each category provides editable JSON arguments and calls the matching MCP tool. This keeps generated code and source edits reviewable in the IDE instead of hidden inside plugin code. Gutter run/debug icons appear next to any TestNG or JUnit `@Test` method in a SHAFT project. The **SHAFT Tests** tab lists recent test runs (pass/fail, class name, timestamp); double-click a row to rerun that test, or Ctrl+double-click to navigate to its source. **Settings | Tools | SHAFT**'s "Enable watch mode" checkbox reruns the last test automatically on every source save under `src/test/`; watch mode checks the saved file's class against the last run's target class first, and on a mismatch offers a "Run ` `" notification action instead of silently replaying a stale result ([ShaftHQ/SHAFT_ENGINE#3467](https://github.com/ShaftHQ/SHAFT_ENGINE/issues/3467)). ![SHAFT IntelliJ Guided workflow panel showing Target URL, a Browser dropdown set to Chrome, a Headless browser checkbox, Advanced options (Backend, Template, Intent, Session path), and an empty Recorded steps list reading Nothing to show](/img/agentic/intellij-plugin-guided.png) ```json {"tool": "shaft_project_upgrade", "arguments": {"projectRoot": ".", "upgradeType": "basic", "dryRun": true, "approve": false}} ``` ## Record in Java code Use **Tools | SHAFT | Record SHAFT Flow Here** from a Java file to prepare a `capture_record_at_target_code_blocks` request for the caret's package, class, method, and source path. The action defaults the session path to `recordings/intellij-capture.json`, matching the Assistant's normal browser recording path. Change it only when you recorded to a different file. After review approval, keep the same capture session path so generation preserves the reviewed browser journey instead of rerunning capture. The generated MCP response includes focused locator/action blocks plus a preview-only patch block; apply changes only after reviewing that preview and running the relevant verification command. This action is available only in IDE installations with Java support enabled. ## Pick Locator at caret Use **Tools | SHAFT | Pick Locator from Live Session** (also on the editor popup menu, visible in SHAFT projects with a Java editor context) to call `capture_pick_locator` and insert the returned `SHAFT.GUI.Locator...` snippet at the caret — the same idea as Playwright's "Pick Locator". Start a SHAFT Capture session first and switch the recorder to inspect mode (`capture_set_mode` with `mode=inspect`, or the overlay's pick-locator control), then click the target element in the managed browser; the action warns instead of inserting anything when no pick is available yet. This v1 requires the recorder-side pick to already exist in the live session; full session pick-state plumbing is tracked in [ShaftHQ/SHAFT_ENGINE#3467](https://github.com/ShaftHQ/SHAFT_ENGINE/issues/3467). ## Coding partner plan Use **Guided | Coding Partner | Plan coding partner** or ask the Assistant to plan reuse before asking the plugin or an agent to create or refactor code. The action prepares `shaft_coding_partner_plan` with the repository path, intent, selected backend, current source path, selected text, optional evidence paths, and `maxResults=10`. The MCP response is preview-only. It returns a working-set summary, ranked reuse matches with existing locators/actions, a structured `stepPlan`, `recommendedTargetSourcePath`, `recommendedInsertionAnchor`, missing code items, suggested MCP proof calls, a focused verification command, evidence paths, and approval warnings. Apply source edits only after reviewing the plan, using the record-at-target patch preview when codegen is involved, and running the returned verification command. Record-at-target previews reuse existing locator fields, skip exact duplicate action lines, and show an apply order before any agent patch is accepted. For Selenium-to-SHAFT work, select the legacy snippet or test first and describe the intended behavior. The plan should preserve working Page Object boundaries and reuse existing locators/actions before adding new SHAFT code. ## Test feedback in the editor - **Locator match counts.** `By.*` literal locators show a gutter count of the elements they match on the live page, through the `element_count` MCP tool. With no live browser session the plugin shows a notice explaining how to start one. - **Last-run results.** Inlay hints show each test's last status and duration from `allure-results`. **Go to last failure** (also on the Reporting panel) jumps to the failing action. - **Page Object from capture.** Generate a Page Object draft from a capture session. You get a preview first, and existing files are never overwritten. ## Kotlin and Gradle - The SHAFT test gutter works in Kotlin files (K1 and K2) when the Kotlin plugin is enabled. - SHAFT run-configuration overrides (browser, headless and so on) also apply to Gradle run configurations as `-D` system properties when the Gradle plugin is enabled. - The plugin is verified nightly against the latest IntelliJ EAP snapshot, and a tracking issue is opened when that run fails. ## Settings and configuration Use **Settings | SHAFT** to configure the plugin's connection, execution, advanced features, and cloud provider credentials. Settings are organized into four sections: - **Connection**: View the MCP stdio command, test the MCP connection against the currently open project, and view the current agent/workspace configuration. Once a wizard-set command already exists, the field is read-only by default — check **Edit manually** to override it directly. - **Execution**: Choose the local Assistant route (Codex, Claude, or Copilot), select the default AI model and reasoning effort, and enable Expert mode to reveal advanced commands in the Assistant composer. - **Advanced**: Configure cloud provider selection for MCP tools and enable advanced workflows. - **Credentials**: Select a detected provider environment variable, or store OpenAI, Anthropic, Gemini, and GitHub API keys in IntelliJ Password Safe for use by MCP tools that request provider assistance. Environment selections persist only the variable name, never the secret value. ### Expert mode When you enable **Settings | SHAFT | Enable advanced workflows and provider options**, the Expert-mode toggle activates on the post-setup settings screen. Expert mode reveals the specialist workflow views (**Guided**, **Recorder**, **Inspector**, **Triage**, **Visual Baselines**, **Evidence**, **Projects**, **Advanced**) and the ad-hoc provider/route controls in the Assistant composer. Regular users never need it: every workflow is reachable by describing the outcome in the Assistant. ### SHAFT project detection The Assistant detects whether the open project actually depends on SHAFT (a `shaft-engine`/`shaft-bom`/`io.github.shafthq` reference in the root or a direct child module's `pom.xml`/`build.gradle`/`build.gradle.kts`). In a project that does not depend on SHAFT yet, mutating or SHAFT-reporting-only tools -- project upgrade, focused verification, and Doctor/Heal triage -- are not dispatched; the Assistant instead replies with a message pointing you at **Create SHAFT Project** in the Projects workflow. Read-only tools such as guide search and coding-partner planning are not gated, since they are useful while you are still adopting SHAFT. The MCP connection heartbeat also stays idle in a non-SHAFT project instead of polling in the background. ### Tool approval SHAFT MCP tool calls made through the Assistant are gated behind an interactive approval bubble rendered inline in the chat transcript. When a recording or code-generation request (or any Assistant feature that calls a SHAFT MCP tool) is about to dispatch a tool you have not approved yet, the Assistant shows the tool name and its arguments with a button per approval scope: - **Approve once** — allow just this one tool call. - **Approve tool always** — remember approval for every future call to this tool. - **Approve all tools** — approve every SHAFT MCP tool from now on. - **Deny** — reject the call; the Assistant reports the denial instead of running the tool. Remembered approvals are stored per project and survive restarts, so approving a tool (or **Approve all SHAFT tools**) in one project never authorizes it in another project open in the same IDE session; **Reset everything** clears approvals for every currently open project. Each distinct tool is prompted at most once per run, so a workflow that calls the same tool repeatedly never prompt-storms you. When the selected Assistant route is Claude Code and Agent mode runs without `Allow source edits`, SHAFT hosts a small local approval server for that run and points the CLI at it (`--permission-prompt-tool`), instead of limiting the CLI to only proposing a plan. Claude's own built-in safety classifier still auto-allows obviously safe, read-only actions without prompting -- matching what you would see running the CLI yourself -- but a genuinely mutating tool call (writing a file, running a command) pauses and renders the same approval bubble described above. Existing grants answer silently; a new request renders the bubble and your decision is sent back to the still-running CLI over that local approval server. These decisions are scoped separately from SHAFT MCP tool approvals, so approving (or denying) a local Claude Code tool call can never silently approve or deny an unrelated SHAFT MCP tool, and vice versa. Codex and GitHub Copilot CLI have no interactive approval protocol, so their tool permissions stay baked into the launch command instead (see the source-edit approval notes above). SHAFT's own MCP tools (`mcp__shaft-mcp__*`) never prompt during local Agent runs: they are first-party capabilities of the Assistant, so Claude Code Agent commands pre-approve the whole `shaft-mcp` server with `--allowedTools` (mirroring the Codex launch-time `default_tools_approval_mode="approve"` flag), and any SHAFT tool request that still reaches the approval bridge is auto-allowed with an `Auto-approved SHAFT tool` transcript milestone. Shell commands and third-party MCP servers keep the interactive approval bubble. ### Configure and reset After setup, the Assistant shows **Configure** next to the current agent. **Configure** opens the five-step wizard again from **This project**. Saved settings, provider keys, tool approvals, and chat history stay in place. **Advanced** on the agent step and the install step includes **Reset setup**. After you confirm, SHAFT returns settings to factory defaults, removes saved provider API keys from IntelliJ Password Safe, clears tool approvals for open projects, deletes Assistant chat for open projects, and shows the wizard again. Project source stays unchanged. A plugin update uses a different reset: it drops a stale MCP command, keeps a finished wizard off this screen, and preserves chat. Configure Codex, Claude, GitHub Copilot, and other MCP clients outside the plugin from the [SHAFT MCP guide](/docs/agentic/mcp). Optional OpenAI, Anthropic, Gemini, and GitHub tokens can come from a selected environment variable or IntelliJ Password Safe. Settings lists only variables that are present, stores only the selected variable name, validates the live selection with the provider-specific **Test** action, and never renders the secret in labels or diagnostics. If a selected variable disappears, the plugin fails closed instead of silently forwarding a different inherited key. Settings also lets you select the configured SHAFT AI provider and model used by MCP tools that explicitly request provider assistance. Direct provider calls remain controlled by `shaft-ai` and the [provider controls](/docs/agentic/providers); the plugin only selects and passes the provider configuration. Settings show whether each provider key is stored, provide explicit clear controls, and keep a test action for validating the current stdio command before using the Assistant or workflows. ## Publishing The engine repository publishes stable builds through the `Publish IntelliJ Plugin` GitHub Actions workflow after the Maven Central release workflow, or manually by maintainers. The workflow signs the plugin, verifies it with the IntelliJ Plugin Verifier, and publishes to the JetBrains Marketplace Stable channel. ## Related - [MCP](/docs/agentic/mcp) - [Pilot](/docs/agentic/pilot) - [Providers](/docs/agentic/providers) --- # Optional AI providers Canonical HTML: https://shafthq.github.io/docs/agentic/providers Markdown: https://shafthq.github.io/md/agentic/providers.md # Optional AI providers SHAFT Pilot separates provider-neutral contracts from optional direct model access: - `shaft-pilot-core` contains immutable request/response records, approval and redaction policies, budgets, safe audit metadata, provider capabilities, `ServiceLoader` discovery, and deterministic fallback. - `shaft-ai` contains direct HTTP adapters for OpenAI, Anthropic, Google Gemini, GitHub Models, and local Ollama. It uses JDK `HttpClient`; no provider SDK type is exposed. - `shaft-engine` exposes only thread-local `SHAFT.Properties.pilot` configuration. It has no dependency on either Pilot module. `shaft-capture` and `shaft-doctor` depend on `shaft-pilot-core`. Add `shaft-ai` only when direct provider calls are required. Capture creation, validation, migration, and redaction remain deterministic with `pilot.ai.enabled=false`. SHAFT Doctor uses these contracts for an optional separately rendered advisory. It submits only minimized evidence from an already-redacted `EvidenceBundle` plus the deterministic diagnosis. Provider output cannot replace the baseline, must cite only submitted evidence IDs, and falls back safely for provider, budget, timeout, or validation failures. See [SHAFT Doctor](/docs/agentic/doctor). ## Safe defaults AI is off unless both settings are changed: ```properties pilot.ai.enabled=false pilot.ai.provider=none ``` Provider models default to blank except `pilot.ai.gemini.model`, which defaults to `gemini-3.5-flash`; remote and local consent default to `false`, and no evidence category is approved by default. Disabled or unavailable AI returns the request's `deterministicFallback` and makes no network call. Credentials are read only from environment variables: | Provider | Default variable | | --- | --- | | OpenAI | `OPENAI_API_KEY` | | Anthropic | `ANTHROPIC_API_KEY` | | Gemini | `GEMINI_API_KEY` | | GitHub Models | `GITHUB_TOKEN` | | Ollama | none | The environment variable names are configurable, but credential values are never SHAFT properties. Provider endpoints and models are configured through `SHAFT.Properties.pilot`; no model is selected implicitly. ## Managed local preview The optional `shaft-ai` module also contains SHAFT's managed-local provider. Set `managedLocalAi.enabled=true` to select it for the current thread and grant local-processing consent. The property defaults to `false`. This setting does not grant remote or on-premises consent, and the provider does not require a credential or caller-selected endpoint. The pin table on the setup page is the documented inventory for revision, license, provenance, size, storage class, resource floors, update policy, cleanup rule, and deterministic fallback. `setup status` and `setup verify` print `target readiness version detail` (version empty unless READY) and report readiness/version/action; see [managed local AI inventory](/docs/start/local-infrastructure/previews#inventory-defaults-and-troubleshooting). Use the review-first configuration while evaluating it: ```properties title="src/main/resources/properties/custom.properties" managedLocalAi.enabled=true managedLocalAi.transparentProvisioning=false managedLocalAi.model=qwen3-0.6b-q8_0 ``` Provision and maintain the reviewed runtime/model pair through the [managed local AI setup flow](/docs/start/local-infrastructure/previews#preview-managed-local-ai). Set `managedLocalAi.transparentProvisioning=true` only when an approved request may provision a missing reviewed pair without a separate CLI install phase. The compact candidate is manual-only because its current recommendation-quality benchmark failed. Keep the request's deterministic fallback authoritative; do not use this preview to enable automatic advisory decisions. ## Approval and evidence Every request carries an `ApprovalPolicy`, and the effective SHAFT properties must independently approve the same processing location and all submitted `EvidenceCategory` values. Remote calls require `pilot.ai.consent.remote=true`. Ollama requires `pilot.ai.consent.local=true`; a loopback endpoint is not trusted automatically. Before provider execution, `AiExecutionService`: 1. checks provider capabilities and availability; 2. enforces request size, token, cost, timeout, concurrency, and circuit-breaker limits; 3. rejects unapproved evidence categories; 4. redacts authorization and cookie headers, password/secret assignments, common tokens, configured DOM selectors, attributes, and patterns; 5. validates provider JSON against the requested schema; 6. returns deterministic fallback for every failure. Audit events contain only request ID, purpose, provider/model identifiers, redaction counts, duration, and status. Prompts, evidence, credentials, raw responses, and original secret values are excluded. ## Example ```java SHAFT.Properties.pilot.set() .enabled(true) .provider("ollama") .localConsent(true) .allowedEvidenceCategories("TEXT") .ollamaModel("your-local-model"); var schema = JsonNodeFactory.instance.objectNode() .put("type", "object"); schema.putObject("properties") .putObject("summary") .put("type", "string"); schema.putArray("required").add("summary"); var approval = new ApprovalPolicy( true, false, Set.of(EvidenceCategory.TEXT)); var request = AiRequest.builder("doctor-summary", schema) .text(redactedDiagnosticText) .approvalPolicy(approval) .deterministicFallback(deterministicDiagnosis) .build(); AiResponse response = new AiExecutionService().execute(request); ``` Call `SHAFT.Properties.clearForCurrentThread()` at lifecycle boundaries after setting per-thread Pilot properties. ## Direct providers and external agents Direct provider adapters are separate from MCP clients. OpenAI, Anthropic, Gemini, and GitHub Models credentials apply only to `shaft-ai` direct calls. GitHub/Microsoft Copilot is supported through SHAFT MCP and retains its own client authentication; SHAFT does not request or define a generic Copilot model API key. Credential-free MCP configuration fixtures are under the [downloadable Pilot example assets](/docs/agentic/pilot#example-assets). See [SHAFT MCP](/docs/agentic/mcp) for transport and deployment details. The representative `doctor_analyze` fixture covers ChatGPT, Codex, Claude, Gemini, and GitHub Copilot without embedding any client or provider credential. Provider request mappings follow the official [OpenAI structured output](https://developers.openai.com/api/docs/guides/structured-outputs), [Anthropic structured output](https://platform.claude.com/docs/en/build-with-claude/structured-outputs), [Gemini structured output](https://ai.google.dev/gemini-api/docs/structured-output), [GitHub Models](https://docs.github.com/en/rest/models/inference), and [Ollama chat](https://docs.ollama.com/api/chat) contracts. ## Related - [Overview](/docs/agentic/overview) - [MCP](/docs/agentic/mcp) - [Pilot](/docs/agentic/pilot) - [Doctor](/docs/agentic/doctor) --- # Record tests with Capture Canonical HTML: https://shafthq.github.io/docs/agentic/capture Markdown: https://shafthq.github.io/md/agentic/capture.md # Record tests with Capture `shaft-capture` defines the provider-neutral intermediate representation used by SHAFT Capture and the managed-browser recorder that produces it. It records browser intent for review, replay, migration, and future Java/JUnit/Cucumber consumers. It generates Java 25 SHAFT/TestNG tests without depending on `shaft-ai`; provider adapters remain optional. Capture creation, validation, serialization, and privacy enforcement work with `pilot.ai.enabled=false`. Optional AI consumers may receive only the already redacted representation after the separate Pilot approval checks succeed. The IntelliJ Assistant and other SHAFT front doors expose this recorder as the `/record` slash command (aliases `/record-web`, `/rec`, `/capture`) and its mobile counterpart as `/record-mobile`. Use `/codegen recordings/flow.json` to generate replay-proven code from an existing persisted recording. In the IntelliJ Assistant, `/codegen ` instead runs the consent-gated AutoBot record, generate, replay, and heal workflow. See [Record and generate a new scenario](/docs/agentic/intellij#record-and-generate-a-new-scenario) for that flow; the persisted-recording route remains unchanged. Generated Capture output follows the [generated locator policy](/docs/reference/actions/GUI/Locators_And_Self_Healing#generated-locator-policy): unique author-written id via `hasAnyTagName().hasId(...)`, then ARIA role, then native relative `By.xpath(...)` only. Record first, confirm the flow with `capture_generate_replay` (`replay=true`) or `verify_run_focused`, then trust the snippets. `test_code_guardrails_check` rejects Smart Locators and raw locator factories. ## Managed browser recording The recorder launches a fresh SHAFT-managed Chrome, Chromium, or Edge session. Firefox and WebKit are rejected with explicit unsupported-browser messages until equivalent event coverage is available. WebDriver BiDi supplies navigation, browsing-context, prompt, and preload-script signals when available. A JavaScript listener drained through ordinary WebDriver provides deterministic interaction capture and remains the compatibility fallback. The recorder always runs the managed browser locally: any configured `executionAddress` (remote Selenium Grid) is intentionally ignored at recording time, because an interactive recording needs the browser on your own screen to click through. Generated tests still honor your configured `executionAddress` when they run. Use the Capture commands on [Connect shaft-mcp](/docs/agentic/mcp#mcp-command-reference). That page owns the runnable MCP command reference and classpath notes. Use `--runtime-dir ` on every command to isolate control files. `stop` also accepts `--discard` (same as `capture_stop` with `discard=true`). Only one recorder may own a runtime directory at a time. The daemon control endpoint is bound to loopback, requires a generated bearer token, and removes its token and descriptor at shutdown. Browser profiles are temporary and removed after normal stop or interruption unless `--user-data-dir ` is supplied. `capture start` also accepts Playwright-codegen-shaped options where SHAFT can map them safely: `--viewport-size`, `--device`, `--color-scheme`, `--geolocation`, `--timezone`, `--block-service-workers`, `--load-storage`, `--save-storage`, `--save-har`, `--test-id-attribute`, `--lang`, `--user-agent`, `--user-data-dir`, `--proxy-server`, `--proxy-bypass`, `--ignore-https-errors`, `--timeout`, and `--session-goal`. Device presets use bundled Chrome/Edge mobile-emulation profiles when available; color scheme, geolocation, timezone, and service-worker bypass use browser protocol support; storage state uses SHAFT's browser storage-state JSON; and HAR output uses the same redacted observability entries as SHAFT failure traces. Unsupported drivers or unmapped device names produce deterministic warnings instead of raw protocol failures. `--save-har-glob` filters the saved HAR to URLs matching the glob and reports how many observed network entries were kept versus dropped. Use `capture features` to list the current Playwright codegen feature map. `--session-goal` stores the recorder intent in session extensions so generated Java can include a safe review comment. ```bash capture start \ --url https://example.test \ --device "Pixel 7" \ --geolocation "30.0444,31.2357" \ --timezone Africa/Cairo \ --color-scheme dark \ --load-storage target/auth-state.json \ --save-storage target/auth-state-out.json \ --save-har target/capture.har ``` The same lifecycle is exposed by the `capture_start`, `capture_status`, and `capture_stop` MCP tools. `capture_start` accepts an optional `sessionGoal` describing the recorded journey; it names the generated test class and method, plus an optional nested `codegenOptions` request (absorbing the former `capture_start_codegen` tool) that replaces the flat targetUrl/browser/outputPath/headless/sessionGoal arguments with the full Playwright-codegen-compatible request when finer control is needed. Within `codegenOptions`, `saveHarContent` leaving blank or `none` keeps today's truncated network-trace preview bodies, while `full` emits complete, redacted request/response bodies (headers redacted, binary content base64-encoded) for every transaction recorded during the session, sourced from the API-capture network events rather than the preview trace. `full` falls back to preview bodies with a warning when no such transactions were recorded, for example when `apiCapture` was not enabled for that session. This option is MCP-only; the local `capture start` CLI does not expose a `--save-har-content` flag. Generation is exposed by `capture_generate`; `capture_codegen_features` returns the feature map. `shaft_coding_partner_plan`, `capture_target_candidates`, `capture_backend_comparison`, and `capture_evidence_pack` cover repository-aware reuse planning, existing-suite targeting, backend comparison, and review evidence manifests. Status contains safe metadata, counts, readiness, and warnings, never typed values; `pendingSignalCount` reports debounced signals (uncommitted typed input, pending clicks) not yet persisted as events, so live monitors do not misread a quiet `eventCount`. Readiness is `READY`, `RISKY`, or `BLOCKED`; risky steps keep recording, while blocked readiness means generated replay will need user action such as a stable locator or required secret input. WebDriver code generation remains the default through `capture_generate`, `capture_generate_replay`, and `capture_code_blocks`. Pass their optional `backend=playwright` parameter (absorbing the former `playwright_capture_generate_replay`/`playwright_capture_code_blocks` tool names) only when the target repository uses `SHAFT.GUI.Playwright` or the user asks for Playwright output; the same tools read the same Capture session format but emit `SHAFT.GUI.Playwright` setup, actions, waits, and assertions. From the local Capture CLI, pass `capture generate --backend playwright` to emit the same Playwright backend from a persisted Capture session; omitting `--backend` keeps WebDriver output. Playwright action recordings are started with `capture_start` against an active Playwright session (`driver_initialize` with `engine=playwright`); `capture_code_blocks` adapts supported recorded actions into a Capture session, returns the generated source/test-data/report/review paths, and keeps the direct replay method for actions that do not yet have a Capture equivalent. When the Assistant's `/codegen` command handles a persisted Playwright recording rather than a Capture session, it initializes a fresh Playwright driver and replays the recording through `capture_generate_replay` (`backend=playwright`), so the returned locators are still validated live before being handed back. Like the mobile recorder, the Playwright recording status speaks the **same vocabulary as the web recorder**: recorded interactions are **steps**, readiness is `READY` / `RISKY` / `BLOCKED` (coordinate-only or redacted-value steps are `RISKY`; a stop with no steps is `BLOCKED`), stopping surfaces the saved session path with a **Review code** next-step, and actions performed while recording is inactive are reported with the same `Ignored: ` transparency note. All three recorders (web, mobile, Playwright) therefore present one status contract. When recording in a visible browser, SHAFT injects a compact Capture panel into the managed Chrome/Edge session. The panel lists captured actions in plain English while the user clicks, types, selects, uploads, or navigates. Its controls pause or resume action capture, add browser or element checkpoints from an in-panel dialog, edit or delete visible action text, reorder captured steps, add a visible assertion from a stored action target, pick locators, and stop the recording. The browser panel and generated capture workbench follow the same SHAFT report visual language as Allure-attached HTML reports, including status chips and wrapping layouts that avoid horizontal scrolling during review. The overlay follows the OS dark theme, aligned with the SHAFT report visual language. ### Record vs inspect mode `capture_set_mode` reads or toggles the active recorder's live authoring mode. `record` (default) captures interactions as replayable steps. `inspect` keeps the session alive for locator picking without appending ordinary click/type steps — required before `capture_pick_locator` / IntelliJ **Pick Locator from Live Session**. Omit `mode` to read the current value. ```bash shaft-cli call capture_set_mode mode=inspect shaft-cli call capture_pick_locator shaft-cli call capture_set_mode mode=record ``` ### Recorder overlay status The status line shows a pill stack at the top of the overlay: - **Readiness pill** (Ready / Risky / Blocked) indicates generation confidence - **Mode pill** (Recording / Paused / Asserting / Picking locator / Stopped) shows the current state - **Steps counter** ("N steps") displays the number of recorded interactions Below the pills, readiness warnings appear on their own line when present. When a navigation occurs within ~10 seconds after a recorded step, it is treated as a consequence of that step and not recorded separately; a brief "Ignored: navigation caused by your last step, kept as part of that step." note appears instead. ### Overlay ergonomics The recorder overlay supports practical interactions: - **First-run coach marks** — On a first recording, a dismissible "New here? 3 quick tips" card orients you (what a step is, assert vs. pick locator, drag/stop). Click **Got it** to dismiss it; the choice is remembered for the page session. - **Dragging and position memory** — Drag the overlay by its header to reposition it on the page. The position is clamped to the viewport boundaries and remembered for the duration of the page session (and re-clamped if you resize the window, so the panel can't end up off-screen). The default position is bottom-right. - **Steps refreshed notice** — When a background re-sync from the saved session changes the visible step list (for example after a cross-origin navigation rehydrated rows), a brief "Steps refreshed from session" note appears instead of the list silently rewriting itself. - **Step kind badges** — Each step row displays a badge indicating the action type: Click, Type, Select, Toggle, Upload, Keys, Navigate, Assert, or Pin. When a step carries a readiness warning, the badge is colored amber for visibility. - **Soft-delete with undo** — Deleting a step from the list is a soft delete with a 5-second undo window. A "Step deleted. Undo" notification appears; click Undo within the window to restore the step. The deletion is only persisted after the window elapses. - **Help sheet** — Click the "?" button in the overlay header to toggle a help sheet listing all recorder controls, including how to drag the overlay, press Esc to cancel assert or pick mode, and access undo. - **IntelliJ status alignment** — The IntelliJ Guided panel's live status mirror the overlay wording: "Recording · N steps · Ready · \ . Stop here or in the browser overlay - both save the session." This makes it clear that stopping from either location (browser or IDE panel) saves the same recording. ### Stopping a recording Pressing stop from the panel opens a confirmation inside the overlay before the session stops. The confirmation displays "Session saved to: \ " (sourced via loopback /session endpoint, best-effort) and prompts next actions — open the SHAFT Assistant in the IDE and use "Review code". Buttons: "Save & close" (stops the session and the browser closes) and "Keep recording" (dismisses the confirmation). If the session ended before a clean stop (the server reports it `INCOMPLETE`), the confirmation switches to recovery copy that reassures every step captured so far was still saved and is safe to review — nothing was lost. Programmatic stops via MCP `capture_stop` are unchanged and close immediately without confirmation. Teams can pin recorder behavior for a whole repository by checking in `.shaft/recorder-policy.json` at the workspace root: ```json { "headless": false, "outputDirectory": "recordings", "browser": "Chrome" } ``` All fields are optional. When the file exists, SHAFT MCP's `capture_start` applies it to every recording started against that workspace — whichever client asked (IntelliJ plugin, agent CLI, raw MCP call): `headless` locks the browser visibility, `outputDirectory` re-roots every recording into the team directory (the requested file name is kept), and `browser` fills the default when a request names none. The IntelliJ Guided panel mirrors the policy visibly: the Headless toggle is applied and locked with a "locked by team policy" tooltip, and the default session path moves into the team directory. A malformed policy file never blocks recording; it just falls back to defaults. For the maintenance loop, the Guided panel's **Weekly flaky triage** template prefills a batch Doctor analysis with historical bundles (`target/shaft-doctor/history`) so repeat offenders surface as trends; follow up with `healer_run_failed_test` per flaky test, and pair the template with a weekly scheduled agent that sends `/doctor` and consolidates the report. The recorder only keeps actions a user actually performed. Browser-synthesized noise is suppressed on both the in-page recorder and the server pipeline: pressing Enter in a form no longer records a phantom click on the form's invisible default submit button, the duplicate `change` re-announcement of an already-recorded typed value no longer appends a second type step, clicks on the bare page background (`body`/`html`) are ignored, and page clicks made while the assertion wizard is open are treated as wizard interaction rather than recorded steps. Use the assertion control to open the guided assertion flow, which offers two deterministic branches. The **Element** branch asks you to click the target element: a prominent top-of-page banner ("Assertion: click the element you want to verify... Press Esc to cancel"), a crosshair cursor, and the hover highlight make the waiting-for-your-click state unmissable, and that pick click is never recorded as a step. It then shows the top scored locator candidates (with a manual XPath or CSS field as an alternative), then the fixed element catalog: exists, visible, enabled, and selected (each with an expected `true`/`false` choice), text equals or contains, attribute equals (with attribute name and expected value fields), image matches, aria snapshot matches (prompts for the baseline snapshot name), and screenshot matches baseline. Negated variants are unsupported for the aria snapshot and screenshot assertions. The **Browser** branch shows the fixed page-level catalog: URL equals or contains, title equals or contains, and page text contains, each with an editable expected value prefilled from the live page. Saved assertions appear in the panel step list and rehydrate across navigations like any other recorded step. The recorder stores these as `VerificationEvent` records. Expected text, URL, title, and attribute values are externalized through the same privacy classifier used for typed data, so generated assertions do not embed captured secrets. Generated GUI assertions use SHAFT assertion builders such as `driver.element().assertThat(...)` and `driver.browser().assertThat()`; do not replace checkpoint notes with raw TestNG or JUnit assertions. Aria snapshot matches and screenshot matches baseline render as `driver.element().assertThat(locator).matchesAriaSnapshot(...)` and `driver.element().assertThat(locator).matchesScreenshot()`. ![SHAFT Capture assertion mode](/img/capture-assertion-mode.png) Use the locator picker control to inspect the target before recording or verifying it. Picker mode highlights the hovered element, opens a ranked candidate list on click, and shows each candidate's strategy, expression, uniqueness count, stability, score rationale, and live probe result (`unique`, `multi-match`, `no match`, or `failed`). Pin a candidate when the deterministic default is not the locator intent; the next captured event for that logical element stores the selected candidate with the `USER_PROVIDED` locator signal, so generation can prefer it without editing generated source. When you select a stable, unique test ID, Capture verifies that it resolves to the exact live element and stores a tag-independent relative XPath such as `//*[@data-testid="result-title-a"]`. Generated WebDriver code uses that verified path through `By.xpath(...)`, so the locator can survive an equivalent markup change such as an ` ` becoming a ` `. Capture rechecks the chosen test ID when you confirm it and when you save the assertion. If the value is now duplicated, changed, removed, or attached to a replacement element, Capture clears the replay evidence and the candidate cannot be promoted. Pinning records your preference among eligible candidates; it never bypasses these checks. Custom names supplied through `--test-id-attribute` are normalized to lowercase for HTML. They must start with an ASCII letter or `_` and contain only ASCII letters, digits, `_`, `.`, or `-`; namespace prefixes are rejected. ![SHAFT Capture locator picker](/img/capture-locator-picker.png) While a capture session is active, `element_*` tools drive the recorded browser directly -- no `driver_initialize` session is needed -- and the driven interactions are recorded like user ones. That makes the scripted agent-performed codegen flow real: `capture_start` with `codegenOptions`, perform the described actions with element tools, `capture_stop`, then generate code from the persisted recording. One-shot agent turns (a CLI that exits after its reply) must only use this same-turn scripted flow; an interactive user-driven recording started from such a turn dies with the turn's MCP process. For agent-driven MCP flows, the intended handoff is: call `capture_start` (with or without `codegenOptions`), let the user interact with the visible browser, wait for either `capture_stop` or a browser-panel stop to complete, then call `shaft_coding_partner_plan` with the user intent, current source path, selected text, and recording/report artifacts. The plan ranks existing Java targets, locator summaries, action summaries, missing code, suggested proof calls, and a focused verification command before code blocks are generated. Then call `capture_code_blocks` (optionally `backend=playwright`), or use `capture_record_at_target_code_blocks` when the plan found a specific insertion target. Only the recording matters for a quick generation: `outputDirectory`, `packageName`, `className`, `overwrite`, and `driverVariableName` are optional and default to `generated-tests`, `tests.generated`, `RecordedFlowTest`, `false`, and `driver` (`page` for Playwright). If a focus or click mistake pollutes the recording, the Assistant discard/re-record commands stop with `discard=true` before starting a fresh capture. The agent should show the generated result and ask whether the user wants the complete Java snippet or wants the agent to insert the code into the current repository. Snippet mode uses the returned Java full-class block, including imports, setup, inline `SHAFT.GUI.Locator.*` locators, SHAFT actions/assertions, and teardown. Insertion mode should reuse the plan's existing Page Object classes when that pattern already exists, or create the smallest matching page/test classes when it does not. The returned code blocks include deterministic Page Object insertion guidance for WebDriver and Playwright captures, including SHAFT locator inventory, action sequence, setup prerequisites for required data and fixtures, assertion suggestions for missing post-navigation or post-submit checks, control-flow review commands, locator confidence queues, validation back-links, and a `capture-page-object-draft` block with locator fields and flow methods when extractable candidates exist. Locator inventory blocks show the selected SHAFT locator expression and ranked alternatives from the generation report. For native mobile journeys, the same `capture_*` tools dispatch to the active mobile session instead of a separate mobile tool family: `capture_code_blocks` returns the replay method, ranked mobile locator inventory, action sequence, and `mobile-page-object-draft`. `capture_record_at_target_code_blocks` adds locator fields and an action snippet for an existing mobile Page Object anchor, plus a preview-only patch block and a locator confidence queue when fallback actions need review. When the Assistant's `/codegen` command handles a persisted mobile recording, it replays it through `capture_generate_replay` against the active mobile session before returning the generated code -- the same live-validation guarantee WebDriver and Playwright recordings get. The mobile recording status speaks the **same vocabulary as the web recorder** rather than a parallel one: recorded interactions are **steps**, readiness is `READY` / `RISKY` / `BLOCKED` (coordinate-only or redacted-value steps are `RISKY`; a stop with no steps is `BLOCKED`), and stopping surfaces the saved session path with a **Review code** next-step. Actions performed while recording is inactive are reported with the same `Ignored: ` transparency note used by the web overlay. The IntelliJ Guided panel's live status mirrors this wording automatically. Use `FLOW_START` and `FLOW_END` checkpoints to mark an explicit reusable flow inside a recording. The checkpoint description becomes the generated helper method name, so a segment marked as `login as admin` generates a `loginAsAdmin()` method and the replay test calls that method at the original point in the journey: ```bash capture checkpoint --kind FLOW_START --description "login as admin" # perform the login steps in the managed browser capture checkpoint --kind FLOW_END --description "login as admin" ``` ```java @Test public void replayCheckout() throws Exception { driver.browser().navigateToURL("https://shop.example/login"); loginAsAdmin(); driver.element().click(SHAFT.GUI.Locator.hasAnyTagName().hasId("checkout-submit").build()); } private void loginAsAdmin() throws Exception { By username = SHAFT.GUI.Locator.hasAnyTagName().hasId("username").build(); driver.element().click(username); driver.element().type(username, requiredData("username")); } ``` For a record-at-target flow, provide the existing Java source and insertion anchor when generating snippets. The CLI accepts `--target-source src/test/java/.../CheckoutTest.java --insert-after replayCheckout` and returns the normal generation result plus a focused insertion plan. MCP agents can call `shaft_coding_partner_plan` or `capture_target_candidates` first, then `capture_record_at_target_code_blocks` to receive separate blocks for SHAFT locator inventory/imports, action lines, and a preview-only patch block. The patch preview shows the apply order, reuses locator fields already present in the target source, and skips exact duplicate action lines before an agent patch is proposed. SHAFT validates that the requested anchor is present when possible, but it never edits the source file until the calling agent performs a separately approved repository change. ## Recorder/codegen implementation Recorder/codegen now turns the main post-recording decisions into reviewable artifacts before any source file is edited. | Rank | Enhancement | User-facing result | | --- | --- | --- | | 1 | Patch preview for record-at-target | `capture_record_at_target_code_blocks` and mobile record-at-target flows return preview-only diff blocks with apply order before an agent applies source edits. | | 2 | Coding partner and target scanner | `shaft_coding_partner_plan` and `capture_target_candidates` suggest likely Page Objects, test classes, package names, driver variables, insertion anchors, locator summaries, action summaries, and the recommended target/anchor from the current repository. | | 3 | Assertion gap checklist | Generated review blocks list missing post-login, post-submit, navigation, and error-state assertions. | | 4 | Locator confidence queue | Weak XPath, multi-match, and coordinate fallback steps are grouped into a fix-first review list. | | 5 | Fixture and secret handoff | Required environment variables, test data, and upload fixtures remain summarized without exposing secret values. | | 6 | Flow grouping assistant | Explicit `FLOW_START` and `FLOW_END` checkpoints become helper-method proposals; repeated groups stay advisory until approved. | | 7 | Replay failure back-links | Validation review blocks point compile or replay failures back to recording step ids and generated code blocks. | | 8 | Backend comparison blocks | `capture_backend_comparison` returns WebDriver and Playwright code-block summaries for the same Capture session. | | 9 | PR evidence pack | `capture_evidence_pack` returns local screenshots, workbench HTML/review paths, generated source paths, and validation commands for review. | | 10 | Guided IDE action copy | IntelliJ Guided templates follow the real flow: plan coding partner work, record, review code, preview patch, apply, and verify. | All process arguments and filesystem paths are built with Java APIs (`ProcessBuilder`, `Path`, and `Files`). No Windows, POSIX shell, or path separator is assumed; restrictive POSIX permissions are applied when supported and otherwise the host filesystem's inherited permissions are used. ## Format Every session has a `schemaVersion`, safe session and browser metadata, ordered events and checkpoints, external test-data references, a redaction summary, and explicit extension maps. The current version is `1.0`; readers migrate the synthetic `0.9` format and reject unsupported versions with an actionable message. The event hierarchy covers: - navigation, click, type, clear, select, check/uncheck, and upload; - keyboard, window/tab, frame, alert, and explicit wait operations; - explicit verification events and replay status. `ElementSnapshot` retains sanitized role, accessible name, label, normalized attributes, visibility state, and `LocatorCandidate` evidence. Candidate scores are deterministic inputs based on strategy, uniqueness, visibility, stability, and recorded signals. No model inference is used to rank locators. The bundled schema is: `shaft-capture/src/main/resources/schema/shaft-capture-session-1.0.schema.json` `CaptureJsonCodec` validates before read and write, emits stable human-readable JSON, preserves explicit extension fields, and never publishes a partially validated recording. ## Privacy boundary `CapturePrivacyClassifier` runs before values enter a `CaptureSession`. Passwords, tokens, configured sensitive fields/selectors/attributes/URL parameters, and configured value patterns produce named environment or secret references with no original value. Ordinary typed data is externalized to `capture-data.json` by default through `ExternalTestDataWriter`. Upload events store a logical fixture reference, sanitized basename, media type, and size. They never retain an arbitrary absolute user path or file contents. Evidence references accept only safe relative paths. Cookies, storage, headers, page source, screenshots, and other evidence are absent unless a later collector explicitly enables a documented category. The persisted redaction summary contains only counts and rule names. It does not contain removed values. ## Lifecycle `CaptureSessionStore` provides thread-safe start, append, checkpoint, interruption, stop, and read operations. Each update serializes and validates a complete snapshot before an atomic replacement. In-progress or crashed sessions remain readable with status `INCOMPLETE`; a normal stop records `COMPLETED` and an end timestamp. Example: ```java var session = CaptureSession.start( "checkout-recording", Instant.now(), browserMetadata); var store = new CaptureSessionStore(Path.of("recordings/checkout.json")); store.start(session); store.append(captureEvent); store.stop(Instant.now()); ``` ## Deterministic TestNG generation Generate a test, SHAFT JSON test data, and a deterministic report with the Capture generation command on [Connect shaft-mcp](/docs/agentic/mcp#mcp-command-reference). The default output layout is: ```text generated-tests/ src/test/java/generated/capture/ Test.java src/test/resources/testDataFiles/ -test.json target/shaft-capture/generation-report.json target/shaft-capture/capture-review.json target/shaft-capture/capture-workbench.html target/shaft-capture/control-flow-preview.json ``` Generation selects locators in the accessibility, label, test-ID, stable ID/name, CSS, then XPath family. The report records the score contribution from uniqueness, visibility, interactability, semantic match, volatility, frame/shadow context, and replay evidence, plus ranked fallbacks. Stable user-provided locators pinned from the recorder overlay can outrank the deterministic default. The review file summarizes deterministic readiness, blockers, risks, typed findings, and next suggestions; MCP generation results expose the same path as `reviewPath` and return the deterministic review warnings in the tool result. Static review findings cover brittle absolute or index-heavy locators, missing post-navigation or post-submit assertions, fixed-duration waits, and sensitive JSON-backed test data. When replay fails and a SHAFT trace exists, Capture maps the failure back to the generated step and failed trace action, and flags failing network/API calls as candidates for HTTP contract replay. Generated class and method names prefer explicit user options, then the recorded `sessionGoal` ("Log in as a valid user" yields `logInAsAValidUser()`), then checkpoint descriptions, page titles, URL paths, and finally the opaque session id. Externalized-secret environment variables use stable names derived from the logical field (`SHAFT_CAPTURE_DATA_PASSWORD`), so re-recording the same journey never shifts the variable names a replay environment provides. Edited step text from the recorder panel is preserved as a generated Java comment so reviewers can keep the user's intent beside the replay statement. Generated source also starts with a safe review header containing readiness, event count, fallback-locator count, and the optional `sessionGoal` comment when provided. The workbench HTML is a local review UI for building record/checkpoint commands, checking blockers and required inputs, reviewing assertion gaps, locator decisions, a Page Object draft, copyable CLI/MCP command starters, the generated code-block summary, deterministic control-flow suggestions, generated source through the browser file picker or download fallback, and the Playwright codegen feature map beside the generated code. Use `capture_backend_comparison` when reviewers need WebDriver and Playwright block summaries side by side, and `capture_evidence_pack` to return the local source, report, review, screenshot, and validation-command manifest for a PR. Add `--enable-fallback-locators` when generating WebDriver replay code to make the generated test try ranked captured alternatives before failing a target lookup. The selected locator remains the first candidate; when ranked alternatives exist, generated source imports `org.openqa.selenium.By` and emits a compact `captureReplayLocator(By primary, By... alternatives)` helper. The helper probes the primary locator first with `driver.element().getElementsCount` and then returns the first alternative with a current match; if none match, it returns the primary locator so SHAFT fails on the intended target. ```java driver.element().click(captureReplayLocator( SHAFT.GUI.Locator.hasAnyTagName().hasId("username").build(), By.xpath(".//form//input[@name='username']"))); ``` Use `--control-flow-preview` to write deterministic suggestions for common non-linear browser journeys without changing the generated replay. Capture flags adjacent identical action groups, likely optional modal or banner close actions, and recovery-like steps after failed or skipped recorded interactions. The same suggestions appear in `generation-report.json` under `controlFlowSuggestions` and in MCP warning results as `review/CONTROL_FLOW` findings. ```bash capture generate --session recordings/checkout.json --control-flow-preview capture generate --session recordings/checkout.json \ --apply-control-flow-preview generated-tests/target/shaft-capture/control-flow-preview.json ``` Default generation stays linear. Applying a reviewed preview only changes approved optional close actions by adding an if-displayed guard: ```java if (isCaptureElementDisplayed(COOKIE_CLOSE_BUTTON_LOCATOR)) { driver.element().click(COOKIE_CLOSE_BUTTON_LOCATOR); } ``` Repeated groups and recovery paths remain review suggestions until the user marks explicit flow checkpoints or edits the generated test. Example review finding: ```json { "category": "LOCATOR", "severity": "WARNING", "summary": "Brittle XPATH locator selected for pay-button: /html/body/div[3]/form/button[2].", "evidenceIds": ["event-4"], "recommendation": "Prefer semantic locator text \"Pay now\" when unique." } ``` Example status payload: ```json { "state": "ACTIVE", "eventCount": 12, "readiness": "RISKY", "warnings": ["Step 7 uses generated positional CSS locator for pay-button."] } ``` Ordinary values are copied from the recording's external JSON into the generated test-data file. Secret and sensitive references become required environment variables and are typed securely. Uploads remain relative fixture references under `src/test/resources`. Missing data is reported as required user input. Captured credentials, cookies, authorization values, and absolute personal paths fail the privacy scan instead of entering generated artifacts. Every generated class creates a fresh driver in `@BeforeMethod` and calls `driver.quit()` from `@AfterMethod(alwaysRun = true)`. Only explicit `VerificationEvent` records become assertions. An `ASSERTION` checkpoint must point at a verification event; unsupported steps fail generation with their event IDs and remediation. Checkpoint notes alone do not satisfy missing assertion warnings. A matched `FLOW_START`/`FLOW_END` pair does not infer abstractions from similar steps; only the explicitly marked events move into a reusable helper method. Compilation is enabled by default. Add `--replay` to run the compiled TestNG class in an isolated process and require populated, passing Allure result files. Existing source, data, report, or preview files are never replaced unless `--overwrite` is supplied. MCP replay code follows the selected backend: WebDriver tools keep `SHAFT.GUI.WebDriver` and Playwright tools generate `SHAFT.GUI.Playwright`. AI enrichment is optional and uses two phases for native CLI users: preview with explicit processing approval, then apply the reviewed fingerprinted preview. Use the canonical Capture command reference on [Connect shaft-mcp](/docs/agentic/mcp#mcp-command-reference) when running it. The provider may suggest Java names and captured-state assertions only. It cannot replace deterministic locators. Preview output is schema-validated and privacy-scanned; apply rejects stale fingerprints, invalid identifiers, unknown events, and assertions that contradict captured state. Accepted changes are compiled and replayed again. When an MCP client calls Capture or Doctor AI-enabled tools, SHAFT treats that tool call as the agent approval boundary for sharing the already redacted local evidence with the calling agent. If no configured provider/API key is available, MCP results still include agent handoff blocks so the MCP client can use its own LLM and repository context. Native terminal commands keep the explicit provider and `--allow-local-ai` or `--allow-remote-ai` approval requirements. Managed local AI remains opt-in and local-consent only; see [managed local AI inventory](/docs/start/local-infrastructure/previews#inventory-defaults-and-troubleshooting). Run the focused suite with: ```bash mvn -pl shaft-capture -am test ``` The real-browser recording and generated-replay suites are opt-in: ```bash mvn -pl shaft-capture -am test \ -DincludeCaptureBrowserE2E=true \ -Dtest=ManagedCaptureRecorderBrowserTest,CaptureGeneratedReplayBrowserTest ``` ## Record web API traffic SHAFT can capture HTTP request and response traffic during browser-based test execution. API traffic recording requires Chrome or Edge browsers with DevTools Protocol (CDP) support and is disabled by default. ### Browser requirements - **Supported:** Chrome, Chromium, Edge (any version with DevTools Protocol) - **Not supported:** Firefox, Safari, WebKit (pending CDP-equivalent event coverage) When recording with an unsupported browser, the capture session completes without network events; no error is raised. Use the `capture.api.enabled` property to enable API capture for the current run. ### Configuration properties API capture behavior is controlled by the following properties (all optional with sensible defaults): | Property | Default | Description | |----------|---------|-------------| | `capture.api.enabled` | `false` | Enable or disable automatic capture of web API network traffic | | `capture.api.maxBodyBytes` | `1048576` | Maximum request/response body size in bytes (1 MB); larger bodies are truncated | | `capture.api.includeAssets` | `false` | Include asset requests (images, CSS, fonts); false captures only API calls | | `capture.api.firstPartyOnly` | `true` | Capture only first-party API calls from the primary domain | | `capture.api.storeSecretsLocally` | `false` | Store authorization headers and sensitive data inline (false = use body externalization via bodyRefId) | Example configuration: ```properties capture.api.enabled=true capture.api.maxBodyBytes=1048576 capture.api.includeAssets=false capture.api.firstPartyOnly=true capture.api.storeSecretsLocally=false ``` ### Privacy and secret handling By default, `capture.api.storeSecretsLocally=false` ensures that authorization headers and sensitive data are stored via `bodyRefId` external references rather than inline in the capture session. This preserves privacy while keeping network traffic visible for debugging. Request and response bodies are stored separately from the session JSON; each network transaction record contains a `bodyRefId` field that references the externalized content. When `capture.api.storeSecretsLocally=true`, headers are captured inline for testing purposes; this should be used only in development environments and never in production scenarios. This mode stores full request/response content inline instead of using external references. ### Recording API traffic via MCP Use the `capture_api_start` tool to begin recording with API capture enabled: ```json { "tool": "capture_api_start", "arguments": { "targetUrl": "https://example.test", "browser": "chrome", "headless": "true", "recordNetworkActivity": true, "excludeAssetTypes": "", "includeOnlyDomains": "", "excludePatterns": "", "sanitizeHeaders": true } } ``` Monitor the recording with `capture_api_status`: ```json { "tool": "capture_api_status", "arguments": {} } ``` Query captured API transactions with `capture_api_transactions`: ```json { "tool": "capture_api_transactions", "arguments": { "limit": 0, "includeAssets": false } } ``` This returns the list of HTTP request/response pairs recorded so far, including request method, sanitized URL, response status, body sizes, and `bodyRefId` references to externalized request/response content. Stop the recording with `capture_api_stop`: ```json { "tool": "capture_api_stop", "arguments": { "stop": true, "discard": false } } ``` ### IntelliJ Record API (Web) action The IntelliJ IDEA plugin provides a **Record API (Web)** action that: 1. Opens a fresh managed Chrome session with API capture enabled 2. Shows the Capture panel with both browser interactions and API calls listed 3. Captures request and response traffic according to configured properties 4. Stores secret/authorization headers as environment variable references by default (respects `capture.api.storeSecretsLocally`) 5. Stops when the user clicks the panel's stop button or closes the browser Access the action through: - **IDE menu:** IntelliJ IDEA > SHAFT > Record API (Web) - **Run configuration:** Select the "Record API (Web)" run type The recorded session is stored in the project's `target/shaft-capture/` directory and ready for immediate generation or further review. ### Chrome/Edge requirement (HasDevTools) API traffic capture is only available for browsers with DevTools Protocol support. The IntelliJ Record API (Web) action will work only with Chrome and Edge. This limitation is enforced by the `HasDevTools` browser capability check. ### Code generation from API recordings After a successful `capture_api_stop`, generate test code using `capture_generate_replay`: ```json { "tool": "capture_generate_replay", "arguments": { "sessionPath": "recordings/checkout-with-api.json", "outputDirectory": "generated-tests", "packageName": "com.example.api", "className": "CheckoutApiTest" } } ``` The generated test class includes: - Browser navigation and interaction events (same as regular Capture) - Executable `SHAFT.API` request/response calls with correlated values chained through variables - External test-data references for typed body values - Request/response body files linked as supporting artifacts #### Response leaf picker Before generating, call `capture_api_response_leaves` on the saved session path to list classified JSON leaves per transaction (`stable`, `volatile`, `sensitive`) without writing test source. Sensitive values stay redacted. Use that list as a pin-this-path picker, then pass the chosen session into `capture_api_generate`. ```bash shaft-cli call capture_api_response_leaves \ sessionPath=recordings/checkout-with-api.json ``` #### Response validation depth `capture_api_generate` accepts a `validationDepth` controlling how thoroughly each recorded response is asserted: - **`STATUS`** — asserts only the recorded HTTP status code. - **`STATUS_HEADERS`** — status plus every stable response header (sensitive/volatile headers skipped). - **`SCHEMA`** (default) — status plus a `matchesSchema` assertion against a schema inferred from the recorded body. - **`FULL_BODY`** — status plus a normalized golden-file body assertion (volatile/correlated leaves placeholdered, sensitive leaves masked). - **`BUSINESS`** — status plus a **targeted value assertion on each stable business field**, pinned by JSON path via `getResponseJSONValue`. Volatile (generated IDs/timestamps), correlated (chained), and sensitive (secret) leaves are deliberately skipped, so the generated test asserts the business-meaningful values a human would check — without flaking or leaking secrets. This is the assertion model for chained business-scenario tests. ### OpenAPI coverage When an OpenAPI/Swagger spec is supplied, generation also reports coverage of the spec's operations against the recorded API calls, so gaps are visible before review. ### Native mobile API capture For device traffic captured outside a browser, the same `capture_api_start`/`capture_api_status`/`capture_api_stop` tools dispatch to a loopback MITM proxy (standalone, needing no live driver session) when an explicit `mobilePlatform` is supplied or a mobile engine is active -- absorbing the former standalone `mobile_api_record_start`/`_status`/`_stop`/`_transactions` tools -- and persist a capture session you can generate from with the same tools. Its status shares the recorder glossary: the recorded unit is a **transaction**, readiness is `READY` / `RISKY` / `BLOCKED` (pairing or dropped-transaction warnings are `RISKY`; a stop with no transactions is `BLOCKED`), the saved session path is always visible, and stopping surfaces a **Review code** next-step. ## Related - [Overview](/docs/agentic/overview) - [MCP](/docs/agentic/mcp) - [Pilot](/docs/agentic/pilot) - [Doctor](/docs/agentic/doctor) --- # Recover locators with Heal Canonical HTML: https://shafthq.github.io/docs/agentic/heal Markdown: https://shafthq.github.io/md/agentic/heal.md # Recover locators with Heal SHAFT Heal is an optional, action-scoped WebDriver locator recovery provider. It is disabled by default and is delivered by `io.github.shafthq:shaft-heal`. The required `shaft-engine` artifact owns the integration SPI and continues to throw the original locator failure when the provider is absent, rejects every candidate, or encounters an internal error. ## Add and enable the module Import `shaft-bom`, then add the optional module beside `shaft-engine`: ```xml io.github.shafthq shaft-heal ``` Enable deterministic recovery explicitly: ```properties healing.strategy=shaft-heal ``` Run the test suite with the strategy enabled: `mvn test '-Dhealing.strategy=shaft-heal'` When the original web locator fails, SHAFT evaluates eligible candidates and records the selected candidate, confidence, alternatives, and rejection reason under `target/shaft-heal/reports`. The equivalent current-thread override is: ```java SHAFT.Properties.healing.set() .strategy("shaft-heal") .minimumConfidence(0.75) .ambiguityMargin(0.10); ``` SHAFT first attempts the original locator. Recovery runs only after a web locator-not-found result, and the engine executes the action only after the provider returns exactly one validated element. ## Strategy and Healenium coexistence | Configuration | Effective behavior | | --- | --- | | `healing.strategy=disabled` | No SHAFT Heal. Legacy `heal-enabled=true` still enables Healenium for compatibility. | | `healing.strategy=healenium` | Healenium only. | | `healing.strategy=shaft-heal` | SHAFT Heal only, even when `heal-enabled=true` remains in an older property file. | | `healing.strategy=composite` | Healenium driver wrapping plus SHAFT Heal fallback. | Use `healing.strategy=composite` only after validating both systems against the same application. It is an explicit opt-in because both providers can add latency and maintain separate histories. ## Deterministic decision policy The provider stores a successful element fingerprint and later discovers a bounded candidate set from stable semantic evidence: - accessible name and associated label - configured test IDs - stable ID and name - role, type, placeholder, title, alt, and autocomplete - visible text - a checksum of the structural DOM path Each evidence category is scored separately. A candidate is used only when it is unique, remains in the requested frame or shadow context, satisfies the action preconditions, meets `healing.minimumConfidence`, and leads the next eligible candidate by at least `healing.ambiguityMargin`. Ties and low confidence preserve the original failure. ## Trust threshold and warnings `healing.minimumTrustPercentage` is the preferred confidence gate for teams that want a human-readable percentage. Leave it at `-1` to keep using `healing.minimumConfidence`; set it from `0` to `100` to require that minimum trust before any recovered locator can be used. ```properties healing.strategy=shaft-heal healing.minimumTrustPercentage=85 ``` The deterministic score must pass this gate before optional provider metadata is considered. Visual or AI provider scores can rank eligible candidates, but they cannot rescue a locator candidate that failed the deterministic trust threshold. When SHAFT accepts a healed locator, it logs a warning and attaches the warning to the report. The warning includes the broken locator, healed locator, trust percentage, configured threshold, provider status, and evidence summary. Common secret-looking tokens are redacted before logging. ## History and reports History is local, versioned, checksummed, bounded, retention-limited, and written atomically: ```properties healing.history.enabled=true healing.history.path=target/shaft-heal/history.json healing.history.maxEntries=500 healing.history.retentionDays=30 ``` Attempt reports are attached to Allure and written below `target/shaft-heal/reports`. Reports include the failed locator, normalized failure category, ranked candidates, per-category scores, selected candidate, decision reason, provider metadata, and post-action outcome. Runtime code can inspect the current thread's latest report with `ShaftHeal.lastReport()`. Successful original resolutions seed history. A recovered fingerprint replaces the old record only after the engine reports that the intended action succeeded. ## Privacy and optional providers SHAFT Heal uses a whitelist-only evidence model. It does not collect input values, cookies, authorization headers, full page source, or the full DOM. Password fields omit text, labels, accessible names, placeholders, and titles. URLs exclude query strings and fragments, and common token or credential patterns are redacted. Visual comparison is local and disabled by default: ```properties healing.visual.enabled=false ``` When enabled, add `shaft-visual`. Its `HealingVisualProvider` compares the stored element screenshot with candidate screenshots. Visual scores remain separate and cannot bypass the deterministic confidence threshold. AI reranking is also disabled by default: ```properties healing.ai.enabled=false healing.ai.trigger=ambiguous ``` AI uses the provider-neutral controls from `shaft-pilot-core`. Add `shaft-ai` only for direct OpenAI, Anthropic, Gemini, or Ollama calls, and configure the Pilot approval, evidence, budget, timeout, and provider properties separately. Managed local AI stays off by default and is local-consent only; see [managed local AI inventory](/docs/start/local-infrastructure/previews#inventory-defaults-and-troubleshooting). Only minimized candidate records are eligible for transfer. Provider output may rerank supplied candidate IDs but cannot invent an element or override deterministic acceptance. The default trigger keeps AI fallback deterministic first: reranking runs only for ambiguous or below-threshold candidate sets. Use `never`, `below-threshold`, or `always` when a stricter or broader trigger is needed. ## Source changes and limits SHAFT Heal never edits test source during runtime. It always reports `sourcePatchProposed=false`. Turning on `healing.sourcePatch.enabled=true` only unlocks a separate, review-gated Doctor proposal workflow; it never patches Java while a test is running. ### Reviewed locator proposals (Doctor MCP) After a Heal report exists under `target/shaft-heal/reports`, enable the consent gate and call the Doctor MCP tools (or `shaft-cli call`): ```properties healing.sourcePatch.enabled=true ``` | Tool | When to use | What it writes | | --- | --- | --- | | `doctor_propose_healed_locator` | Verified recovery (`RECOVERED`) mapped to exactly one Java expression in an allowlisted source file | Reviewable replace proposal under `target/shaft-doctor/healing-proposals` | | `doctor_propose_advisory_locator` | Low-trust / `BELOW_THRESHOLD` report | Review comment proposal only; never replaces the locator | Both tools require explicit `sourcePatchConsent=true`, never edit the live worktree, and never publish. Manifests under `target/shaft-doctor/healing-proposals` (`healing-locator-proposal-*.json` / `healing-locator-advisory-*.json`) are Heal locator proposals, not Doctor repair proposals. Do not pass them to `publish-draft-pr`. To open a draft PR from an approved *replace* proposal (`doctor_propose_healed_locator`): 1. Map the proposal's `patch` object into a `repair-input.json` `patches` array (same `path`, `operation`, `content`, `rationale`, and `evidenceIds`), and add approved Maven `validationCommands`. 2. Run `doctor analyze`, then `doctor propose-fix` with `--diagnosis`, `--base-sha`, `--issue`, `--allowed-path`, and `--repair-input`. 3. After review, run `publish-draft-pr` on the *repair* manifest (`repair-proposal-*.json`) and its approval token. Advisory proposals stay review comments only. Full CLI shape lives on [Diagnose failures with Doctor](/docs/agentic/doctor#reviewed-repair-proposals) and in the [MCP command reference](/docs/agentic/mcp#mcp-command-reference). ```bash shaft-cli call doctor_propose_healed_locator \ repositoryRoot=. \ healingReportPath=target/shaft-heal/reports/ .json \ sourcePath=src/test/java/com/example/LoginPage.java \ sourcePatchConsent=true shaft-cli call doctor_propose_advisory_locator \ repositoryRoot=. \ healingReportPath=target/shaft-heal/reports/ .json \ sourcePath=src/test/java/com/example/LoginPage.java \ sourcePatchConsent=true ``` The current provider supports web element actions in the active browsing context. Native mobile recovery is excluded. Frame identity is retained, and shadow-content recovery is supported while the configured shadow host remains resolvable. A changed frame locator or shadow-host locator preserves the original failure. ## Related - [Overview](/docs/agentic/overview) - [MCP](/docs/agentic/mcp) - [Pilot](/docs/agentic/pilot) - [Doctor](/docs/agentic/doctor) --- # SHAFT Pilot Canonical HTML: https://shafthq.github.io/docs/agentic/pilot Markdown: https://shafthq.github.io/md/agentic/pilot.md # SHAFT Pilot SHAFT Pilot combines deterministic browser Capture, TestNG generation, failure diagnosis, reviewed repair proposals, and MCP interoperability. AI is optional and disabled by default. Capture, generation, Doctor, and MCP remain usable without a provider account, API key, model, or network call. ## Modules | Module | Purpose | | --- | --- | | `shaft-pilot-core` | Approval, redaction, budget, schema, audit, and deterministic fallback contracts. | | `shaft-capture` | Managed Chrome/Edge recording and deterministic TestNG generation. | | `shaft-doctor` | Portable evidence, deterministic diagnosis, and isolated reviewed repair proposals. | | `shaft-ai` | Optional direct OpenAI, Anthropic, Gemini, GitHub Models, and Ollama adapters. | | `shaft-mcp` | Executable stdio and Streamable HTTP server plus Capture and Doctor CLI. | Library consumers should import `shaft-bom` and add only the modules they use: ```xml io.github.shafthq shaft-bom ${shaft.version} pom import io.github.shafthq shaft-capture io.github.shafthq shaft-doctor ``` Add `shaft-ai` only for direct provider calls. External ChatGPT, Codex, Claude, Gemini, and GitHub Copilot clients use SHAFT through MCP and keep their own authentication. ## Install the MCP server Use [Connect shaft-mcp](/docs/agentic/mcp) for installer, client configuration, local command, and Streamable HTTP setup. The [MCP command reference](/docs/agentic/mcp#mcp-command-reference) is the only page that carries runnable MCP command examples. ## Capture example Start a privacy-filtered recording from the [MCP command reference](/docs/agentic/mcp#mcp-command-reference). Omit `--headless` when a person will drive the browser locally. Drive the visible browser or the automation controlling the headless session, optionally add a checkpoint, then stop and generate from the same canonical command page. Generation writes Java source, externalized ordinary test data, and `target/shaft-capture/generation-report.json`. Passwords, configured sensitive fields, credential-shaped values, private paths, and unsafe locator evidence are excluded or redacted. Replay succeeds only when the generated project compiles, passes, and produces populated Allure result JSON. During recording, SHAFT writes actions incrementally so a cross-domain navigation does not discard earlier steps. Text entry is consolidated into a single type action until it is committed or followed by a special key, while keys such as Enter and Tab remain separate actions. The recorder overlay lets you edit, delete, reorder, and add visible assertions for captured steps, and deleting a typed step also removes its externalized test-data reference. Each action keeps sanitized page context and a DOM snapshot so code generation can rank semantic, stable locators and honor a pinned locator preference. Stopping the recording closes the managed browser, saves the session, and returns the next code-generation request as a fenced block, ready to send. ## Doctor example Analyze only explicitly allowed evidence with the Doctor command on [Connect shaft-mcp](/docs/agentic/mcp#mcp-command-reference). Outputs include `doctor-evidence.json`, `doctor-report.json`, and `doctor-report.md`. The deterministic rules classify product, test, locator, data, timing/synchronization, environment/configuration, infrastructure, and unknown failures. Empty and retry-only evidence remains visible instead of being converted into a false success. ## Reviewed repair example Create a complete reviewed input based on `examples/shaft-pilot/doctor/repair-input.json`, then use the reviewed repair commands from [Connect shaft-mcp](/docs/agentic/mcp#mcp-command-reference). Doctor creates and validates a temporary isolated worktree. It does not modify the current branch or write to GitHub. After reviewing the diff and validation result, publish only a draft pull request with the exact returned token. Full-file repair stays on the Doctor CLI (`doctor propose-fix`, `doctor publish-draft-pr`). Those names are not MCP tools. Locator-only Heal proposals use `doctor_propose_healed_locator` / `doctor_propose_advisory_locator` instead. Publication cannot merge, bypass branch protection, or proceed without separate explicit approval. ## MCP clients For Codex, Claude, or GitHub Copilot, choose the matching application command in [Connect shaft-mcp](/docs/agentic/mcp). The installer resolves the latest Maven Central release, verifies the JAR, and updates the per-user local stdio configuration without requiring you to edit a client configuration file. Use the [remote-client section](/docs/agentic/mcp#remote-clients) for Streamable HTTP setup. The endpoint is `/mcp` and the default port is `8081`. ChatGPT developer mode/apps and cloud agents cannot launch a local JAR, so deploy the container and expose `/mcp` over HTTPS. GitHub Copilot is an MCP client integration; it is not a direct provider API-key adapter. External client capabilities were verified against official documentation on June 12, 2026: | Client | Supported SHAFT connection | Current limitation or control | | --- | --- | --- | | ChatGPT apps/developer mode | Public HTTPS Streamable HTTP `/mcp`. See [Connect from ChatGPT](https://developers.openai.com/apps-sdk/deploy/connect-chatgpt). | Hosted ChatGPT cannot start the local JAR; workspace plan and administrator controls apply. | | Codex CLI and IDE extension | Local stdio or Streamable HTTP, configured by CLI or shared `config.toml`. See [Codex MCP](https://developers.openai.com/codex/mcp). | Tool approval remains a Codex client policy. | | Grok CLI | Local stdio via `~/.grok/config.toml` (`[mcp_servers.shaft-mcp]`). Install with `--client grok`. Autobot ASK/PLAN stay read-only; AGENT mutates only with explicit approval and never defaults to `--yolo`. | Grok already loads `AGENTS.md` and `.agents/skills/`; do not add a second policy tree. | | Claude Code and Desktop | Local stdio; Claude Code also supports HTTP and can import Desktop configuration. See [Claude Code MCP](https://docs.anthropic.com/en/docs/claude-code/mcp). | The Claude Messages API uses its separate remote [MCP connector](https://docs.anthropic.com/en/docs/agents-and-tools/mcp-connector). | | Gemini CLI | Local stdio, SSE, or Streamable HTTP through `settings.json`. See [Gemini CLI MCP](https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md). | Server trust and tool confirmation are controlled by Gemini CLI. | | GitHub Copilot | MCP-capable IDEs and CLI can use local or remote servers; cloud agent uses repository MCP configuration. See [GitHub Copilot MCP](https://docs.github.com/en/copilot/concepts/context/mcp). | Cloud agent/code review currently consume tools only, and organization policy may restrict MCP. | ## Optional providers Safe defaults: ```properties pilot.ai.enabled=false pilot.ai.provider=none pilot.ai.consent.local=false pilot.ai.consent.remote=false pilot.ai.allowedEvidenceCategories= ``` Download the [OpenAI](/examples/shaft-pilot/providers/openai.properties), [Anthropic](/examples/shaft-pilot/providers/anthropic.properties), [Gemini](/examples/shaft-pilot/providers/gemini.properties), [GitHub Models](/examples/shaft-pilot/providers/github.properties), or [Ollama](/examples/shaft-pilot/providers/ollama.properties) example. Remote providers read credentials only from their configured environment variable names. Ollama needs no credential but still requires local consent. Remote consent, local consent, and every evidence category are denied unless explicitly enabled. Provider output is advisory. It cannot replace deterministic diagnosis, apply a Capture enrichment without review, or publish a repair without a second approval. Timeout, rate limit, authentication, malformed JSON, schema failure, budget exhaustion, and provider unavailability all preserve deterministic output. ## Privacy and security - Review recordings, externalized data, Doctor bundles, and repair manifests before sharing them. - Screenshots and page snapshots are excluded from Doctor unless explicitly requested. - Remote provider sharing requires explicit consent after minimization and redaction. - Credentials remain in environment variables or provider-native client authentication and must not be stored in SHAFT properties, examples, logs, generated tests, or Allure attachments. - Pilot audit events contain provider/model identifiers, redaction counts, duration, and status, but not prompts, evidence, credentials, or raw model responses. ## Troubleshooting | Symptom | Resolution | | --- | --- | | MCP process exits or prints non-protocol output | Use Java 25, launch with the installer-generated argfile or thin classpath, and keep stdio logs on stderr. | | HTTP client cannot connect | Start with `--spring.profiles.active=http`, expose port `8081`, and use `/mcp`. | | Capture cannot start | Confirm Chrome or Edge is installed, no prior Capture session owns the runtime directory, and use `--headless` in CI. | | Generated test does not replay | Read `generation-report.json`; fix missing external data or an unsupported/ambiguous locator before regenerating. | | Doctor reports incomplete evidence | Provide populated `*-result.json` Allure files and set the correct `--allowed-root`. | | Provider is unavailable | Keep the deterministic result; verify enablement, consent, model, endpoint, budget, and environment variable name before retrying. | | Repair proposal is rejected | Use an exact base SHA, repository-relative allowlist, complete file content, tokenized Maven commands, and a clean isolated worktree. | See [SHAFT Capture](/docs/agentic/capture), [SHAFT Doctor](/docs/agentic/doctor), [optional AI providers](/docs/agentic/providers), and [SHAFT MCP](/docs/agentic/mcp) for the complete contracts. ## Example assets Credential-free example assets for the commands above — review and replace paths/content before reusing any of them: - [`providers/ollama.properties`](/examples/shaft-pilot/providers/ollama.properties) — load as SHAFT properties to try the optional local Ollama advisory after reviewing the [provider controls](/docs/agentic/providers). The deterministic diagnosis remains the baseline if Ollama is unavailable or returns invalid output. - [`doctor/repair-input.json`](/examples/shaft-pilot/doctor/repair-input.json) — use only after replacing its paths and content with a reviewed change. Doctor repair creates an isolated worktree and does not publish to GitHub without a separate approval token. - [Doctor chat invocations](/examples/shaft-pilot/mcp/doctor-analyze-invocations.json) — a credential-free Doctor example. - [OpenAI](/examples/shaft-pilot/providers/openai.properties), [Anthropic](/examples/shaft-pilot/providers/anthropic.properties), [Gemini](/examples/shaft-pilot/providers/gemini.properties), and [GitHub Models](/examples/shaft-pilot/providers/github.properties) property examples for the other optional providers. ## Related - [Overview](/docs/agentic/overview) - [MCP](/docs/agentic/mcp) - [Doctor](/docs/agentic/doctor) - [Providers](/docs/agentic/providers) --- # shaft-cli command line Canonical HTML: https://shafthq.github.io/docs/agentic/cli Markdown: https://shafthq.github.io/md/agentic/cli.md # shaft-cli command line `shaft-cli` is an MCP client and command-line interface to [shaft-mcp](/docs/agentic/mcp). It brings the full shaft-mcp tool set to the command line with zero tool-logic duplication, operating in two modes: stateless one-shot commands and a persistent session mode for stateful tools. ## Install Use the standalone `--install-shaft-cli` command in [Install SHAFT agent tools](/docs/agentic/overview#install-shaft-agent-tools). It installs shaft-cli and the required shaft-mcp runtime automatically without configuring an MCP client. No local Java or Python is required. The installer downloads and SHA-256-verifies `shaft-cli- .jar` from Maven Central, then writes a runnable launcher (`shaft-cli` on macOS/Linux, `shaft-cli.cmd` on Windows) plus a `shaft-cli.args` Java argfile next to it, under the platform's application-data root: - Windows: `%LOCALAPPDATA%\ShaftHQ\shaft-cli\versions\ \` - macOS: `~/Library/Application Support/ShaftHQ/shaft-cli/versions/ /` - Linux: `${XDG_DATA_HOME:-~/.local/share}/shafthq/shaft-cli/versions/ /` shaft-mcp is an internal prerequisite that the standalone installer provides. shaft-cli locates it in this order: 1. The `SHAFT_MCP_JAR` environment variable 2. The installer's versions directory 3. A sibling `../shaft-mcp/target` (development checkout) ## Modes **One-shot mode** (default): each command spawns an ephemeral shaft-mcp child process over stdio, runs a single tool call, and exits. Suitable for stateless tools like guide search and doctor analysis. **Session mode**: `shaft-cli session start` launches a persistent shaft-mcp daemon (HTTP on a localhost port) and records the session endpoint to `~/.shaft/cli-session.json`. Subsequent commands connect over HTTP, preserving browser and device state across invocations. Commands that need live state (`browser`, `element`, `capture`) fail fast when no session is running, unless `--stdio-ok` is passed. Routing: if a live session exists, commands use HTTP; otherwise a one-shot stdio child is spawned. ## Commands All commands support `-h`/`--help`; the root supports `-V`/`--version`. **`shaft-cli tools [--json]`** — list all available tools (name and first sentence of description). `--json` outputs the raw `tools/list` result. **`shaft-cli call [key=value ...] [--args ' '] [--json] [--stdio-ok]`** — invoke any tool by name. Arguments come from repeated `key=value` pairs (values are coerced: `true`/`false` to booleans, integers/decimals to numbers, `{...}`/`[...]` to JSON, otherwise strings) and/or `--args '{"k":"v"}'` (`key=value` overrides keys from `--args`). `--json` outputs the raw JSON-RPC result instead of rendered text. **`shaft-cli session start | status | stop`** — manage the daemon. `start` prints the port and pid; `status` prints `running — port , pid , started `. **Curated aliases** (same options as `call`): - `shaft-cli browser navigate|screenshot|dom|url` (session required) - `shaft-cli element click|type|hover` (session required) - `shaft-cli capture start|stop|status|code` (session required) - `shaft-cli guide search` (stateless) - `shaft-cli doctor analyze|suggest` (stateless) **`shaft-cli setup ...`** — diagnose local prerequisites and create an exact, reviewable plan for SHAFT-owned tools. Setup commands run directly and do not require an MCP session. Follow the [local infrastructure setup guide](/docs/start/local-infrastructure) for the plan, digest approval, policy options, readiness exit codes, and current provider coverage. For managed local AI, `shaft-cli setup status --profile LOCAL_AI --mode MANAGED` and `shaft-cli setup verify --profile LOCAL_AI --mode MANAGED` print `target readiness version detail`. Version is empty unless READY. Commands report readiness/version/action. `shaft-cli doctor local-ai-status` reports enablement, eligibility, and the DISABLED snapshot; it does not list the reviewed pin table. Defaults stay off. Enablement is local-processing consent only; it is not remote consent or tool approval. CI/headless Maven never downloads. See [inventory and troubleshooting](/docs/start/local-infrastructure/previews#inventory-defaults-and-troubleshooting). ## Examples ```text shaft-cli tools # trace_latest — returns recent persisted SHAFT trace indexes from target/shaft-traces # [...] shaft-cli session start # shaft-cli session started on port 12345 (pid 9876). shaft-cli session status # running — port 12345, pid 9876, started 2025-12-20T14:30:45Z shaft-cli call shaft_guide_search query='click element' maxResults=1 # [JSON guide-search result] shaft-cli guide search query='click element' --stdio-ok # [guide search works with no session] shaft-cli call test_plan_explore targetUrl=https://example.test goal='checkout happy path' maxDepth=2 # [Markdown test plans written to specs/] ``` ## Exit codes - **0**: success - **1**: tool error, transport error, or `--json` result with `isError` - **2**: unknown alias action ## Related - [MCP](/docs/agentic/mcp) - [Set up local infrastructure](/docs/start/local-infrastructure) - [Overview](/docs/agentic/overview) - [Capture](/docs/agentic/capture) - [Doctor](/docs/agentic/doctor) --- # BrowserStack Canonical HTML: https://shafthq.github.io/docs/integrations/browserstack Markdown: https://shafthq.github.io/md/integrations/browserstack.md # BrowserStack `io.github.shafthq:shaft-browserstack` adds the BrowserStack Java SDK runtime. It does not add or replace SHAFT WebDriver methods. Direct BrowserStack WebDriver/Appium support remains in `shaft-engine`. For a SHAFT-owned local testing tunnel, use the [managed BrowserStack Local setup flow](/docs/start/local-infrastructure/services#install-managed-browserstack-local). ## Direct session or SDK orchestration? ```mermaid flowchart TD Start["Run on BrowserStack"] --> Need{"Need SDK interception, multi-platform YAML, or SDK orchestration?"} Need -- No --> Engine["Use shaft-engine only SHAFT creates the remote session"] Need -- Yes --> SDK["Add shaft-browserstack SDK consumes browserstack.yml"] Engine --> Same["Test methods stay unchanged"] SDK --> Same ``` ## Works with `shaft-engine` only With `executionAddress=browserstack`, these paths are core: - `new SHAFT.GUI.WebDriver()` and `DriverFactory` routing. - Desktop web, mobile web, and native Appium sessions. - W3C `bstack:options` construction. - BrowserStack app upload from `browserStack.appRelativeFilePath`. - Credentials, device/browser/OS properties, BrowserStack Local flag, debug/network logs, geolocation, and custom nonblank `browserStack.*` capabilities. App upload keys such as `appUrl`, `appName`, and `appRelativeFilePath` are handled separately. - `BrowserStackSdkHelper.generateBrowserStackYml()` generation/copy behavior. The generated `browserstack.yml` does not orchestrate anything by itself. With no SDK runtime, SHAFT simply creates the configured direct remote session. The test body is the same as the bundled TestNG web sample: ```java @BeforeMethod public void beforeMethod() { driver = new SHAFT.GUI.WebDriver(); } @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")); } ``` ## Requires `shaft-browserstack` Add the optional module when the BrowserStack SDK must read the YAML and intercept the test runtime: ```xml io.github.shafthq shaft-browserstack ``` | Configuration/functionality | Why the SDK module is required | |----------------------------------------------------------------------------------------|---------------------------------------------------------------------------| | `browserStack.platformsList` | The SDK expands the YAML platform array into executions. | | `browserStack.parallelsPerPlatform` | The SDK controls parallel executions per platform. | | `browserStack.browserstackAutomation` | The SDK interprets whether to intercept and route WebDriver creation. | | `browserStack.customBrowserStackYmlPath` | SHAFT copies the file in core; the SDK interprets its settings. | | SDK capability override, listeners, test orchestration, and SDK reporting integrations | Implemented by `browserstack-java-sdk`, which is supplied by this module. | Example SDK-specific configuration: ```java SHAFT.Properties.browserStack.set() . platformsList(""" [ {"os":"Windows","osVersion":"11","browserName":"Chrome"}, {"os":"OS X","osVersion":"Sonoma","browserName":"Safari"} ] """) . parallelsPerPlatform(2) . browserstackAutomation(true); ``` Credentials should remain in SHAFT property files, environment-backed Maven/CI configuration, or the selected secret store. Do not hardcode them in tests. For the SDK's runtime behavior, see [How BrowserStack SDK works](https://www.browserstack.com/docs/automate/selenium/how-sdk-works). ## Related - [Modules](/docs/features/modules) - [Upgrade](/docs/start/upgrade) - [Set up local infrastructure](/docs/start/local-infrastructure/services#install-managed-browserstack-local) - [Visual](/docs/integrations/visual) - [Desktop and video](/docs/integrations/desktop-and-video#video-recording) --- # Desktop and video Canonical HTML: https://shafthq.github.io/docs/integrations/desktop-and-video Markdown: https://shafthq.github.io/md/integrations/desktop-and-video.md Two optional modules extend SHAFT to the local desktop: `shaft-video` records the session, and `shaft-sikulix` drives desktop apps that have no DOM or Appium locators. ## Video recording `io.github.shafthq:shaft-video` supplies SHAFT's optional local desktop recording provider, including Automation Remarks and the platform-specific JAVE/FFmpeg payload. ```mermaid flowchart TD Record["videoParamsRecordVideo=true"] --> Mobile{"Appium native driver?"} Mobile -- Yes --> Engine["shaft-engine driver-native recording"] Mobile -- No --> Desktop{"Local + non-headless desktop?"} Desktop -- Yes --> Video["shaft-video desktop provider"] Desktop -- No --> Remote["Remote/provider video configured by capabilities"] ``` Add the module only for local, non-headless desktop recording: ```xml io.github.shafthq shaft-video ``` `RecordManager.startVideoRecording()` requires the desktop provider when those conditions are met. `RecordManager.startVideoRecording(WebDriver)` keeps Appium Android/iOS recording in `shaft-engine` through driver-native `startRecordingScreen()`/`stopRecordingScreen()` calls. Remote BrowserStack, LambdaTest, Selenium Grid, Selenoid, or Moon video options are capabilities of those remote providers and do not require `shaft-video`. ## SikuliX desktop automation `io.github.shafthq:shaft-sikulix` adds optional SikuliX image matching for desktop workflows that cannot be reached through DOM or Appium locators. ```xml io.github.shafthq shaft-bom ${shaft.version} pom import io.github.shafthq shaft-engine io.github.shafthq shaft-sikulix ``` ```java title="SikuliXActions.java" new SHAFT.GUI.SikuliX().element() .click("src/test/resources/images/save-button.png") .type("src/test/resources/images/name-field.png", "SHAFT"); ``` Attach actions to an already open application window when the match should be scoped to that desktop app: ```java title="SikuliXScopedToWindow.java" SHAFT.GUI.SikuliX calculator = SHAFT.GUI.SikuliX.getInstance("Calculator"); calculator.element() .click("src/test/resources/images/one.png") .click("src/test/resources/images/plus.png"); calculator.quit(); ``` If `shaft-sikulix` is missing, `SHAFT.GUI.SikuliX` reports the missing optional dependency and tells you to add it instead of surfacing a generic class loading error. :::tip Use `shaft-engine` alone for Appium-backed Windows desktop sessions. Add `shaft-sikulix` only for image matching. ::: ## Related - [Modules](/docs/features/modules) - [Mobile and Appium testing](/docs/testing/mobile) - [Visual testing](/docs/integrations/visual) - [Upgrade](/docs/start/upgrade) - [BrowserStack](/docs/integrations/browserstack) --- # OCR and visible-text automation Canonical HTML: https://shafthq.github.io/docs/integrations/ocr Markdown: https://shafthq.github.io/md/integrations/ocr.md # OCR and visible-text automation :::warning[Preview: module not released] `shaft-ocr` is available in `SHAFT_ENGINE` source but is not included in the current published SHAFT release. Do not add the dependency below to a released BOM project yet. Build the current source reactor only when you are evaluating this preview, and wait for a containing release before using it in a released project. ::: Use `io.github.shafthq:shaft-ocr` in a source-build preview when you need to recognize text from pixels. The module runs Tesseract locally through bundled native JavaCPP libraries; it does not require a system Tesseract installation or a cloud OCR service. ## Add the module After a containing release is published, import its SHAFT BOM, then add `shaft-engine` and `shaft-ocr` without module versions: ```xml io.github.shafthq shaft-bom ${shaft.version} pom import io.github.shafthq shaft-engine io.github.shafthq shaft-ocr ``` Without `shaft-ocr`, OCR calls fail with a message that names the missing optional dependency. ## Target visible text Create exact or partial OCR targets through the usual locator namespace. Use the target with WebDriver, Appium, or Playwright element actions: ```java var exactText = SHAFT.GUI.Locator.hasOcrText("Checkout"); var partialText = SHAFT.GUI.Locator.containsOcrText("Check"); driver.element().click(exactText); driver.element().hover(partialText); driver.element().doubleClick(exactText); ``` The default target requires one match. Select a zero-based occurrence when the same text appears more than once: ```java driver.element().click( SHAFT.GUI.Locator.containsOcrText("Save").occurrence(1) ); ``` SHAFT maps recognized screenshot coordinates to the active backend. WebDriver uses the browser viewport, Appium uses touch input, and Playwright uses its page mouse. Add `shaft-sikulix` to use the same OCR targets for desktop screen actions: ```java new SHAFT.GUI.SikuliX().element() .click(SHAFT.GUI.Locator.hasOcrText("Calculator")); ``` ## Assert recognized text Assert an element screenshot with the same native string assertion syntax used elsewhere in SHAFT: ```java driver.element().assertThat(By.id("receipt")) .ocrText() .contains("Payment complete"); ``` Assert an encoded image or image file directly: ```java SHAFT.Validations.assertThat() .image(Path.of("test-data/receipt.png")) .ocrText() .contains("Total"); SHAFT.Validations.verifyThat() .image(imageBytes) .ocrText() .contains("Order number"); ``` OCR recognition attaches the source image and recognition details to the report. OCR targeting also attaches the selected match, including its text, confidence, and bounds. ## Process PDF documents Use `PdfFileManager` to read native text and recognize text from scanned or mixed pages in the same PDF. SHAFT processes every page, keeps native positioned text when it is available, and calls `shaft-ocr` with a whole-page render only for pages that need pixel recognition. ```java var document = new PdfFileManager("test-data/invoice.pdf").process(); String text = document.fullText(); var firstPage = document.pages().getFirst(); System.out.println(firstPage.source()); // NATIVE, OCR, or HYBRID System.out.println(firstPage.confidence()); System.out.println(firstPage.tables()); ``` Each page result contains page, block, paragraph, line, and word geometry. It also reports any applied orientation or deskew correction, inferred tables, confidence, and warnings. Result lists are immutable. Table inference uses aligned word geometry; validate irregular, borderless, or merged-cell results before using them as structured data. Request exports explicitly. SHAFT writes each export through a temporary sibling file and then moves it into place: ```java Files.createDirectories(Path.of("build")); var result = new PdfFileManager("test-data/scanned-invoice.pdf").process( PdfExportRequest.to(PdfExportFormat.SEARCHABLE_PDF, Path.of("build/invoice-searchable.pdf")), PdfExportRequest.to(PdfExportFormat.HOCR, Path.of("build/invoice.hocr")), PdfExportRequest.to(PdfExportFormat.TSV, Path.of("build/invoice.tsv")), PdfExportRequest.to(PdfExportFormat.JSON, Path.of("build/invoice.json")) ); result.exports().forEach(export -> System.out.println(export.output() + " " + export.sha256()) ); ``` Existing output files are rejected by default. Call `replacingExisting()` on an export request when replacement is intentional. Searchable export of a signed PDF is also rejected because changing the document invalidates its signatures; call `allowingSignatureInvalidation()` only when that consequence is acceptable. Process independent PDFs as an ordered batch: ```java var requests = List.of( PdfDocumentRequest.of(Path.of("test-data/one.pdf")), PdfDocumentRequest.of(Path.of("test-data/two.pdf")) ); PdfBatchResult batch = PdfFileManager.processAll( requests, new PdfBatchOptions(4, 256L * 1024 * 1024, false) ); batch.items().forEach(item -> System.out.println(item.source() + " successful=" + item.successful()) ); ``` The batch keeps request order and records item failures without discarding successful results. Set `failFast` to `true` to stop before later requests can publish exports; fail-fast execution is serial for that reason. Set per-call recognition and safety limits through `PdfDocumentOptions`: ```java var options = PdfDocumentOptions.defaults() .withRenderDpi(240) .withResourceLimits(100L * 1024 * 1024, 250, 20_000_000) .withPageTimeout(Duration.ofSeconds(60)) .withAllureEvidence(false); var document = new PdfFileManager("test-data/archive.pdf").process(options); ``` PDF processing attaches a JSON document summary and page-level recognition details to Allure by default. Those details can contain recognized document text, geometry, tables, warnings, and the source path. Disable them with `withAllureEvidence(false)` when the document is sensitive. `shaft.ocr.document.maximumAllureArtifactBytes` controls whether explicit export files are attached or represented by a size and checksum manifest; it does not cap page-level JSON details. :::warning SHAFT accepts PDF input only; it does not add Tabula or an ML table runtime. It rejects encrypted PDFs, inputs and page counts above their limits, and individually oversized rendered pages. Concurrent raster work is throttled by the batch byte budget. Treat OCR page timeouts as caller-side bounds: a native OCR library call may finish in its background thread after the timed operation has returned. ::: ## Tune recognition Start from `OcrOptions.defaults()` when asserting an image or element. Tune a target directly when interacting with visible text: ```java var options = OcrOptions.defaults() .withLanguages("English", "Arabic") .withMinimumConfidence(0.80) .withPreprocessingMode(OcrPreprocessingMode.GRAYSCALE) .withPageSegmentationMode(OcrPageSegmentationMode.SPARSE_TEXT) .within(new OcrRectangle(0, 0, 900, 500)); SHAFT.Validations.assertThat() .image(Path.of("test-data/bilingual-receipt.png")) .ocrText(options) .contains("الإجمالي"); ``` Available preprocessing modes are `AUTO`, `NONE`, `GRAYSCALE`, `BINARY`, and `INVERT`. `AUTO` derives an Otsu threshold from the image, composites alpha onto white, and preserves pixel coordinates. Page segmentation modes cover automatic text, a single block, line, or word, and sparse text. ## Configure language models English and Arabic are the default languages. Pass Tesseract three-letter model codes or supported human-readable names for other languages. SHAFT downloads missing models on first use from a pinned `tessdata_fast` revision, verifies their integrity, and stores them in the user cache. Configure provisioning through the typed property namespace: ```java SHAFT.Properties.ocr.set() .cacheDirectory("build/shaft-ocr-models") .downloadEnabled(false) .documentRenderDpi(300) .documentMaximumPages(500) .documentMaximumInFlightRasterBytes(256L * 1024 * 1024); ``` Use the matching `shaft.ocr.*` keys in `custom.properties` or as system properties when code configuration is not appropriate. Document options passed to `process(...)` override the defaults for that call. :::warning When downloads are disabled, every requested language model must already exist in the configured cache and pass integrity verification. SHAFT fails before recognition if a model is missing or altered. ::: ## Preview: managed OCR setup :::warning[Not released] The selection-aware managed setup workflow below is not yet available on `SHAFT_ENGINE` `main` or in a published SHAFT release. Do not run these commands or compile against these overloads until a containing release is available. The planned provider pins every supported Tesseract language to one `tessdata_fast` revision and SHA-256 checksum. It also applies the setup artifact safety ceiling. Create and review an immutable plan before install: ```bash shaft-cli setup plan --profile OCR --mode MANAGED \ --language eng --language ara \ --output /absolute/path/ocr-plan.json # Review the JSON and copy its digest, then repeat every policy option. shaft-cli setup install --plan /absolute/path/ocr-plan.json \ --approve sha256: shaft-cli setup verify --profile OCR \ --language eng --language ara ``` Omit `--language` for the baseline `eng` and `ara` bundle. Repeat it with three-letter Tesseract codes such as `fra` and `deu` to provision another exact set. Java callers will be able to pass ```java new SetupSelection(List.of("fra", "deu")) ``` to the selection-aware `SHAFT.Infrastructure.plan`, `SHAFT.Infrastructure.status`, `SHAFT.Infrastructure.verify`, and `SHAFT.Infrastructure.install` overloads (or the equivalent low-level service overloads). CLI install recovers the selection from the reviewed actions; repeating `--language` is optional and must match when supplied. A custom cache must be absolute. The provider will prefer it only when the complete requested set verifies there, otherwise it will use the platform-native shared setup cache when that complete set verifies. ::: ## Choose OCR for pixel-only text Prefer semantic locators when the application exposes a stable DOM, accessibility tree, or native element hierarchy. Use OCR for canvases, remote desktops, streamed applications, rendered documents, screenshots, inaccessible native surfaces, and other cases where text exists only as pixels. OCR accuracy depends on image resolution, contrast, font rendering, language models, and segmentation. Restrict the region, choose a suitable preprocessing mode, and raise the confidence threshold when the screen contains unrelated text. ## Related - [Visual testing](./visual.md) - [Desktop and video automation](./desktop-and-video.md) - [Modular dependencies](../features/modules.md) - [Mobile testing](../testing/mobile.md) --- # Visual testing Canonical HTML: https://shafthq.github.io/docs/integrations/visual Markdown: https://shafthq.github.io/md/integrations/visual.md # Visual testing `io.github.shafthq:shaft-visual` supplies the optional `VisualProcessingProvider` implementation and its OpenCV, Applitools Eyes, and Selenium Shutterbug dependencies. ## Add the module ```xml io.github.shafthq shaft-bom ${shaft.version} pom import io.github.shafthq shaft-engine io.github.shafthq shaft-visual ``` No initialization call is required. Java `ServiceLoader` discovers the provider. ## Dependency decision ```mermaid flowchart TD Start["What does the test do?"] --> Ref{"Reference-image assertion or image-path touch action?"} Ref -- Yes --> Visual["Add shaft-visual"] Ref -- No --> Core{"Only capture, attach, highlight, GIF, or compare folders?"} Core -- Yes --> Engine["shaft-engine is sufficient"] Core -- No --> Check["Check the method table below"] ``` ## Requires `shaft-visual` | API | Functionality | |----------------------------------------------------------------|--------------------------------------------------------------| | `matchesReferenceImage()` | Exact OpenCV reference-image comparison for WebDriver, Appium, and Playwright. | | `matchesReferenceImage(VisualValidationEngine)` | OpenCV, Shutterbug, or Eyes comparison selected by the enum. Playwright routes `Locator.screenshot()` bytes through the provider; Shutterbug requests fall back to OpenCV because Shutterbug is Selenium-backed. | | `doesNotMatchReferenceImage()` and its overload | Negative OpenCV/visual-engine comparison. | | `TouchActions.tap(String)` | Finds and taps an image inside the current screen. | | `TouchActions.tap(ImageTarget)` | Finds a typed image target and taps its mapped screenshot center. | | `TouchActions.type(String, ...)` | Finds an image, taps it, then types into the active field. | | `TouchActions.waitUntilElementIsVisible(String)` | Waits for an image match. | | `TouchActions.waitUntilElementIsNotVisible(String)` | Waits until an image match disappears from the screen. | | `TouchActions.swipeElementIntoView(String, ...)` | Swipes until the reference image is found. | | `TouchActions.swipeElementIntoView(ImageTarget, ...)` | Searches while scrolling up, down, left, or right. The overload with `By` restricts search and gestures to a container. | | `ImageProcessingActions.findImageWithinCurrentPage(...)` | Direct OpenCV-backed image lookup. | | `ImageProcessingActions.compareAgainstBaseline(...)` | Direct baseline comparison. | | `ImageProcessingActions.loadOpenCV()` | Explicit provider/native-library loading. | | Built-in Cucumber OpenCV, Shutterbug, and Eyes assertion steps | Delegates to the same provider. | Use `ImageTarget` when you need confidence, occurrence, region, or algorithm control. SHAFT validates the encoded image before native processing, requires a unique match unless you select an occurrence, and maps screenshot pixels to the active viewport: ```java title="TypedImageTarget.java" var payButton = ImageTarget.fromPath(Path.of("src/test/resources/pay.png")) .minimumConfidence(0.94) .within(new ImageRectangle(0, 300, 1080, 1200)) .matchingMode(ImageMatchingMode.AUTO); driver.touch() .swipeElementIntoView(payButton, TouchActions.SwipeDirection.DOWN) .tap(payButton); ``` `AUTO` uses color-aware, alpha-masked, multi-scale template matching first. If that cannot establish a match, it tries SIFT feature matching with RANSAC homography verification for rotated or perspective-shifted targets. Choose `TEMPLATE` or `FEATURE` to require one path. Feature matching resolves one geometrically verified occurrence, so narrow the search region when repeated rotated copies are present. On Appium, SHAFT tries local OpenCV first. It can use Appium Images as a capability fallback for an unconstrained target, temporarily applying SHAFT's visual threshold and restoring the session setting afterward. Explicit target confidence, region, or matching-mode constraints fail closed when the fallback cannot enforce them. The bundled TestNG/JUnit web samples use: ```java driver.browser().navigateToURL(targetUrl) .and().element().assertThat(logo).matchesReferenceImage(); ``` The bundled Cucumber sample uses: ```gherkin Then I Assert that the element found by "xpath": "//div[contains(@class,'container_fullWidth__1H_L8')]//img", exactly matches with the expected reference image using AI OpenCV ``` Both styles require `shaft-visual`. `SHAFT.GUI.Playwright` supports the same element visual assertion surface. The Playwright backend captures `Locator.screenshot()` bytes and compares them through `shaft-visual`: ```java title="PlaywrightVisualValidation.java" driver.assertThat().element(By.id("logo")) .matchesReferenceImage(ValidationEnums.VisualValidationEngine.EXACT_OPENCV); ``` The no-argument overload uses `EXACT_OPENCV` on every backend. Applitools Eyes engines also receive Playwright screenshot bytes. Selenium Shutterbug remains available for WebDriver/Appium visual checks. ## Remains in `shaft-engine` | API/functionality | Implementation | |-------------------------------------------------------------------|---------------------------------------| | WebDriver/Appium screenshots and report attachments | Selenium/Appium plus SHAFT reporting. | | `ImageProcessingActions.highlightElementInScreenshot(...)` | JDK `BufferedImage`/`Graphics2D`. | | `ImageProcessingActions.compareImageFolders(...)` | JDK `ImageIO` and data buffers. | | `formatElementLocatorToImagePath(...)` | Baseline naming only. | | `getReferenceImage(...)` and `getShutterbugDifferencesImage(...)` | Baseline file reads only. | | Animated GIF generation | Core image/reporting implementation. | | Locator-based touch methods such as `tap(By)` | Selenium/Appium locator execution. | | Healenium | Independent integration. | Without `shaft-visual`, provider-dependent methods throw an `IllegalStateException` that names the missing Maven coordinate. Core screenshot and image-file operations continue to work. ## Comparison engines `shaft-visual` supports the visual validation engines in the `VisualValidationEngine` enum: | Engine | Description | Best for | |---|---|---| | `EXACT_EYES` | Pixel-perfect comparison | Static assets, logos, icons | | `STRICT_EYES` | High-sensitivity comparison with minor tolerance | UI components | | `CONTENT_EYES` | Compares content while ignoring minor rendering differences | Text-heavy pages | | `LAYOUT_EYES` | Compares layout structure, ignores content changes | Page layout regression | | `EXACT_OPENCV` | Uses OpenCV for image comparison | OpenCV-based visual checks | ```java title="VisualTesting.java" // Assert element matches a reference image (stores baseline on first run) driver.element().assertThat(By.id("logo")).matchesReferenceImage(); // Layout comparison — ignores content, checks structure driver.element().assertThat(By.id("productCard")) .matchesReferenceImage(ValidationEnums.VisualValidationEngine.LAYOUT_EYES); ``` When an intentional UI change is made, delete the relevant baseline image from `src/test/resources/DynamicObjectRepository/` and run the test once to regenerate it. Run visual tests in a consistent environment (same OS, browser version, screen resolution) to avoid false positives, and avoid mixing headless and headed baselines. ## matchesScreenshot() `matchesScreenshot()` is an OpenCV-only pixel-diff assertion built into `shaft-engine`, so it does not require `shaft-visual`. It runs when you call it. Pass a `VisualComparisonOptions` object to tune the diff budget and masks, mirroring Playwright's `toHaveScreenshot()` options: ```java title="ScreenshotBaseline.java" driver.element().assertThat(By.id("logo")) .matchesScreenshot(VisualComparisonOptions.create() .maxDiffPixelRatio(0.01) .mask(By.id("timestamp"))); ``` For a straight comparison with default settings, call `matchesScreenshot()` with no arguments. ### Per-browser/OS baseline naming Baselines are stored per browser and platform, with a sanitized `_ _ ` suffix appended to the hashed baseline file name (for example, ` _chrome_windows.png`). The suffix is built from `SHAFT.Properties.web.targetBrowserName()` and `SHAFT.Properties.platform.targetPlatform()`, lowercased with non-alphanumeric characters stripped, so cross-browser and cross-OS runs no longer share (and fight over) a single baseline image. If no per-browser/OS baseline exists yet, SHAFT falls back to a legacy unsuffixed baseline when one is present, logging a one-line notice, so baselines captured before this change keep working. New baselines — and any run with `-Dshaft.updateSnapshots=true` — always write to the new per-browser/OS path. The IntelliJ plugin's **Visual Baselines** panel lists pending `*_diff.png` comparisons and lets you Accept or Reject each one without leaving the IDE — see the [IntelliJ IDEA plugin](/docs/agentic/intellij) guide. ## Related - [Modules](/docs/features/modules) - [Upgrade](/docs/start/upgrade) - [BrowserStack](/docs/integrations/browserstack) - [Desktop and video](/docs/integrations/desktop-and-video#video-recording) --- # Reference Canonical HTML: https://shafthq.github.io/docs/reference/overview Markdown: https://shafthq.github.io/md/reference/overview.md # Reference Use this section after completing the [quick start](/docs/start/quick-start). It contains the detailed action, property, validation, reporting, and design guides. ## I want to... | Task | Start here | |---|---| | Open a browser, click, type, wait, and assert | [Web testing](/docs/testing/web) | | Choose resilient web locators | [Web locator strategy](/docs/testing/web#locator-strategy) | | Send REST requests and validate responses | [API testing](/docs/testing/api) | | Configure browser, API, mobile, retry, or reporting behavior | [Property Types](/docs/reference/properties/PropertyTypes) | | Generate a custom `.properties` file | [Config Generator tab](/docs/reference/properties/PropertiesList?PropertyTypes=generator) on the Properties Reference | | Attach evidence and understand reports | [Reporting](/docs/reference/reporting/) | | Diagnose flaky or failed runs | [How SHAFT reduces flakiness](/docs/testing/flakiness) | | Query a database or run a terminal command | [Database testing](/docs/testing/database) and [CLI testing](/docs/testing/cli) | ## Reference areas | Area | Reference | |---|---| | Browser and element actions | [GUI Actions](/docs/reference/actions/GUI/Browser_Actions) | | REST API | [API request builder](/docs/reference/actions/API/Request_Builder) | | CLI | [CLI Actions](/docs/reference/actions/CLI/Terminal_Actions) | | Database | [Database Actions](/docs/reference/actions/DB/DB_Actions) | | Configuration | [Property types](/docs/reference/properties/PropertyTypes) | | Assertions | [Validations](/docs/reference/actions/Validations) | | Engineering practices | [Solution design](/docs/reference/guides/Solution_Design) | Start from the facade namespace for the surface you are testing: ```java SHAFT.API api = new SHAFT.API("https://jsonplaceholder.typicode.com"); api.get("/todos/1"); api.assertThatResponse().extractedJsonValue("id").isEqualTo("1"); ``` ## Related - [Quick start](/docs/start/quick-start) - [API request builder](/docs/reference/actions/API/Request_Builder) - [Validations](/docs/reference/actions/Validations) --- # Alert Actions Canonical HTML: https://shafthq.github.io/docs/reference/actions/GUI/Alert_Actions Markdown: https://shafthq.github.io/md/reference/actions/GUI/Alert_Actions.md Alerts are dialog boxes that appear in the browser to notify or prompt the user. SHAFT provides the `AlertActions` class to interact with these dialogs. ## Accessing Alert Actions Use `driver.alert()` to access alert actions: ```java title="AlertAccess.java" driver.alert().acceptAlert(); ``` ## acceptAlert() Accepts the currently displayed alert dialog (equivalent to clicking "OK"). ```java title="AcceptAlert.java" driver.alert().acceptAlert(); ``` ## dismissAlert() Dismisses the currently displayed alert dialog (equivalent to clicking "Cancel"). ```java title="DismissAlert.java" driver.alert().dismissAlert(); ``` ## getAlertText() Retrieves the text message displayed in the currently active alert dialog. ```java title="GetAlertText.java" String alertMessage = driver.alert().getAlertText(); ``` ## typeIntoPromptAlert() Types text into a prompt alert dialog before accepting or dismissing it. ```java title="TypeIntoPrompt.java" driver.alert().typeIntoPromptAlert("your text"); ``` ## isAlertPresent() Checks whether an alert dialog is currently present on the page. Returns `true` if an alert is present, `false` otherwise. ```java title="IsAlertPresent.java" boolean alertPresent = driver.alert().isAlertPresent(); ``` ## Complete Example ```java title="AlertDemo.java" public class AlertDemo { private SHAFT.GUI.WebDriver driver; private By jsAlertButton = By.xpath("//button[text()='Click for JS Alert']"); private By jsConfirmButton = By.xpath("//button[text()='Click for JS Confirm']"); private By jsPromptButton = By.xpath("//button[text()='Click for JS Prompt']"); @BeforeMethod public void setup() { driver = new SHAFT.GUI.WebDriver(); driver.browser().navigateToURL("https://the-internet.herokuapp.com/javascript_alerts"); } @Test public void handleSimpleAlert() { driver.element().click(jsAlertButton); if (driver.alert().isAlertPresent()) { String alertText = driver.alert().getAlertText(); System.out.println("Alert message: " + alertText); driver.alert().acceptAlert(); } } @Test public void handleConfirmAlert() { driver.element().click(jsConfirmButton); driver.alert().dismissAlert(); } @Test public void handlePromptAlert() { driver.element().click(jsPromptButton); driver.alert().typeIntoPromptAlert("SHAFT Engine"); driver.alert().acceptAlert(); } @AfterMethod public void teardown() { driver.quit(); } } ``` ## Related - [Browser Actions](/docs/reference/actions/GUI/Browser_Actions) - [Element Actions](/docs/reference/actions/GUI/Element_Actions) - [Element Identification](/docs/reference/actions/GUI/Element_Identification) - [Web](/docs/testing/web) --- # API Authentication Canonical HTML: https://shafthq.github.io/docs/reference/actions/API/API_Authentication Markdown: https://shafthq.github.io/md/reference/actions/API/API_Authentication.md Use `setAuthentication()` for BASIC or FORM authentication. For bearer tokens, API keys, and cookies, add the relevant header before sending the request. --- ## BASIC Authentication Pass a username and password using `AuthenticationType.BASIC`: ```java title="APIAuthentication.java" SHAFT.API api = new SHAFT.API("https://api.example.com"); api.get("/secure/data") .setAuthentication("username", "password", AuthenticationType.BASIC) .setTargetStatusCode(200) ; ``` --- ## FORM Authentication Submit credentials as form parameters using `AuthenticationType.FORM`: ```java title="APIAuthentication.java" api.post("/login") .setAuthentication("user@example.com", "password123", AuthenticationType.FORM) .setTargetStatusCode(200) ; ``` --- ## OAuth2 Bearer Token Add the `Authorization` header with a `Bearer` token prefix: ```java title="APIAuthentication.java" api.get("/oauth/resource") .addHeader("Authorization", "Bearer your-oauth-token") .setTargetStatusCode(200) ; ``` --- ## API Key Authentication ### API Key in Header ```java title="APIAuthentication.java" api.get("/data") .addHeader("X-API-Key", "your-api-key") .setTargetStatusCode(200) ; ``` ### API Key in Query Parameter ```java title="APIAuthentication.java" api.get("/data") .setUrlArguments("api_key=your-api-key") .setTargetStatusCode(200) ; ``` --- ## Cookie-Based Authentication Pass a session cookie using `addHeader`: ```java title="APIAuthentication.java" api.get("/profile") .addHeader("Cookie", "session_id=abc123xyz; token=your-session-token") .setTargetStatusCode(200) ; ``` --- ## Persistent headers and cookies Use `addHeader()` or `addCookie()` on `SHAFT.API` when a token or cookie should be sent with later requests: ```java title="APIAuthentication.java" SHAFT.API api = new SHAFT.API("https://api.example.com"); api.addHeader("Authorization", "Bearer your-oauth-token"); api.addCookie("session_id", "your-session-id"); api.get("/users").setTargetStatusCode(200); ``` --- ## Complete Test Example ```java title="APIAuthTest.java" public class APIAuthTest { @Test public void testBasicAuth() { SHAFT.API api = new SHAFT.API("https://httpbin.org"); api.get("/basic-auth/user/pass") .setAuthentication("user", "pass", AuthenticationType.BASIC) .setTargetStatusCode(200) ; api.assertThatResponse() .extractedJsonValue("$.authenticated") .isEqualTo("true"); } @Test public void testBearerToken() { SHAFT.API api = new SHAFT.API("https://api.example.com"); api.get("/protected") .addHeader("Authorization", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...") .setTargetStatusCode(200) ; } } ``` --- :::tip Store authentication credentials in SHAFT property files or environment variables — never hardcode tokens or passwords directly in test code. ::: :::warning OAuth2 tokens expire. For CI/CD pipelines, implement a token-refresh step before your test suite runs or retrieve the token programmatically as part of test setup. ::: ## Related - [Request Builder](/docs/reference/actions/API/Request_Builder) - [Response Validations](/docs/reference/actions/API/Response_Validations) - [API](/docs/testing/api) --- # Async Element Actions Canonical HTML: https://shafthq.github.io/docs/reference/actions/GUI/Async_Element_Actions Markdown: https://shafthq.github.io/md/reference/actions/GUI/Async_Element_Actions.md ## Overview SHAFT supports non-blocking element actions via Java virtual threads on the project's Java 25 baseline through `driver.async().element()`. Actions queued on the async executor run in parallel and return immediately, letting you kick off multiple interactions concurrently and synchronize at a chosen point. This is useful when: - Filling a long form where fields are independent and can be typed into simultaneously - Triggering multiple UI actions that don't need to happen in strict sequence - Reducing wall-clock test execution time without sacrificing accuracy --- ## Basic Usage ### Parallel Form Fill ```java title="AsyncFormFill.java" // Queue multiple element actions — they start concurrently driver.async().element() .type(By.id("firstName"), "Alice") .type(By.id("lastName"), "Smith") .type(By.id("email"), "alice@example.com") .select(By.id("country"), "United States") .synchronize(); // blocks the current thread until all virtual threads complete; propagates any action failures as exceptions ``` ### Parallel Click and Screenshot ```java title="AsyncClickAndCapture.java" driver.async().element() .click(By.id("submitBtn")) .captureScreenshot(By.id("confirmationMessage")) .join(); // alternative to synchronize() ``` :::note `.synchronize()` and `.join()` are equivalent — use whichever reads more naturally in context. ::: --- ## Synchronisation Methods After queuing async actions, call one of the following to block until all actions complete: | Method | Description | |:-------|:------------| | `.synchronize()` | Waits for all queued async actions to complete | | `.join()` | Alias for `.synchronize()` | | `.sync()` | Alias for `.synchronize()` | ```java title="AsyncSynchronise.java" driver.async().element() .type(By.id("fieldA"), "value1") .type(By.id("fieldB"), "value2") .type(By.id("fieldC"), "value3") .sync(); // equivalent to .synchronize() and .join() ``` --- ## Complete Example: Registration Form ```java title="AsyncRegistrationTest.java" public class AsyncRegistrationTest { SHAFT.GUI.WebDriver driver; @BeforeMethod public void setup() { driver = new SHAFT.GUI.WebDriver(); } @Test public void registerUserWithAsyncActions() { driver.browser().navigateToURL("https://example.com/register"); // Fill all fields in parallel using virtual threads driver.async().element() .type(By.id("firstName"), "Alice") .type(By.id("lastName"), "Smith") .type(By.id("email"), "alice@example.com") .type(By.id("phone"), "+1-555-0100") .select(By.id("country"), "United States") .synchronize(); // Proceed sequentially once the form is filled driver.element().click(By.id("submitBtn")); driver.assertThat().browser().url().contains("/success"); } @AfterMethod public void teardown() { driver.quit(); } } ``` --- ## When to Use Async vs. Synchronous Actions | Scenario | Recommendation | |:---------|:---------------| | Independent form fields with no DOM side-effects | ✅ Use async | | Fields where typing in one triggers a validation on another | ❌ Use standard sequential actions | | Bulk data entry where order doesn't matter | ✅ Use async | | Actions that depend on the result of a previous action | ❌ Use standard sequential actions | | Capturing screenshots while another action runs | ✅ Use async | :::tip Async actions are best suited for **write** operations (type, select, click) that operate on independent elements. Avoid using them when one action's outcome affects the next element's state. ::: --- ## Related - [Element Actions](./Element_Actions) — standard synchronous element API - [SHAFT_ENGINE source: AsyncElementActions](https://github.com/ShaftHQ/SHAFT_ENGINE/blob/main/shaft-engine/src/main/java/com/shaft/gui/element/AsyncElementActions.java) --- # Basic Configuration for API Canonical HTML: https://shafthq.github.io/docs/reference/configuration/apiConfig Markdown: https://shafthq.github.io/md/reference/configuration/apiConfig.md The properties below apply to all `SHAFT.API` (REST Assured) test executions. Place them in your `custom.properties` file to adjust connectivity, timeouts, and contract validation. --- ## Connection & Timeouts ```properties title="src/main/resources/properties/custom.properties" # Timeout (seconds) for reading data from the server socket apiSocketTimeout=30 # Timeout (seconds) to establish a TCP connection apiConnectionTimeout=30 # Timeout (seconds) to acquire a connection from the pool apiConnectionManagerTimeout=30 ``` - These timeouts are read when each request runs, so a `SHAFT.Properties.timeouts.set()` call after `new SHAFT.API(...)` still applies. They are per thread. - `0` means no timeout. The maximum is `2147483` seconds. - Empty, non-numeric, or out-of-range values fail fast with the key, the value, and a `Fix:` line. --- ## Status Code Assertions By default, SHAFT asserts that every response has a `2xx` status code. ```properties title="src/main/resources/properties/custom.properties" # Disables only the implicit 2xx check for requests that set no target status code automaticallyAssertResponseStatusCode=false ``` - An explicit `setTargetStatusCode(n)` is **always** asserted, whatever this flag says. For negative tests, set the expected `4xx`/`5xx` status instead of turning the flag off. - `setTargetStatusCode(0)` means "no explicit target". Any other value must be a three-digit status (`100`–`999`); anything else fails fast with a `Fix:` line. - The flag is read when each request runs, so `SHAFT.Properties.flags.set()` after `new SHAFT.API(...)` applies. - Booleans accept `true`/`false` in any case, with surrounding whitespace. Invalid or empty values fail with the key, the value, and a `Fix:` line. - A failure with no target reads `Expected a 2xx status but found N`. - A status mismatch is an `AssertionError` (an `Error`, not an `Exception`), so Failsafe or retry policies that handle only `Exception` will not retry it. --- ## Proxy Only needed if you are behind a corporate proxy: ```properties title="src/main/resources/properties/custom.properties" com.SHAFT.proxySettings=proxy.corp.example.com:8080 ``` --- ## Test Retries ```properties title="src/main/resources/properties/custom.properties" # Example: allow three additional attempts after the first failure retryMaximumNumberOfAttempts=3 ``` The default is `0`, so retries are opt-in. --- ## Swagger / OpenAPI Contract Validation Enable automatic schema validation against a Swagger/OpenAPI specification file to ensure your API communication complies with the defined contract. ```properties title="src/main/resources/properties/custom.properties" # Enable/disable Swagger schema validation swagger.validation.enabled=true # URL or file path to your Swagger / OpenAPI specification swagger.validation.url=https://petstore.swagger.io/v2/swagger.json # Include an end-of-run operation coverage report openapi.coverage.report.enabled=true # Optional minimum operation coverage percentage; 0 disables threshold enforcement openapi.coverage.threshold=80 ``` When validation is enabled, every request and response is validated against the schema automatically. If `swagger.validation.enabled=true` or `openapi.coverage.report.enabled=true` and `swagger.validation.url` is missing or blank, the run fails with a `Fix:` line. Invalid values for `swagger.validation.enabled`, `openapi.coverage.report.enabled`, and `openapi.coverage.threshold` fail with the key, the value, and a `Fix:` line. When coverage reporting is enabled, SHAFT also summarizes exercised operations, untested operations, unmatched requests, and validation failures by operation ID at the end of the run. --- :::tip You can learn more about the different **[property types]** and the **[full list of supported properties]** by visiting the related pages. ::: [property types]: /docs/reference/properties/PropertyTypes [full list of supported properties]: /docs/reference/properties/PropertiesList ## Related - [Property Types](/docs/reference/properties/PropertyTypes) - [Properties List](/docs/reference/properties/PropertiesList) - [Common Examples](/docs/reference/properties/CommonExamples) --- # Basic Configuration for Mobile GUI Canonical HTML: https://shafthq.github.io/docs/reference/configuration/mobileConfig Markdown: https://shafthq.github.io/md/reference/configuration/mobileConfig.md SHAFT uses Appium under the hood for mobile automation. Configure the properties below to target a local Appium server or a cloud device farm. :::tip - Set up your Appium server following the [official Appium documentation](https://appium.io/docs/en/latest/). - For cloud execution without managing your own devices, consider [BrowserStack](https://www.browserstack.com/) or [LambdaTest](https://www.lambdatest.com/). ::: --- ## Common Properties (Android & iOS) ```properties title="src/main/resources/properties/custom.properties" # Address of your Appium server (default for local) executionAddress=localhost:4723 # Target platform: ANDROID or IOS targetOperatingSystem=ANDROID # Automation driver — UiAutomator2 or Espresso for Android, XCUITest for iOS mobile_automationName=UiAutomator2 ``` --- ## Android Native App ```properties title="src/main/resources/properties/custom.properties" # Install a fresh APK before each run (recommended) mobile_app=src/test/resources/apps/MyApp.apk # Device name as reported by `adb devices` mobile_deviceName=Pixel_7_API_34 # Android version (optional but recommended) mobile_platformVersion=14.0 ``` Alternatively, launch an already-installed app by package/activity (not recommended for clean test runs): ```properties title="src/main/resources/properties/custom.properties" mobile_appPackage=com.example.android.myApp mobile_appActivity=.MainActivity ``` --- ## iOS Native App ```properties title="src/main/resources/properties/custom.properties" targetOperatingSystem=IOS mobile_automationName=XCUITest # .app bundle for Simulator, .ipa for real device mobile_app=src/test/resources/apps/MyApp.app # Device name as shown in Xcode device list mobile_deviceName=iPhone 15 mobile_platformVersion=17.0 # UDID required for real device mobile_udid=00008110-001A23456789AB01 # Optional: launch an already-installed iOS app by bundle identifier mobile_bundleId=com.example.ios.myApp ``` --- ## BrowserStack Native Apps When `executionAddress=browserstack`, SHAFT can start native mobile sessions from either a BrowserStack app URL or a remote app value in `mobile_app`: ```properties title="src/main/resources/properties/custom.properties" executionAddress=browserstack targetOperatingSystem=ANDROID mobile_automationName=UiAutomator2 mobile_deviceName=Google Pixel 7 mobile_platformVersion=13.0 # Preferred when you already know the uploaded BrowserStack app URL browserStack.appUrl=bs:// # Also supported for remote app references when browserStack.appUrl is not set # mobile_app=bs:// # mobile_app=https://example.com/apps/MyApp.apk ``` If both `browserStack.appUrl` and `mobile_app` point to remote app values, `browserStack.appUrl` takes precedence. Use local `mobile_app` paths when SHAFT should upload the app for you. --- ## Mobile Web (Browser on Device) For mobile browser testing, configure the same properties as [Web GUI](./webConfig) and add the mobile target: ```properties title="src/main/resources/properties/custom.properties" executionAddress=localhost:4723 targetOperatingSystem=ANDROID mobile_automationName=UiAutomator2 mobile_browserName=Chrome mobile_deviceName=Pixel_7_API_34 baseURL=https://m.example.com ``` --- ## SHAFT MCP Mobile Automation `shaft-mcp` can start mobile sessions through the same SHAFT Engine setup: - `driver_initialize` with `engine=mobile_web` runs a resized desktop browser in mobile compatibility mode; `engine=mobile_native` starts an Appium-backed Android or iOS native session. Both take a nested `mobileOptions` request (absorbing the former separate `mobile_initialize_web_emulation`/`mobile_initialize_native` tools) carrying the Appium server, device name, UDID, app, app package/activity, or iOS bundle ID. - `mobile_take_screenshot` captures the current mobile screen, and `mobile_get_accessibility_tree` returns the active Appium page source/accessibility XML so the MCP client can understand the screen before acting. - `capture_start`, `capture_stop`, `capture_generate_replay`, `capture_code_blocks`, and `capture_record_at_target_code_blocks` dispatch to the active mobile session (absorbing the former `mobile_record_start`/`mobile_record_stop`/`mobile_replay_recording`/`mobile_recording_code_blocks`/`mobile_record_at_target_code_blocks` tools) to support record/playback, Page Object handoff, and copy-pasteable SHAFT action snippets. --- :::tip You can learn more about the different **[property types]** and the **[full list of supported properties]** by visiting the related pages. ::: [appium user guide]: [Web GUI basic config]: /docs/reference/configuration/webConfig [property types]: /docs/reference/properties/PropertyTypes [full list of supported properties]: /docs/reference/properties/PropertiesList [BrowserStack]: [LambdaTest]: ## Related - [Property Types](/docs/reference/properties/PropertyTypes) - [Properties List](/docs/reference/properties/PropertiesList) - [Common Examples](/docs/reference/properties/CommonExamples) --- # Basic Configuration for Web GUI Canonical HTML: https://shafthq.github.io/docs/reference/configuration/webConfig Markdown: https://shafthq.github.io/md/reference/configuration/webConfig.md SHAFT auto-generates a default `custom.properties` file on your first test run. Use it to tailor browser selection, timeouts, and reporting without touching your test code. :::tip All properties can also be set programmatically or overridden via Maven `-D` flags. See [Programmatic Config](/docs/reference/properties/Programmatic_Config) and [Properties Reference](/docs/reference/properties/PropertiesList). ::: --- ## Target Browser ```properties title="src/main/resources/properties/custom.properties" # Default: chrome. Other supported values are firefox, safari, and edge. targetBrowserName=chrome # Default: false. Set true for a headless CI run. headlessExecution=false ``` Override from the command line without changing the file: ```bash mvn test -DtargetBrowserName=FIREFOX -DheadlessExecution=true ``` --- ## Base URL and Timeouts ```properties title="src/main/resources/properties/custom.properties" # Used as a prefix when you call driver.browser().navigateToURL("/path") baseURL=https://staging.example.com # Default: 10 seconds to find an element before failing defaultElementIdentificationTimeout=10 # Time (seconds) for default UI state waits such as driver.element().waitUntil(...) waitForUiStateTimeout=600 # Enable SHAFT's browser readiness wait before browser and element actions waitForLazyLoading=true # Time (seconds) to wait for document, JS framework, and network readiness waitForLazyLoadingTimeout=30 # Initial network quiet window when no activity has been observed yet lazyLoadingNetworkIdleInitialObservationMillis=200 # Required network quiet window after observed XHR/fetch/resource activity lazyLoadingNetworkIdleQuietWindowMillis=500 # Cheap per-action DOM quiet window (0 = off). Navigation uses the next property. lazyLoadingDomStabilityQuietWindowMillis=0 # DOM quiet window after navigateToURL and public waitForLazyLoading() lazyLoadingDomStabilityOnNavigationQuietWindowMillis=300 # Default: eager. Pair with readinessState=interactive pageLoadStrategy=eager readinessState=interactive # Default: 30 seconds to wait for a page navigation to complete browserNavigationTimeout=30 # Default: 30 seconds to wait for the full page DOM to load pageLoadTimeout=30 ``` --- ## Visual Reporting ```properties title="src/main/resources/properties/custom.properties" # Let the granular visual reporting controls below override the default profile evidenceLevel=CUSTOM # Generate an animated GIF of each test in the Allure report createAnimatedGif=true # Record a full MP4 video of each test execution videoParams_recordVideo=true # Keep the Allure 3 Awesome report portable as one HTML file allure.singleFile=true # Group the Allure report tree by Java package and class allure.groupBy=package,testClass ``` --- ## Reliability & Retries ```properties title="src/main/resources/properties/custom.properties" # Automatically retry failed tests up to N times retryMaximumNumberOfAttempts=3 # Retries are additional attempts after the first failure; JUnit retries run # after @AfterEach cleanup and execute @BeforeEach again # Loosen the built-in pre-action checks if your app has unusual rendering forceCheckForElementVisibility=false # Still throws on every retry within the identification timeout; only once that # timeout is exhausted does SHAFT make one last-resort attempt to auto-resolve to # a single displayed-and-enabled match before throwing MultipleElementsFoundException # (see SHAFT_ENGINE #4321) forceCheckElementLocatorIsUnique=false forceCheckTextWasTypedCorrectly=false forceCheckNavigationWasSuccessful=false # Use JavaScript click as a fallback when a WebDriver click fails clickUsingJavascriptWhenWebDriverClickFails=true ``` --- ## Proxy Only needed if you are behind a corporate proxy: ```properties title="src/main/resources/properties/custom.properties" com.SHAFT.proxySettings=proxy.corp.example.com:8080 ``` --- [property types]: [full list of supported properties]: ## Related - [Property Types](/docs/reference/properties/PropertyTypes) - [Properties List](/docs/reference/properties/PropertiesList) - [Common Examples](/docs/reference/properties/CommonExamples) --- # BDD-Style Reports with Allure Annotations Canonical HTML: https://shafthq.github.io/docs/reference/guides/BDD_Style_Reports Markdown: https://shafthq.github.io/md/reference/guides/BDD_Style_Reports.md Many teams adopt Cucumber or similar BDD frameworks to produce **business-readable reports**. However, you can achieve the same report output using **Allure annotations** directly in your Java tests — without the overhead of maintaining `.feature` files, step definitions, and glue code. --- ## Why Allure Annotations Over Cucumber? | Aspect | Cucumber BDD | Allure Annotations | |---|---|---| | Report readability | ✅ Business-readable | ✅ Business-readable | | Maintenance overhead | High — feature files, step defs, glue | Low — annotations on test methods | | Refactoring support | Poor — string matching between files | Full IDE support — rename, find usages | | Debugging | Harder — extra abstraction layer | Direct — standard Java test methods | | Setup complexity | Cucumber plugin + runner + config | Just Allure (already included in SHAFT) | :::tip SHAFT Engine includes Allure out of the box. You do not need any additional dependencies or plugins to use these annotations. ::: --- ## The Allure BDD Annotations Allure provides a hierarchy of annotations that map directly to BDD concepts: | Annotation | BDD Equivalent | Purpose | |---|---|---| | `@Epic("...")` | Epic / Theme | Top-level business capability | | `@Feature("...")` | Feature | Specific feature being tested | | `@Story("...")` | User Story | Individual user story | | `@Description("...")` | Scenario description | Given/When/Then narrative | | `@Step("...")` | Step | Individual test step | --- ## Example: BDD-Style Test with Allure ```java title="RegistrationTest.java" @Epic("User Management") @Feature("Registration") public class RegistrationTest { private SHAFT.GUI.WebDriver driver; @BeforeMethod public void setup() { driver = new SHAFT.GUI.WebDriver(); } @Story("Successful registration") @Description("Given I am on the registration page\n" + "When I fill in valid registration details\n" + "Then I should see a confirmation message") @Test(description = "Verify new user registration") public void testSuccessfulRegistration() { navigateToRegistration(); fillRegistrationForm("user@example.com", "securePass123"); verifyRegistrationSuccess(); } @Step("Navigate to the registration page") private void navigateToRegistration() { driver.browser().navigateToURL("https://example.com/register"); } @Step("Fill in the registration form with email: {email}") private void fillRegistrationForm(String email, String password) { driver.element() .type(By.id("email"), email) .and().type(By.id("password"), password) .and().type(By.id("confirm-password"), password) .and().click(By.id("register-btn")); } @Step("Verify registration success message is displayed") private void verifyRegistrationSuccess() { driver.assertThat(By.id("confirmation")) .text().contains("Registration successful") .withCustomReportMessage("Confirming registration completed successfully"); } @AfterMethod public void teardown() { driver.quit(); } } ``` --- ## What the Report Looks Like The Allure report groups tests by **Epic → Feature → Story**, producing a hierarchy that stakeholders can navigate: ``` 📁 User Management (Epic) 📁 Registration (Feature) 📄 Successful registration (Story) ✅ Verify new user registration → Navigate to the registration page → Fill in the registration form with email: user@example.com → Verify registration success message is displayed ``` The `@Description` text appears in the test detail view, giving business stakeholders the familiar **Given / When / Then** narrative without requiring Cucumber. --- ## Best Practices 1. **Use `@Epic` and `@Feature` at the class level** to group related tests. 2. **Use `@Story` at the method level** to tie each test to a user story. 3. **Write `@Description` in Given/When/Then format** for stakeholder readability. 4. **Extract steps into `@Step`-annotated private methods** to keep test methods clean and reports detailed. 5. **Use `@Step` parameter substitution** (e.g., `{email}`) to make step names dynamic in the report. :::info This approach gives you the best of both worlds: business-readable reports that map to user stories, and simple Java code that is easy to maintain, refactor, and debug. ::: ## Related - [Solution Design](/docs/reference/guides/Solution_Design) - [Test Structure](/docs/reference/guides/Test_Structure) - [Ci Cd Integration](/docs/reference/guides/CI_CD_Integration) - [Quick Start](/docs/start/quick-start) --- # Browser Actions Canonical HTML: https://shafthq.github.io/docs/reference/actions/GUI/Browser_Actions Markdown: https://shafthq.github.io/md/reference/actions/GUI/Browser_Actions.md ## Getting Started To interact with web pages, create an instance of `SHAFT.GUI.WebDriver`: ```java title="DriverSetup.java" SHAFT.GUI.WebDriver driver = new SHAFT.GUI.WebDriver(); ``` SHAFT detects your configuration from [property files](/docs/reference/properties/PropertyTypes). If no properties are set, SHAFT uses sensible defaults. To close all running driver instances: ```java title="DriverTeardown.java" driver.quit(); ``` For trust-gated natural-language browser, element, and touch workflows, see [Natural Language Actions](./Natural_Language_Actions). ## Navigation ### Navigate to URL ```java title="NavigateToURL.java" driver.browser().navigateToURL("https://www.google.com"); ``` Navigates to the specified URL. If the URL matches the current page, it refreshes instead. You can optionally verify the target URL after navigation: ```java title="NavigateToURLWithVerification.java" driver.browser().navigateToURL("https://www.google.com/", "google"); ``` ### Navigate to URL in a New Tab or Window ```java title="NavigateNewTab.java" driver.browser().navigateToURL("https://www.google.com", WindowType.TAB); driver.browser().navigateToURL("https://www.google.com", WindowType.WINDOW); ``` ### Navigate Back ```java title="NavigateBack.java" driver.browser().navigateBack(); ``` Navigates one step back in the browser history. ### Navigate Forward ```java title="NavigateForward.java" driver.browser().navigateForward(); ``` Navigates one step forward in the browser history. ### Refresh Page ```java title="RefreshPage.java" driver.browser().refreshCurrentPage(); ``` Refreshes the current page. ### Get Current URL ```java title="GetCurrentURL.java" String currentUrl = driver.browser().getCurrentURL(); ``` Returns the URL of the current page. ### Navigate to URL with Basic Authentication ```java title="NavigateWithBasicAuth.java" driver.browser().navigateToURLWithBasicAuthentication( "https://staging.example.com/secure", "myUsername", "myPassword", "https://staging.example.com/dashboard" ); ``` Navigates to a URL that requires HTTP Basic Authentication. Provide the target URL, credentials, and the expected URL after a successful login. Useful for staging environments and internal tools protected by basic auth. ## Window Management ### Maximize Window ```java title="MaximizeWindow.java" driver.browser().maximizeWindow(); ``` Maximizes the current browser window. ### Full Screen Window ```java title="FullScreenWindow.java" driver.browser().fullScreenWindow(); ``` Sets the current window to full screen mode. ### Resize Window ```java title="ResizeWindow.java" driver.browser().setWindowSize(1440, 900); ``` Resizes the current window to the specified width and height. ### Get Window Size ```java title="GetWindowSize.java" String windowSize = driver.browser().getWindowSize(); ``` Returns the current window size as a string. ### Get Window Title ```java title="GetWindowTitle.java" String title = driver.browser().getCurrentWindowTitle(); ``` Returns the current window title. ### Close Current Window ```java title="CloseWindow.java" driver.browser().closeCurrentWindow(); ``` Closes the current browser window. ### Switch Windows or Tabs ```java title="SwitchWindows.java" String windowHandle = driver.browser().getWindowHandle(); // ... code that opens a new window ... driver.browser().switchToWindow(windowHandle); // switch back to the original window ``` The `getWindowHandle()` method returns a unique identifier for the current window, which can be used to switch between tabs and windows. ### Get Page Source ```java title="GetPageSource.java" String pageSource = driver.browser().getPageSource(); ``` Returns the current page source as a string. ## Cookies ### Add Cookie ```java title="AddCookie.java" driver.browser().addCookie("cookieName", "cookieValue"); ``` ### Get Cookie ```java title="GetCookie.java" Cookie cookie = driver.browser().getCookie("cookieName"); ``` ### Get All Cookies ```java title="GetAllCookies.java" Set cookies = driver.browser().getAllCookies(); ``` ### Get Cookie Value ```java title="GetCookieValue.java" String cookieValue = driver.browser().getCookieValue("cookieName"); ``` ### Get Cookie Domain ```java title="GetCookieDomain.java" String cookieDomain = driver.browser().getCookieDomain("cookieName"); ``` ### Get Cookie Path ```java title="GetCookiePath.java" String cookiePath = driver.browser().getCookiePath("cookieName"); ``` ### Delete Cookie ```java title="DeleteCookie.java" driver.browser().deleteCookie("cookieName"); ``` ### Delete All Cookies ```java title="DeleteAllCookies.java" driver.browser().deleteAllCookies(); ``` ## Storage State Use storage state when a test needs to reuse an authenticated browser session without repeating the login flow. SHAFT saves cookies, `localStorage`, and `sessionStorage` to a JSON file. `saveStorageState()`/`loadStorageState()` are implemented identically on `SHAFT.GUI.WebDriver` and `SHAFT.GUI.Playwright`, so a file saved from one backend loads on the other. ```java title="SaveStorageState.java" driver.browser() .navigateToURL("https://app.example.com") .and().saveStorageState("target/auth-state.json"); ``` Load storage state after navigating to a compatible origin so browser cookie domain rules can apply. ```java title="LoadStorageState.java" driver.browser() .navigateToURL("https://app.example.com") .and().loadStorageState("target/auth-state.json") .and().refreshCurrentPage(); ``` ### Auto-load storage state on driver init Set `SHAFT.Properties.web.storageStatePath` (property key `storageStatePath`) to a storage-state JSON file and a freshly-initialized driver loads it automatically, without an explicit `loadStorageState()` call: ```java title="AutoLoadStorageState.java" SHAFT.Properties.web.set().storageStatePath("target/auth-state.json"); SHAFT.GUI.WebDriver driver = new SHAFT.GUI.WebDriver(); ``` SHAFT reads the `origin` recorded inside the storage-state file (falling back to `SHAFT.Properties.web.baseURL()` when the file has none) and navigates the fresh driver there first, since cookies cannot be added for an arbitrary domain before any page has loaded. This is fail-soft: a missing file, unreadable origin, or any other load failure is logged as a warning and never fails driver initialization. See [Authentication and session reuse](/docs/reference/actions/GUI/Infrastructure_Network_And_Visual#using-cookies) for the cached-login helper built on top of this property. ## Screenshots and Snapshots ### Capture Screenshot ```java title="CaptureScreenshot.java" driver.browser().captureScreenshot(); ``` Captures a screenshot and attaches it to the Allure report. ### captureSnapshot() vs capturePageSnapshot() SHAFT provides two distinct snapshot methods — choose the one that fits your reporting needs: | Method | What it captures | Attached to Allure | |---|---|---| | `captureSnapshot()` | Full-page screenshot **and** page source | Yes — both screenshot and HTML | | `capturePageSnapshot()` | Serialized DOM/page data only (no image) | Yes — HTML source only | ### captureSnapshot() ```java title="CaptureSnapshot.java" driver.browser().captureSnapshot(); ``` Captures a full page snapshot including both a screenshot and the page source, and attaches both to the Allure report. ### capturePageSnapshot() ```java title="CapturePageSnapshot.java" driver.browser().capturePageSnapshot(); ``` Captures and serializes the current page DOM data and attaches it to the Allure report as an HTML artifact. Use this when you only need the page structure without a visual screenshot. ### generateLightHouseReport() :::warning[Managed setup is not released] The managed flow below depends on [SHAFT Engine issue #4884](https://github.com/ShaftHQ/SHAFT_ENGINE/issues/4884). It is not yet available on `SHAFT_ENGINE` `main` or in a published SHAFT release. Keep using the current Lighthouse flow until a release that contains the managed `LIGHTHOUSE` provider is available. ::: Install and verify the managed Lighthouse profile before running the test. The report action does not install tools or use a global Node or npm command. Use the default SHAFT roots for this preview so the setup CLI and Browser Actions resolve the same managed installation. See [Set up local infrastructure](/docs/start/local-infrastructure/services#install-managed-lighthouse) for the canonical plan, install, policy, and offline-cache instructions. ```java title="LightHouseReport.java" SHAFT.Properties.performance.set().isEnabled(true); driver.browser().generateLightHouseReport(); ``` Run a desktop performance audit on the currently open page. SHAFT invokes its managed Lighthouse 13.4.1 CLI with managed Node 24.19.0 and connects to the debugging port of the local Chromium browser owned by WebDriver. SHAFT does not install Chromium as part of the `LIGHTHOUSE` profile. The action writes a validated HTML file under `lighthouse-reports/` and attaches its contents to the Allure output. It creates no executable JavaScript in the project. A missing or degraded managed toolchain stops the action with the setup command needed to repair it. :::warning[Report opening is opt-in] `openLighthouseReportWhileExecution` defaults to `false`, which is suitable for headless and CI runs. Set `openLighthouseReportWhileExecution=true` only when you want a desktop test run to open the generated HTML report and Java has a supported desktop/default-browser handler. If the handler is unavailable or opening fails, the report action fails before attaching the HTML to Allure. ::: ## Wait Actions ### Wait for Lazy Loading ```java title="WaitForLazyLoading.java" driver.browser().waitForLazyLoading(); ``` Waits for lazy-loaded content to finish loading on the page. On the **Selenium WebDriver** backend this public method uses the **navigation** wait: network/framework readiness plus `lazyLoadingDomStabilityOnNavigationQuietWindowMillis` (default `300`ms). The same navigation wait runs after `navigateToURL` in the current window and after `navigateToURL(url, WindowType)` / new-tab / new-window navigation. Cheap per-action waits (element actions, validations) stay on the global `lazyLoadingDomStabilityQuietWindowMillis` default of `0`. On the **Playwright** backend, `waitForLazyLoading()` still maps to Playwright load-state waiting, not this Selenium DOM-quiet window. `pageLoadStrategy=eager` and `readinessState=interactive` only unblock Selenium and BiDi at `DOMContentLoaded`. SHAFT's JS readiness still waits for `loaded`/`complete` plus XHR/fetch idle; it does not treat `interactive` as document-ready. For scroll-triggered content, call `scrollToLoadAll()` explicitly. SHAFT never auto-sweeps the page during readiness waits. ### Wait for a page condition Use the element action surface with a Selenium condition when a page state must settle before the next action. For content loaded after the initial page, use `waitForLazyLoading()` above. ```java title="WaitForPageState.java" driver.element().waitUntil(ExpectedConditions.titleContains("Dashboard")); ``` ## Network Interception ### Intercept and Mock HTTP Requests ```java title="MockRequest.java" driver.browser() .interceptRequest() .get() .urlContains("/api/data") .respond() .statusCode(200) .jsonBody("{}"); ``` Intercepts browser HTTP requests matching the builder criteria and returns the mocked response. ### Validate Intercepted Responses ```java title="ValidateResponse.java" driver.browser() .interceptRequest() .get() .pathEquals("/api/data") .assertResponse(response -> response .body() .contains("{}")); ``` Use `clearNetworkInterceptors()` to remove active browser network rules before the driver session ends. ### Record, Replay, and Validate Contracts Contract methods capture matching browser traffic into the same deterministic contract file used by `SHAFT.API`. Replay turns captured responses into browser network mocks. Assert and verify modes compare live traffic with the stored contract and attach readable Allure diffs for mismatches. ```java title="BrowserContractActions.java" driver.browser().startContractRecording( "src/test/resources/contracts/search.json", "/api/search"); driver.browser().navigateToURL("https://example.com/search"); SHAFT.Contracts.stopRecording(); driver.browser().assertContract( "src/test/resources/contracts/search.json", "/api/search"); driver.browser().navigateToURL("https://example.com/search"); SHAFT.Contracts.stopValidation(); driver.browser().replayContract("src/test/resources/contracts/search.json"); ``` See [UI and API contract replay](/docs/testing/contracts) for combined browser and API examples. ### Browser Network Profiles DevTools-capable Selenium drivers can switch the active browser session offline, throttle throughput, block resource patterns, and restore the default network state. Unsupported drivers keep the test running and add a deterministic observability warning to the trace metadata. ```java title="NetworkProfiles.java" driver.browser() .goOffline() .and().restoreNetwork() .and().throttleNetwork(250, 64, 32) .and().blockNetworkResources("*.png", "*.jpg"); ``` Call `restoreNetwork()` after a profile-specific assertion when later steps need normal connectivity. ## Mobile Context ### Get and Set Context ```java title="MobileContext.java" String context = driver.browser().getContext(); driver.browser().setContext("WEBVIEW_1"); ``` ### Get Context Handles ```java title="ContextHandles.java" List contexts = driver.browser().getContextHandles(); ``` ## Accessibility Testing SHAFT Engine integrates [axe-core](https://github.com/dequelabs/axe-core) to run automated WCAG accessibility audits. Chain `.accessibility()` onto any browser action to start auditing: ```java title="AccessibilityExample.java" driver.browser().navigateToURL("https://example.com") .accessibility() .assertNoCriticalViolations("Home Page"); ``` For a full reference of all accessibility methods, see [Accessibility Testing](/docs/reference/actions/GUI/Infrastructure_Network_And_Visual#accessibility-testing). ## Fluent Chaining All browser actions support fluent chaining with `.and()`: ```java title="FluentChaining.java" driver.browser() .navigateToURL("https://www.google.com") .and().maximizeWindow() .and().captureScreenshot(); ``` :::tip SHAFT provides automatic reporting for every browser action. Check the **Reporting** section in the sidebar for details on the rich reports generated for each action. ::: ## Tabs and windows `openNewTab(String targetUrl)` opens the URL in a new browser tab and switches focus to it. `openNewWindow(String targetUrl)` does the same in a new window. Both exist on WebDriver and Playwright browser actions. ## Browser sub-actions `driver.browser()` exposes grouped sub-actions with the same API on the WebDriver and Playwright backends. Each group returns to browser actions with `and()`. | Accessor | Purpose and main methods | | --- | --- | | `network()` | Network conditions: `offline()`, `online()`, `throttle(long latencyMs, long downloadKbps, long uploadKbps)`, and `block(String... urlPatterns)` to block matching requests. | | `storage()` | Web storage: `local()` and `session()` return key-value controls (`get`, `set`, `remove`, `clear`); `state()` returns storage-state controls with `save(String filePath)` and `load(String filePath)`. | | `downloads()` | Downloads: `all()` lists captured downloads, `latest()` returns the newest one, `waitFor(Runnable trigger)` or `waitFor(Predicate , Runnable trigger)` runs the trigger and waits for a matching download, and `clear()` resets the list. | | `emulation()` | Device emulation: `screen()` (viewport and screen size), `location()` (geolocation, timezone, locale), `media()` (media type, color scheme, reduced motion), and `runtime()` (user agent, scripting override). | | `dialog()` | Handles JavaScript dialogs (alert, confirm, prompt). | | `context()` | Browser context controls. | | `console()` | Reads captured browser console messages. | | `script()` | Runs JavaScript in the page. | | `permissions()` | Grants or clears browser permissions. | | `authentication()` | Registers HTTP authentication credentials. | ```java driver.browser().network().throttle(200, 500, 250).and() .storage().local().set("feature", "on").and().and() .navigateToURL("https://example.com"); BrowserDownload report = driver.browser().downloads() .waitFor(() -> driver.element().click(By.id("export"))); driver.browser().emulation().location().geolocation(30.04, 31.23).timezone("Africa/Cairo"); ``` ## Playwright-only browser helpers | Method | What it does | | --- | --- | | `locator(String selector)` | Returns a native Playwright `Locator` on the current page. | | `getNativePage()` | Returns the underlying Playwright `Page` for advanced use. | | `registerNetworkInterceptionRule(BrowserNetworkInterceptionRule rule, String successMessage)` | Adds a network interception rule (for example a mock) to the session and logs the message. | ## Related - [Element Actions](/docs/reference/actions/GUI/Element_Actions) - [Element Identification](/docs/reference/actions/GUI/Element_Identification) - [Using Cookies / authentication and session reuse](/docs/reference/actions/GUI/Infrastructure_Network_And_Visual#using-cookies) - [Network Mocking and HAR replay](/docs/reference/actions/GUI/Infrastructure_Network_And_Visual#network-mocking) - [Web](/docs/testing/web) --- # Built-in Cucumber BDD Step Definitions Canonical HTML: https://shafthq.github.io/docs/reference/guides/Cucumber_BDD_Steps Markdown: https://shafthq.github.io/md/reference/guides/Cucumber_BDD_Steps.md SHAFT Engine ships with **500+ pre-built Gherkin step definitions** covering browser navigation, element actions, and assertions. You can write feature files and start running BDD tests immediately — without authoring glue code from scratch. :::tip This page describes how to **use Cucumber with SHAFT**. If you prefer a simpler BDD approach without `.feature` files, see [BDD-Style Reports with Allure Annotations](BDD_Style_Reports). ::: --- ## Maven Dependency Add the SHAFT Cucumber integration dependency to your `pom.xml`: ```xml title="pom.xml" io.github.shafthq shaft-engine ``` SHAFT's built-in Cucumber steps are included in the core engine — no separate artifact is required. --- ## Test Runner Setup Create a runner class using JUnit Platform Suite: ```java title="CucumberTestRunner.java" @Suite @IncludeEngines("cucumber") @SelectClasspathResource("features") @ConfigurationParameter( key = "cucumber.glue", value = "com.shaft.cucumber,your.steps.package" ) @ConfigurationParameter( key = "cucumber.plugin", value = "io.qameta.allure.cucumber7jvm.AllureCucumber7Jvm" ) public class CucumberTestRunner { } ``` :::info Always include **both** `com.shaft.cucumber` (SHAFT's built-in steps) and your own steps package in the `cucumber.glue` value. Separate them with a comma. ::: --- ## Feature File Examples ### Browser Navigation and Element Interaction ```gherkin title="login.feature" Feature: User Login Scenario: Successful login with valid credentials Given I open the target browser When I navigate to "https://example.com/login" And I type "testuser" into the element found by "id" "username" And I type "password123" into the element found by "id" "password" And I click the element found by "id" "loginBtn" Then I assert that the "title" attribute of the browser, equals "Dashboard" And I assert that the element found by "id" "welcomeMsg", exists ``` ### Assertions and Validations ```gherkin title="profile.feature" Feature: User Profile Scenario: Profile page displays correct user information Given I open the target browser And I navigate to "https://example.com/profile" Then I assert that the element found by "id" "fullName", text contains "John Doe" And I assert that the element found by "id" "email", text equals "john@example.com" And I assert that the element found by "id" "role", is visible And I close the current browser ``` --- ## Available Step Packages SHAFT's built-in steps are organised into the following packages under `com.shaft.cucumber`: | Package | Steps Provided | |---|---| | `BrowserSteps` | Open/close browser, navigation, URL and title assertions | | `ElementSteps` | Type, click, select, scroll, hover, drag-and-drop | | `AssertionSteps` | Assert element existence, visibility, text, and attributes | | `AlertSteps` | Accept, dismiss, and interact with browser alerts | | `APISteps` | Send requests and validate responses in Gherkin | --- ## Adding Custom Step Definitions Create your own step definitions in a separate package and include it in `cucumber.glue`: ```java title="CustomSteps.java" package your.steps.package; public class CustomSteps { private SHAFT.GUI.WebDriver driver; @Given("I am logged in as {string}") public void loginAs(String username) { driver = new SHAFT.GUI.WebDriver(); driver.browser().navigateToURL("https://example.com/login"); driver.element() .type(By.id("username"), username) .type(By.id("password"), "defaultPassword") .click(By.id("loginBtn")); } @Then("I should see the dashboard") public void verifyDashboard() { driver.assertThat().browser().url().contains("/dashboard"); } } ``` --- :::warning Do not remove `com.shaft.cucumber` from the `cucumber.glue` configuration. Removing it disables all of SHAFT's built-in steps and will cause step-not-found errors. ::: :::note Allure reporting for Cucumber is enabled by adding `io.qameta.allure.cucumber7jvm.AllureCucumber7Jvm` to `cucumber.plugin`. This produces the same rich Allure report as non-Cucumber SHAFT tests. ::: ## Related - [Solution Design](/docs/reference/guides/Solution_Design) - [Test Structure](/docs/reference/guides/Test_Structure) - [Ci Cd Integration](/docs/reference/guides/CI_CD_Integration) - [Quick Start](/docs/start/quick-start) --- # Common Configuration Examples Canonical HTML: https://shafthq.github.io/docs/reference/properties/CommonExamples Markdown: https://shafthq.github.io/md/reference/properties/CommonExamples.md Here are common scenarios and how to configure SHAFT properties for them. Each example shows three ways to configure properties: **File-based**, **CLI-based**, and **Code-based**. ## Example 1: Run tests in headless mode on Firefox browser To run tests in headless mode on Firefox, you need to set both `targetBrowserName` and `headlessExecution` properties: ```properties title="src/main/resources/properties/custom.properties" targetBrowserName=firefox headlessExecution=true ``` ```bash mvn test -DtargetBrowserName=firefox -DheadlessExecution=true ``` ```java SHAFT.Properties.web.set() .targetBrowserName(Browser.FIREFOX.browserName()) .headlessExecution(true); ``` --- ## Example 2: Run tests on Chrome with mobile emulation ```properties title="src/main/resources/properties/custom.properties" targetBrowserName=chrome isMobileEmulation=true mobileEmulation.deviceName=iPhone12Pro ``` ```bash mvn test -DtargetBrowserName=chrome -DisMobileEmulation=true -DmobileEmulation.deviceName=iPhone12Pro ``` ```java SHAFT.Properties.web.set() .targetBrowserName(Browser.CHROME.browserName()) .isMobileEmulation(true) .mobileEmulationDeviceName("iPhone12Pro"); ``` --- ## Example 3: Remote execution on Selenium Grid For remote execution on a Selenium Grid, you need to configure the execution address, target OS, and optionally enable headless mode: :::tip Learn how to set up your own local Selenium Grid in the [Local Selenium Grid Execution](/docs/reference/actions/GUI/Infrastructure_Network_And_Visual#local-selenium-grid) guide. ::: ```properties title="src/main/resources/properties/custom.properties" executionAddress=192.168.1.100:4444 targetOperatingSystem=Linux headlessExecution=true targetBrowserName=chrome ``` ```bash mvn test -DexecutionAddress=192.168.1.100:4444 -DtargetOperatingSystem=Linux -DheadlessExecution=true -DtargetBrowserName=chrome ``` ```java SHAFT.Properties.platform.set() .executionAddress("192.168.1.100:4444") .targetPlatform(Platform.LINUX.name()); SHAFT.Properties.web.set() .targetBrowserName(Browser.CHROME.browserName()) .headlessExecution(true); ``` --- ## Example 4: BrowserStack execution For running tests on BrowserStack cloud platform: ```properties title="src/main/resources/properties/custom.properties" executionAddress=browserstack targetOperatingSystem=Windows targetBrowserName=chrome browserStack.userName=your_username browserStack.accessKey=your_access_key browserStack.osVersion=11 ``` ```bash mvn test -DexecutionAddress=browserstack -DtargetOperatingSystem=Windows -DtargetBrowserName=chrome ``` Note: For CLI execution, configure `browserStack.userName` and `browserStack.accessKey` in your properties file for security. ```java SHAFT.Properties.platform.set() .executionAddress("browserstack") .targetPlatform(Platform.WINDOWS.name()); SHAFT.Properties.web.set() .targetBrowserName(Browser.CHROME.browserName()); SHAFT.Properties.browserStack.set() .userName("your_username") .accessKey("your_access_key") .osVersion("11"); ``` --- ## Example 5: Native mobile app testing (Android) For testing native Android applications using Appium: ```properties title="src/main/resources/properties/custom.properties" targetOperatingSystem=ANDROID executionAddress=localhost:4723 mobile_platformVersion=13.0 mobile_deviceName=emulator-5554 mobile_automationName=UiAutomator2 mobile_app=src/test/resources/apps/ApiDemos-debug.apk mobile_appPackage=io.appium.android.apis mobile_appActivity=.view.Controls1 ``` ```bash mvn test -DtargetOperatingSystem=ANDROID -DexecutionAddress=localhost:4723 -Dmobile_platformVersion=13.0 -Dmobile_deviceName=emulator-5554 -Dmobile_automationName=UiAutomator2 -Dmobile_app=src/test/resources/apps/ApiDemos-debug.apk ``` ```java @BeforeClass public void beforeClass() { SHAFT.Properties.platform.set() .targetPlatform(Platform.ANDROID.name()) .executionAddress("localhost:4723"); SHAFT.Properties.mobile.set() .platformVersion("13.0") .deviceName("emulator-5554") .automationName(AutomationName.ANDROID_UIAUTOMATOR2) .app("src/test/resources/apps/ApiDemos-debug.apk") .appPackage("io.appium.android.apis") .appActivity(".view.Controls1"); } @Test public void testNativeAndroidApp() { driver = new SHAFT.GUI.WebDriver(); driver.element().type(AppiumBy.accessibilityId("Views"), "Test Input"); // Your test code here } ``` --- ## Example 6: Mobile web testing For testing web applications on mobile browsers using Appium: ```properties title="src/main/resources/properties/custom.properties" # Android Mobile Web targetOperatingSystem=ANDROID executionAddress=localhost:4723 mobile_platformVersion=13.0 mobile_deviceName=emulator-5554 mobile_automationName=UiAutomator2 browserName=chrome # iOS Mobile Web (uncomment for iOS) # targetOperatingSystem=IOS # mobile_platformVersion=16.0 # mobile_deviceName=iPhone 14 # mobile_automationName=XCUITest # browserName=safari ``` ```bash # Android mvn test -DtargetOperatingSystem=ANDROID -DexecutionAddress=localhost:4723 -Dmobile_platformVersion=13.0 -Dmobile_deviceName=emulator-5554 -DbrowserName=chrome # iOS mvn test -DtargetOperatingSystem=IOS -DexecutionAddress=localhost:4723 -Dmobile_platformVersion=16.0 -Dmobile_deviceName="iPhone 14" -DbrowserName=safari ``` ```java @BeforeClass public void beforeClass() { // Android Mobile Web SHAFT.Properties.platform.set() .targetPlatform(Platform.ANDROID.name()) .executionAddress("localhost:4723"); SHAFT.Properties.mobile.set() .platformVersion("13.0") .deviceName("emulator-5554") .automationName(AutomationName.ANDROID_UIAUTOMATOR2) .browserName("chrome"); // For iOS Mobile Web, uncomment below: // SHAFT.Properties.platform.set() // .targetPlatform(Platform.IOS.name()) // .executionAddress("localhost:4723"); // // SHAFT.Properties.mobile.set() // .platformVersion("16.0") // .deviceName("iPhone 14") // .automationName(AutomationName.IOS_XCUI_TEST) // .browserName("safari"); } @Test public void testMobileWeb() { driver = new SHAFT.GUI.WebDriver(); driver.browser().navigateToURL("https://example.com"); driver.element().type(By.id("username"), "testuser"); // Your test code here } ``` --- ## Example 7: Enable maximum performance mode Choose a maximum performance mode to reduce optional work during execution: ```properties title="src/main/resources/properties/custom.properties" maximumPerformanceMode=2 ``` ```bash mvn test -DmaximumPerformanceMode=2 ``` ```java SHAFT.Properties.flags.set().maximumPerformanceMode(2); ``` **Performance Mode Values:** - `0` = Disabled (default) - `1` = Enabled without headless execution - `2` = Enabled with headless execution (fastest, ~400% performance boost) --- ## Example 8: Cross-browser testing Run the same test across multiple browsers sequentially or in parallel: ```properties title="src/main/resources/properties/custom.properties" # Sequential execution across Chrome, Firefox, and Safari SHAFT.CrossBrowserMode=sequential # Parallel execution (requires Docker Desktop) # SHAFT.CrossBrowserMode=parallelized ``` ```bash mvn test -DSHAFT.CrossBrowserMode=sequential ``` ```java SHAFT.Properties.platform.set().crossBrowserMode("sequential"); // or for parallel: .crossBrowserMode("parallelized"); ``` **Cross-Browser Mode Values:** - `off` = Normal execution with configured browser (default) - `sequential` = Tests run on Chrome, Firefox, and Safari in sequence - `parallelized` = Tests run on all three browsers in parallel :::note Cross-browser mode requires Docker Desktop to be installed and configured to use Linux images. ::: --- ## Example 9: API testing with Swagger validation and OpenAPI coverage Enable Swagger/OpenAPI contract validation and operation coverage reporting for API tests: ```properties title="src/main/resources/properties/custom.properties" swagger.validation.enabled=true swagger.validation.url=https://petstore.swagger.io/v2/swagger.json openapi.coverage.report.enabled=true openapi.coverage.threshold=80 ``` ```bash mvn test -Dswagger.validation.enabled=true -Dswagger.validation.url=https://petstore.swagger.io/v2/swagger.json -Dopenapi.coverage.report.enabled=true -Dopenapi.coverage.threshold=80 ``` ```java SHAFT.Properties.api.set() .swaggerValidationEnabled(true) .swaggerValidationUrl("https://petstore.swagger.io/v2/swagger.json") .openApiCoverageReportEnabled(true) .openApiCoverageThreshold(80); ``` The coverage report lists exercised operations, untested operations, requests without a matching contract entry, and validation failures linked to operation IDs. --- ## Example 10: Screenshot and video recording configuration Configure when and how to capture screenshots and videos: ```properties title="src/main/resources/properties/custom.properties" evidenceLevel=CUSTOM # Screenshot configuration screenshotParams_whenToTakeAScreenshot=Always screenshotParams_screenshotType=FullPage screenshotParams_highlightElements=true screenshotParams_watermark=true # Video recording videoParams_recordVideo=true videoParams_scope=DriverSession # Animated GIF creation createAnimatedGif=true animatedGif_frameDelay=500 ``` ```bash mvn test -DevidenceLevel=CUSTOM -DscreenshotParams_whenToTakeAScreenshot=Always -DvideoParams_recordVideo=true -DcreateAnimatedGif=true ``` ```java SHAFT.Properties.reporting.set().evidenceLevel("CUSTOM"); SHAFT.Properties.visuals.set() .screenshotParamsWhenToTakeAScreenshot("Always") .screenshotParamsScreenshotType("FullPage") .screenshotParamsHighlightElements(true) .videoParamsRecordVideo(true) .createAnimatedGif(true); ``` --- ## More Resources For a complete list of all available properties, their default values, and descriptions, visit: - [Properties Types](PropertyTypes.md) - Learn about the three ways to configure properties - [Properties List](PropertiesList.mdx) - Complete reference of all SHAFT properties ## Related - [Property Types](/docs/reference/properties/PropertyTypes) - [Properties List](/docs/reference/properties/PropertiesList) - [Programmatic Config](/docs/reference/properties/Programmatic_Config) --- # Complete Properties Reference Canonical HTML: https://shafthq.github.io/docs/reference/properties/PropertiesList Markdown: https://shafthq.github.io/md/reference/properties/PropertiesList.md You can use these tab groups to navigate and get information about how to manage your desired properties in all the different ways possible. The **Config Generator** tab is an interactive builder covering every property below — search, click to add, edit the value, then copy or download the resulting `.properties` file. *You can read/write any property programmatically to provide more flexibility and control during runtime.* *You can set some properties via the CLI command that you use to execute your tests.* *The traditional way of configuring SHAFT properties, using this approach you can simply create your own `custom.properties` file.* *The default/preset values built into SHAFT, with a description of what each one does and the possible values.* Build a `.properties` file interactively instead of copying keys by hand: search or browse by category, click a property to add it, edit its value, then copy or download the generated file(s) below. ## Platform - These properties control the basic target execution platform settings, like execution location and target operating system. ```java showLineNumbers import com.shaft.driver.SHAFT; import org.openqa.selenium.Platform; /** get **/ String crossBrowserMode = SHAFT.Properties.platform.crossBrowserMode(); String executionAddress = SHAFT.Properties.platform.executionAddress(); String targetPlatform = SHAFT.Properties.platform.targetPlatform(); String proxy = SHAFT.Properties.platform.proxy(); Boolean driverProxy = SHAFT.Properties.platform.driverProxySettings(); Boolean jvmProxy = SHAFT.Properties.platform.jvmProxySettings(); Boolean enableBiDi = SHAFT.Properties.platform.enableBiDi(); /** set **/ SHAFT.Properties.platform.set().crossBrowserMode("off"); SHAFT.Properties.platform.set().executionAddress("local"); SHAFT.Properties.platform.set().targetPlatform(Platform.LINUX.name()); SHAFT.Properties.platform.set().proxySettings(""); SHAFT.Properties.platform.set().driverProxySettings(true); SHAFT.Properties.platform.set().jvmProxySettings(true); SHAFT.Properties.platform.set().enableBiDi(true); ``` ```powershell mvn -e test "-DexecutionAddress=localhost:4444" "-DtargetOperatingSystem=LINUX" ``` ```properties showLineNumbers title="src/main/resources/properties/custom.properties" SHAFT.CrossBrowserMode=off executionAddress=local targetOperatingSystem=LINUX com.SHAFT.proxySettings= driverProxySettings=true jvmProxySettings=true enableBiDi=true ``` | Property Name | Default Value | Possible Values | Description | | ----------------------- | ------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | SHAFT.CrossBrowserMode | `off` | `off`, `sequential`, `parallelized` | • Cross Browser Mode allows SHAFT to run your test class against Chrome, Firefox, and Safari! • You need to have 'Docker Desktop' installed on your machine, and configured to use Linux images. • Off → Your tests will run normally and respect your configuration. • Sequential → Your tests will run on Chrome, Firefox, and Safari in sequence. • Parallelized → Your tests will run on Chrome, Firefox and Safari in parallel. And for each browser they will run in sequence. | | executionAddress | `local` | `local`, `dockerized`, `testcontainers`, `browserstack`, `host:port`, `http://host:port/wd/hub` | • For Appium, set the below settings and move to the Mobile tab to continue. • For BrowserStack, set the "Target Operating System" below, and the "Automation Name" in the Mobile tab, then configure the "browserStack.properties" file in your project directory. • `testcontainers` starts one `selenium/standalone- ` container per JVM through Testcontainers. Add `org.testcontainers:testcontainers` to your test classpath and have Docker running; SHAFT fails fast with a clear message when either is missing. | | targetOperatingSystem | `Linux` | `Linux`, `Windows`, `Mac`, `Android`, `iOS` | • The target operating system for test execution. | | com.SHAFT.proxySettings | ` ` | `host:port` | • Used to configure testing behind a proxy. e.g. corporate proxy. | | driverProxySettings | `true` | `true`, `false` | • To enable or disable the driver proxy. | | jvmProxySettings | `true` | `true`, `false` | • To enable or disable the JVM proxy. | | enableBiDi | `true` | `true`, `false` | • To enable or disable the [WebDriver BiDi protocol](https://www.selenium.dev/documentation/webdriver/bidirectional/webdriver_bidi/). | | remotePreflightEnabled | `false` | `true`, `false` | • Query Selenium Grid `/status` and `/graphql` before remote sessions and attach a preflight summary when Grid metadata is available. | | remoteAdaptiveSessionThrottling | `false` | `true`, `false` | • Limit local remote-session creation to the detected matching Selenium Grid slot count. | | remotePreflightFailFast | `false` | `true`, `false` | • Fail before WebDriver retries when Grid preflight proves the requested browser slot is unavailable. | | remotePreflightTimeoutSeconds | `5` | seconds | • Timeout for each Grid preflight `/status` or `/graphql` call. | ## Web - These properties control web GUI test automation settings, like target browser, headless execution, and mobile emulation. ```java showLineNumbers import com.shaft.driver.SHAFT; import org.openqa.selenium.remote.Browser; /** get **/ String targetBrowserName = SHAFT.Properties.web.targetBrowserName(); boolean headlessExecution = SHAFT.Properties.web.headlessExecution(); boolean forceBrowserDownload = SHAFT.Properties.web.forceBrowserDownload(); boolean incognitoMode = SHAFT.Properties.web.incognitoMode(); boolean isMobileEmulation = SHAFT.Properties.web.isMobileEmulation(); boolean mobileEmulationIsCustomDevice = SHAFT.Properties.web.mobileEmulationIsCustomDevice(); String mobileEmulationDeviceName = SHAFT.Properties.web.mobileEmulationDeviceName(); int mobileEmulationWidth = SHAFT.Properties.web.mobileEmulationWidth(); int mobileEmulationHeight = SHAFT.Properties.web.mobileEmulationHeight(); double mobileEmulationPixelRatio = SHAFT.Properties.web.mobileEmulationPixelRatio(); String mobileEmulationUserAgent = SHAFT.Properties.web.mobileEmulationUserAgent(); String baseURL = SHAFT.Properties.web.baseURL(); int browserWindowWidth = SHAFT.Properties.web.browserWindowWidth(); int browserWindowHeight = SHAFT.Properties.web.browserWindowHeight(); String storageStatePath = SHAFT.Properties.web.storageStatePath(); /** set **/ SHAFT.Properties.web.set().targetBrowserName(Browser.CHROME.browserName()) .headlessExecution(false) .forceBrowserDownload(false) .incognitoMode(false) .isMobileEmulation(false) .mobileEmulationIsCustomDevice(false) .mobileEmulationDeviceName("") .mobileEmulationWidth(360) .mobileEmulationHeight(640) .mobileEmulationPixelRatio(1.0) .mobileEmulationUserAgent("") .baseURL("") .browserWindowWidth(1920) .browserWindowHeight(1080) .storageStatePath(""); ``` ```powershell mvn -e test "-DtargetBrowserName=FIREFOX" "-DheadlessExecution=true" "-DforceBrowserDownload=true" ``` ```properties showLineNumbers title="src/main/resources/properties/custom.properties" targetBrowserName=chrome forceBrowserDownload=false headlessExecution=false incognitoMode=false isMobileEmulation=false mobileEmulation.isCustomDevice=false mobileEmulation.deviceName= mobileEmulation.width= mobileEmulation.height= mobileEmulation.pixelRatio=1.0 mobileEmulation.userAgent= baseURL= browserWindowWidth=1920 browserWindowHeight=1080 pageLoadStrategy=eager readinessState=interactive storageStatePath= ``` | Property Name | Default Value | Possible Values | Description | | ------------------------------ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------- | | targetBrowserName | `chrome` | `chrome`, `firefox`, `safari`, `edge` | • The target web browser for test execution. | | forceBrowserDownload | `false` | `true`, `false` | • This only works for Chrome and Firefox. • Allows Selenium Manager to download a Chrome for Testing browser build compatible with the current Selenium/browser resolution flow. | | headlessExecution | `false` | `true`, `false` | • This only works for Chrome, Firefox and Edge. | | incognitoMode | `false` | `true`, `false` | • Enable browser incognito/private mode. | | isMobileEmulation | `false` | `true`, `false` | • This only works for Chrome and Edge. | | mobileEmulation.isCustomDevice | `false` | `true`, `false` | • This only works for Chrome and Edge. | | mobileEmulation.deviceName | ` ` | `blackberryZ30`, `BlackberryPlayBook`, `galaxyNote3`, `galaxyNoteII`, `galaxySIII`, `galaxyS5`, `galaxyS8`, `samsungGalaxyS8+`, `galaxyS9+`, `galaxyTabS4`, `galaxyFold`, `samsungGalaxyS20Ultra`, `samsungGalaxyA51/71`, `kindleFireHDX`, `lgOptimusL70`, `microsoftLumia550`, `microsoftLumia950`, `motoG4`, `nexus10`, `nexus4`, `nexus5`, `nexus5X`, `nexus6`, `nexus6P`, `nexus7`, `nokiaLumia520`, `nokiaN9`, `nestHub`, `nestHubMax`, `pixel2`, `pixel2XL`, `pixel3`, `pixel3XL`, `pixel4`, `pixel5`, `jioPhone2`, `iPhone4`, `iPhone5/SE`, `iPhone6/7/8`, `iPhone6/7/8Plus`, `iPhoneSE`, `iPhoneX`, `iPhoneXR`, `iPhone12Pro`, `iPad`, `iPadPro`, `iPadAir`, `iPadMini`, `surfacePro7`, `surfaceDuo` | • This only works for Chrome and Edge. | | mobileEmulation.width | ` ` | example: `360` | • This only works for Chrome and Edge. | | mobileEmulation.height | ` ` | example: `600` | • This only works for Chrome and Edge. | | mobileEmulation.pixelRatio | `1.0` | example: `2.0` | • This only works for Chrome and Edge. | | mobileEmulation.userAgent | ` ` | example: `Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:35.0) Gecko/20100101 Firefox/35.0` | • This only works for Chrome and Edge. | | baseURL | ` ` | example: `https://github.com/ShaftHQ/SHAFT_ENGINE` | • Base URL for the application under test. | | browserWindowWidth | `1920` | | • Won't work if autoMaximizeBrowserWindow is enabled. | browserWindowHeight | `1080` | | • Won't work if autoMaximizeBrowserWindow is enabled. | pageLoadStrategy | `eager` | `none`, `eager`, `normal` | • Controls when Selenium considers the page loaded. Default `eager` waits until `DOMContentLoaded`. | readinessState | `interactive` | `interactive`, `none`, `complete` | • Controls the document readiness state to wait for on BiDi navigation. Default `interactive`. | storageStatePath | ` ` | example: `target/auth-state.json` | • Path to a storage-state JSON file automatically loaded into a freshly-initialized driver. • SHAFT navigates to the file's recorded origin (falling back to `baseURL`) first; a load failure is logged as a warning and never fails driver init. • Blank (default) disables the feature. | failOnBrowserConsoleErrors | `false` | `true`, `false` | • Fail a passing test when the browser console captured error-level messages that don't match `browserConsoleErrorAllowlist`. The messages are attached to the report. | | browserConsoleErrorAllowlist | ` ` | Java regex, example: `favicon\.ico\|ResizeObserver loop` | • Console errors matching this pattern never fail a test. Combine patterns with `\|`. | ## Playwright - These properties control the `SHAFT.GUI.Playwright` backend. Browser name, headless mode, base URL, and viewport still come from the Web properties unless a Playwright-specific override is listed below. ```java showLineNumbers import com.shaft.driver.SHAFT; /** get **/ String browserName = SHAFT.Properties.playwright.browserName(); String deviceName = SHAFT.Properties.playwright.deviceName(); String connectionMode = SHAFT.Properties.playwright.connectionMode(); String endpoint = SHAFT.Properties.playwright.endpoint(); String channel = SHAFT.Properties.playwright.channel(); int slowMo = SHAFT.Properties.playwright.slowMo(); int launchTimeout = SHAFT.Properties.playwright.launchTimeoutMilliseconds(); int defaultTimeout = SHAFT.Properties.playwright.defaultTimeoutMilliseconds(); int navigationTimeout = SHAFT.Properties.playwright.navigationTimeoutMilliseconds(); String artifactsDirectory = SHAFT.Properties.playwright.artifactsDirectory(); String downloadsDirectory = SHAFT.Properties.playwright.downloadsDirectory(); boolean acceptDownloads = SHAFT.Properties.playwright.acceptDownloads(); boolean tracingEnabled = SHAFT.Properties.playwright.tracingEnabled(); /** set **/ SHAFT.Properties.playwright.set() .browserName("chromium") .deviceName("") .connectionMode("local") .endpoint("") .channel("") .slowMo(0) .launchTimeoutMilliseconds(30000) .defaultTimeoutMilliseconds(30000) .navigationTimeoutMilliseconds(30000) .artifactsDirectory("target/playwright-artifacts") .downloadsDirectory("") .acceptDownloads(true) .tracingEnabled(false) .tracingOnRetryOnly(true) .tracingScreenshots(true) .tracingSnapshots(true) .tracingSources(true); ``` ```powershell mvn -e test "-Dplaywright.browserName=chromium" "-Dplaywright.connectionMode=local" ``` ```properties showLineNumbers title="src/main/resources/properties/custom.properties" playwright.browserName=chromium playwright.deviceName= playwright.connectionMode=local playwright.endpoint= playwright.channel= playwright.slowMo=0 playwright.launchTimeoutMilliseconds=30000 playwright.defaultTimeoutMilliseconds=30000 playwright.navigationTimeoutMilliseconds=30000 playwright.artifactsDirectory=target/playwright-artifacts playwright.downloadsDirectory= playwright.acceptDownloads=true playwright.tracing.enabled=false playwright.tracing.onRetryOnly=true playwright.tracing.screenshots=true playwright.tracing.snapshots=true playwright.tracing.sources=true ``` | Property Name | Default Value | Possible Values | Description | | ------------------------------------------ | ----------------------------- | --------------- | ----------- | | playwright.browserName | ` ` | `chromium`, `chrome`, `firefox`, `webkit`, `safari`, `edge` | Optional Playwright browser engine override. When empty, SHAFT uses the device descriptor browser type or falls back to `targetBrowserName`. | | playwright.deviceName | ` ` | Any Microsoft Playwright device descriptor name, `Galaxy S26 Ultra`, `iPhone 17 Pro Max` | Device descriptor applied to new browser contexts. | | playwright.connectionMode | `local` | `local`, `connect`, `connectOverCDP` | Launch a local browser, connect over Playwright protocol, or connect over CDP. | | playwright.endpoint | ` ` | WebSocket or CDP endpoint | Remote endpoint used when `connectionMode` is `connect` or `connectOverCDP`. | | playwright.channel | ` ` | Chromium channel such as `chrome` or `msedge` | Optional Chromium channel passed to Playwright launch options. | | playwright.slowMo | `0` | milliseconds | Slow motion delay for Playwright actions. | | playwright.launchTimeoutMilliseconds | `30000` | milliseconds | Timeout for launching or connecting to a browser. | | playwright.defaultTimeoutMilliseconds | `30000` | milliseconds | Default timeout applied to Playwright page actions. | | playwright.navigationTimeoutMilliseconds | `30000` | milliseconds | Default timeout applied to Playwright page navigation. | | playwright.artifactsDirectory | `target/playwright-artifacts` | relative or absolute path | Directory for Playwright traces and artifacts. | | playwright.downloadsDirectory | ` ` | relative or absolute path | Optional downloads directory. Empty uses Playwright defaults. | | playwright.acceptDownloads | `true` | `true`, `false` | Allow Playwright browser contexts to accept downloads. | | playwright.timezoneId | ` ` | IANA time zone identifier | Optional time zone for new browser contexts. Empty uses the browser default. | | playwright.locale | ` ` | locale identifier | Optional locale for new browser contexts. Empty uses the browser default. | | playwright.userAgent | ` ` | user-agent string | Optional user agent for new browser contexts. Empty uses the browser or device default. | | playwright.javaScriptEnabled | `true` | `true`, `false` | Enables JavaScript in new browser contexts. | | playwright.tracing.enabled | `false` | `true`, `false` | Start Playwright tracing for the session. | | playwright.tracing.onRetryOnly | `true` | `true`, `false` | Enable tracing automatically only during SHAFT retry evidence capture. | | playwright.tracing.screenshots | `true` | `true`, `false` | Include screenshots in trace artifacts. | | playwright.tracing.snapshots | `true` | `true`, `false` | Include DOM snapshots in trace artifacts. | | playwright.tracing.sources | `true` | `true`, `false` | Include source files in trace artifacts. | ## Mobile - These properties control web and/or native mobile GUI test automation settings, like target platform version, path to the app under test, and the activity name that you want to start testing. ```java showLineNumbers import com.shaft.driver.SHAFT; import org.openqa.selenium.Platform; import io.appium.java_client.remote.AutomationName; /** get **/ String platformName = SHAFT.Properties.mobile.platformName(); String platformVersion = SHAFT.Properties.mobile.platformVersion(); String deviceName = SHAFT.Properties.mobile.deviceName(); String automationName = SHAFT.Properties.mobile.automationName(); String udid = SHAFT.Properties.mobile.udid(); String browserName = SHAFT.Properties.mobile.browserName(); String browserVersion = SHAFT.Properties.mobile.browserVersion(); String app = SHAFT.Properties.mobile.app(); String appPackage = SHAFT.Properties.mobile.appPackage(); String appActivity = SHAFT.Properties.mobile.appActivity(); String bundleId = SHAFT.Properties.mobile.bundleId(); /** set **/ SHAFT.Properties.platform.set().targetPlatform(Platform.ANDROID.name()); SHAFT.Properties.mobile.set().platformVersion("13.0") .deviceName("Google Pixel 7") .automationName(AutomationName.ANDROID_UIAUTOMATOR2) .udid("") .browserName("") .browserVersion("") .app("src/test/resources/testDataFiles/apps/ApiDemos-debug.apk") .appPackage("") .appActivity("") .bundleId(""); ``` ```powershell mvn -e test "-Dmobile_platformVersion=13.0" "-Dmobile_deviceName=Google Pixel 7" ``` ```properties showLineNumbers title="src/main/resources/properties/custom.properties" targetOperatingSystem=ANDROID mobile_platformVersion=13.0 mobile_deviceName=Google Pixel 7 mobile_automationName=UiAutomator2 mobile_udid= browserName= MobileBrowserVersion= mobile_app=src/test/resources/testDataFiles/apps/ApiDemos-debug.apk mobile_appPackage= mobile_appActivity= mobile_bundleId= ``` | Property Name | Default Value | Possible Values | Description | | ---------------------------- | -------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | platformName | ` ` | example: `Android`, `iOS` | • Raw Appium `platformName` capability. Usually derived automatically from `targetOperatingSystem`; set this to override it directly. | | mobile_platformVersion | ` ` | example: `11.0, 13.0` | • You can add any property from the List of Appium Capabilities directly to your .property files or via CLI arguments, just make sure to add `mobile_` as a prefix. | | mobile_deviceName | ` ` | example: `ANDROID_EMULATOR` | • You can add any property from the List of Appium Capabilities directly to your .property files or via CLI arguments, just make sure to add `mobile_` as a prefix. | | mobile_automationName | `UiAutomator2` | `UiAutomator2`, `Espresso`, `XCUITest` | • You can add any property from the List of Appium Capabilities directly to your .property files or via CLI arguments, just make sure to add `mobile_` as a prefix. | | mobile_udid | ` ` | example: `RQ3005TAQP` | • Unique device identifier of the connected physical device (leave empty if not applicable). • You can add any property from the List of Appium Capabilities directly to your .property files or via CLI arguments, just make sure to add `mobile_` as a prefix. | | | browserName | ` ` | `chrome`, `Chromium`, `Browser`, `Safari`, `samsung` | • You can add any property from the List of Appium Capabilities directly to your .property files or via CLI arguments, just make sure to add `mobile_` as a prefix. | | MobileBrowserVersion | ` ` | example: `83.0.4103.39` | • The WebDriver executable version compatible with the target mobile browser. Check Selenium driver requirements for browser-specific compatibility. • You can add any property from the List of Appium Capabilities directly to your .property files or via CLI arguments, just make sure to add `mobile_` as a prefix. | | | mobile_app | ` ` | `relativePath/to/myApp.apk`, `absolutePath/to/myApp.apk`, `http://myapp.com/app.ipa` | • You can add any property from the List of Appium Capabilities directly to your .property files or via CLI arguments, just make sure to add `mobile_` as a prefix. | | mobile_appPackage | ` ` | example: `com.example.android.myApp` | • You can add any property from the List of Appium Capabilities directly to your .property files or via CLI arguments, just make sure to add `mobile_` as a prefix. | | mobile_appActivity | ` ` | example: `.MainActivity` | • You can add any property from the List of Appium Capabilities directly to your .property files or via CLI arguments, just make sure to add `mobile_` as a prefix. | | mobile_bundleId | ` ` | example: `com.example.ios.myApp` | • iOS bundle identifier for launching an already installed app without providing `mobile_app`. • You can add any property from the List of Appium Capabilities directly to your .property files or via CLI arguments, just make sure to add `mobile_` as a prefix. | | mobile_flutterElementWaitTimeout | `0` | | Mobile flutter element wait timeout. | | mobile_flutterServerLaunchTimeout | `0` | | Mobile flutter server launch timeout. | | mobile_flutterSystemPort | `0` | | Mobile flutter system port. | | mobile_flutterEnableMockCamera | `false` | `true`, `false` | Mobile flutter enable mock camera. | ## API This section lists all configurable properties related to Swagger/OpenAPI contract validation and operation coverage reporting for SHAFT API testing. ```java showLineNumbers import com.shaft.driver.SHAFT; /** get **/ boolean swaggerValidationEnabled = SHAFT.Properties.api.swaggerValidationEnabled(); String swaggerValidationUrl = SHAFT.Properties.api.swaggerValidationUrl(); boolean openApiCoverageReportEnabled = SHAFT.Properties.api.openApiCoverageReportEnabled(); int openApiCoverageThreshold = SHAFT.Properties.api.openApiCoverageThreshold(); /** set **/ SHAFT.Properties.api.set() .swaggerValidationEnabled(true) .swaggerValidationUrl("https://petstore.swagger.io/v2/swagger.json") .openApiCoverageReportEnabled(true) .openApiCoverageThreshold(80); ``` ```bash showLineNumbers mvn test \ -Dswagger.validation.enabled=true \ -Dswagger.validation.url=https://petstore.swagger.io/v2/swagger.json \ -Dopenapi.coverage.report.enabled=true \ -Dopenapi.coverage.threshold=80 ``` ```properties showLineNumbers title="src/main/resources/properties/custom.properties" swagger.validation.enabled=true swagger.validation.url=https://petstore.swagger.io/v2/swagger.json openapi.coverage.report.enabled=true openapi.coverage.threshold=80 ``` | Property Name | Default Value | Description | |------------------------------|----------------|-----------------------------------------------------------------------------| | `swagger.validation.enabled` | `false` | Enables or disables Swagger/OpenAPI contract validation for API testing. | | `swagger.validation.url` | `` | URL or file path to the OpenAPI schema for validation. | | `openapi.coverage.report.enabled` | `false` | Generates an end-of-run OpenAPI operation coverage report using `swagger.validation.url` as the spec source. | | `openapi.coverage.threshold` | `0` | Minimum covered operation percentage required for a passing run; `0` disables threshold enforcement. | | `shaft.contract.sensitiveKeys` | `authorization,cookie,set-cookie,password,passwd,secret,token,api-key,apikey,access-key,accesskey` | Configured sensitive key fragments. | | `shaft.contract.volatileKeys` | `requestId,traceId,spanId,sessionId,nonce,timestamp,createdAt,updatedAt,expiresAt,date,etag` | Configured volatile key names. | ## Capture - These properties control web API request/response recording and capture settings, including network event filtering, request/response body externalization, and sensitive data redaction for API traffic capture during test execution. ```java showLineNumbers import com.shaft.driver.SHAFT; /** get **/ boolean captureApiEnabled = SHAFT.Properties.capture.isEnabled(); int captureApiMaxBodyBytes = SHAFT.Properties.capture.maxBodyBytes(); boolean captureApiIncludeAssets = SHAFT.Properties.capture.includeAssets(); boolean captureApiFirstPartyOnly = SHAFT.Properties.capture.firstPartyOnly(); boolean captureApiStoreSecretsLocally = SHAFT.Properties.capture.storeSecretsLocally(); /** set **/ SHAFT.Properties.capture.set() .enabled(true) .maxBodyBytes(1048576) .includeAssets(false) .firstPartyOnly(true) .storeSecretsLocally(false); ``` ```bash showLineNumbers mvn test \ -Dcapture.api.enabled=true \ -Dcapture.api.maxBodyBytes=1048576 \ -Dcapture.api.includeAssets=false \ -Dcapture.api.firstPartyOnly=true \ -Dcapture.api.storeSecretsLocally=false ``` ```properties showLineNumbers title="src/main/resources/properties/custom.properties" capture.api.enabled=true capture.api.maxBodyBytes=1048576 capture.api.includeAssets=false capture.api.firstPartyOnly=true capture.api.storeSecretsLocally=false ``` | Property Name | Default Value | Possible Values | Description | |----------------------------------|----------------|-----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `capture.api.enabled` | `false` | `true`, `false` | • Enable or disable automatic capture of web API network traffic during test execution. • Requires Chrome or Edge browser with DevTools Protocol support. | | `capture.api.maxBodyBytes` | `1048576` | bytes (integer) | • Maximum request/response body size to capture in bytes (1 MB). • Bodies exceeding this limit are truncated in the capture session. | | `capture.api.includeAssets` | `false` | `true`, `false` | • Include asset requests (images, CSS, fonts, etc.) in API capture. • When false, only data/API calls are recorded. | | `capture.api.firstPartyOnly` | `true` | `true`, `false` | • Capture only first-party API calls from the primary domain. • When false, all cross-origin requests are included. | | `capture.api.storeSecretsLocally` | `false` | `true`, `false` | • Store authorization headers and sensitive data in the capture session. • When false (default), secrets are referenced via bodyRefId instead. | | `capture.api.maxTransactions` | `500` | integer | • Maximum number of network transactions to retain per capture session. | | `capture.api.urlIncludeGlobs` | ` ` | comma-separated glob patterns | • When non-empty, only URLs matching one of these glob patterns are captured. | | `capture.api.urlExcludeGlobs` | ` ` | comma-separated glob patterns | • URLs matching one of these glob patterns are always excluded from capture. | ## Flags - These properties control generic platform flags, like the number of test retry attempts, auto-maximization of web browser window, and any other built-in checks or workarounds that aim to stabilize your test execution. ```java showLineNumbers import com.shaft.driver.SHAFT; /** get **/ var clickUsingJavascriptWhenWebDriverClickFails = SHAFT.Properties.flags.clickUsingJavascriptWhenWebDriverClickFails(); /** set **/ SHAFT.Properties.flags.set().clickUsingJavascriptWhenWebDriverClickFails(true); ``` Override one run with Maven, for example `mvn test '-DpropertyName=value'`. Replace `propertyName` with the key from the Default Values table below. Add the key to the relevant `.properties` file, for example `propertyName=value`. Use the target file and exact key from the Default Values table below. | Property Name | Default Value | Possible Values | Description | | ------------------------------------------- | ------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | automaticallyAddRecommendedChromeOptions | `false` | `true`, `false` | • Automatically add recommended Chrome options for better stability. | | retryMaximumNumberOfAttempts | `0` | example: `0`, `1`, `2`, `3`, `4`, ...etc | • Maximum number of retry attempts after the first failed execution. • JUnit retries run after `@AfterEach` cleanup and execute `@BeforeEach` again for isolated retry state. | | forceCaptureSupportingEvidenceOnRetry | `true` | `true`, `false` | • When retrying a failed test, enable GIF/video/log evidence for the retry attempt. | | autoMaximizeBrowserWindow | `true` | `true`, `false` | • Automatically maximize browser window on launch. | | forceCheckForElementVisibility | `true` | `true`, `false` | • Legacy configuration property with no current runtime consumer. | | forceCheckElementLocatorIsUnique | `true` | `true`, `false` | • Force check that element locator returns only one element. • This is still enforced on every retry within the identification timeout; only once that timeout is exhausted does SHAFT make one last-resort attempt to auto-resolve to a single displayed-and-enabled match before throwing `MultipleElementsFoundException` ([#4321](https://github.com/ShaftHQ/SHAFT_ENGINE/issues/4321)). | | forceCheckTextWasTypedCorrectly | `false` | `true`, `false` | • In WebDriver element actions, after `type`, `typeSecure`, or `typeAppend`, compare the final element value/text with the expected typed text. • SHAFT skips this comparison for special key sequences such as `Keys.ENTER`. | | clearBeforeTypingMode | `native` | `native`, `backspace`, `off` | • Replaces both deprecated `attemptClearBeforeTyping` and `attemptClearBeforeTypingUsingBackspace` flags. `native` = clear using native Selenium method, `backspace` = clear by deleting letter by letter, `off` = no clearing before typing. | | scrollingMode | `javascript` | `javascript`, `native` | • Mode for scrolling to elements. | | attemptToClickBeforeTyping | `false` | `true`, `false` | • Click the element before typing when `clearBeforeTypingMode` is not `native`. | | forceCheckNavigationWasSuccessful | `false` | `true`, `false` | • Force check that navigation to URL was successful. | | respectBuiltInWaitsInNativeMode | `true` | `true`, `false` | • Respect built-in waits when using native mode. | | forceCheckStatusOfRemoteServer | `false` | `true`, `false` | • Force check status of remote server before execution. | | clickUsingJavascriptWhenWebDriverClickFails | `false` | `true`, `false` | • Fallback to JavaScript click when native WebDriver click fails, including invalid-state and intercepted-click failures. | | autoCloseDriverInstance | `true` | `true`, `false` | • Automatically close driver instance after test execution. | | reuseBrowserSession | `false` | `true`, `false` | • Reuse one browser per thread within a test class. `quit()` resets tabs, storage and cookies to `about:blank`; the real quit happens after the class or at the end of execution. | | automaticallyAssertResponseStatusCode | `true` | `true`, `false` | • Assert the implicit `2xx` status for requests with no target status code. An explicit `setTargetStatusCode(n)` is always asserted. Read per request. Invalid values fail with a `Fix:` line. | | maximumPerformanceMode | `0` | `0`, `1`, `2` | • `0` → Disabled, `1` → Without Headless Execution, `2` → With Headless Execution • Enabling maximumPerformanceMode will disable all complementary features to ensure the fastest execution possible with a 400% calculated performance boost. | | skipTestsWithLinkedIssues | `false` | `true`, `false` | • It is recommended to leave this feature disabled unless you explicitly want to skip any tests that have the @Issue or @Issues annotation. | | disableCache | `false` | `true`, `false` | • To disable the cache in a browser session. | | enableTrueNativeMode | `false` | `true`, `false` | • Enable true native mode for mobile testing. | | handleNonSelectDropDown | `true` | `true`, `false` | • Handle non-select dropdown elements. | | validateSwipeToElement | `false` | `true`, `false` | • Validate swipe to element action on mobile. | | disableSslCertificateCheck | `false` | `true`, `false` | • Disable SSL certificate validation. | | telemetry.enabled | `true` | `true`, `false` | • Enable telemetry data collection for SHAFT usage analytics. | | hideKeyboardAfterTyping | `false` | `true`, `false` | • When `true`, hides the native soft keyboard after a successful mobile-native `type()`. | ## Reporting - These properties control the engine's built-in reporting capabilities, such as whether or not to capture the element name in the report to make it more readable, capturing WebDriver logs for debugging, and the behavior of reports before and after test execution. ```java showLineNumbers import com.shaft.driver.SHAFT; /** get **/ var captureElementName = SHAFT.Properties.reporting.captureElementName(); var locatorHealthEnabled = SHAFT.Properties.reporting.locatorHealthEnabled(); var locatorHealthWarnBelowScore = SHAFT.Properties.reporting.locatorHealthWarnBelowScore(); var flakeProfilerEnabled = SHAFT.Properties.reporting.flakeProfilerEnabled(); var diagnosticsBundleEnabled = SHAFT.Properties.reporting.diagnosticsBundleEnabled(); var evidenceLevel = SHAFT.Properties.reporting.evidenceLevel(); var whenToAttachSourceCode = SHAFT.Properties.reporting.whenToAttachSourceCode(); var traceMode = SHAFT.Properties.reporting.traceMode(); /** set **/ SHAFT.Properties.reporting.set() .captureElementName(false) .locatorHealthEnabled(true) .locatorHealthWarnBelowScore(70) .locatorHealthAttachDashboard(true) .locatorHealthFailBelowScore(-1) .slowLocatorThresholdMillis(750) .failOnLocatorHealthWarnings(false) .flakeProfilerEnabled(true) .flakeProfilerAttachPerTest(true) .flakeProfilerFailOnSevereFlakeRisk(false) .flakeProfilerSlowActionThresholdMs(2000) .diagnosticsBundleEnabled(true) .diagnosticsMaxArtifactMb(50) .traceEnabled(true) .traceMode("failure") .evidenceLevel("FAILURE_ONLY") .whenToAttachSourceCode("FailuresOnly"); ``` Override one run with Maven, for example `mvn test '-DpropertyName=value'`. Replace `propertyName` with the key from the Default Values table below. Add the key to the relevant `.properties` file, for example `propertyName=value`. Use the target file and exact key from the Default Values table below. | Property Name | Default Value | Possible Values | Description | | --------------------------------------------- | ------------- | --------------- | -------------------------------------------------------- | | captureElementName | `true` | `true`, `false` | • Capture element name in reports for better readability.| | captureWebDriverLogs | `false` | `true`, `false` | • Capture WebDriver logs for debugging purposes. | | alwaysLogDiscreetly | `false` | `true`, `false` | • Always log discreetly without detailed output. | | debugMode | `false` | `true`, `false` | • Enable debug mode for verbose logging. | | openLighthouseReportWhileExecution | `false` | `true`, `false` | • Open Lighthouse performance report during execution. | | cleanSummaryReportsDirectoryBeforeExecution | `true` | `true`, `false` | • Clean summary reports directory before execution. | | openExecutionSummaryReportAfterExecution | `false` | `true`, `false` | • Open execution summary report after test execution. | | disableLogging | `false` | `true`, `false` | • Disable all logging output. | | attachFullLog | `false` | `true`, `false` | • Attach a streamed, deduplicated snapshot of the full engine log to the Allure report after test execution without deleting the live log file. | | attachFailureEvidence | `true` | `true`, `false` | • Attach one redacted "Failure evidence" JSON (at most 256 KB) when an element action fails: locator, match counts per frame, nearest candidates, URL, title and screenshot reference. | | evidenceLevel | `FAILURE_ONLY` | `FAILURE_ONLY`, `BALANCED`, `FAST`, `FULL`, `CUSTOM` | • Select the evidence profile. Profile values except `CUSTOM` override granular screenshot, page-source, GIF, video, WebDriver-log, full-log, diagnostics, and trace controls. | | whenToAttachSourceCode | `Never` | `Never`, `FailuresOnly`, `ValidationPointsOnly`, `Always` | • Attach a five-line Java source snippet to eligible Allure steps. Validation mode includes assertions/verifications and failed actions. Independent of `evidenceLevel` and trace code context. See [source code attachments](/docs/reference/reporting#source-code-attachments). | | locatorHealthReportEnabled | `false` | `true`, `false` | • Generate end-of-run HTML/JSON locator health reports and attach them to Allure. | | shaft.locatorHealth.enabled | `false` | `true`, `false` | • Generate end-of-run locator health reports. | | shaft.locatorHealth.warnBelowScore | `70` | `0` to `100` | • Mark locators below this health score as risky in the dashboard. | | shaft.locatorHealth.attachDashboard | `true` | `true`, `false` | • Attach the HTML locator health dashboard to Allure. | | shaft.locatorHealth.failBelowScore | `-1` | `-1` or `0` to `100` | • Fail the run when any locator score is below this threshold; `-1` disables score-based failure. | | slowLocatorThresholdMillis | `750` | Integer milliseconds | • Mark locator lookups at or above this duration as slow warnings. | | failOnLocatorHealthWarnings | `false` | `true`, `false` | • Fail the run when locator health warnings are present. | | shaft.flakeProfiler.enabled | `false` | `true`, `false` | • Attach opt-in flake and auto-wait timing profiles to Allure. | | shaft.flakeProfiler.attachPerTest | `true` | `true`, `false` | • Attach each test's JSON/HTML profile when profiler signals exist. | | shaft.flakeProfiler.failOnSevereFlakeRisk | `false` | `true`, `false` | • Fail the run when severe flake-risk actions are found. | | shaft.flakeProfiler.slowActionThresholdMs | `2000` | Integer milliseconds | • Duration threshold used to flag slow and severe-risk actions. | | shaft.diagnostics.enabled | `true` | `true`, `false` | • Attach `shaft-diagnostics.zip` with sanitized `diagnostics.json` for failed and broken tests. | | shaft.diagnostics.maxArtifactMb | `50` | Integer megabytes | • Maximum size for a single diagnostics ZIP entry. | | shaft.trace.enabled | `true` | `true`, `false` | • Attach the SHAFT failure trace viewer archive to Allure. | | shaft.trace.mode | `auto` | `auto`, `failure`, `retry`, `always` | • Control trace generation: `auto` is retry-aware (resolves to `retry` when retryMaximumNumberOfAttempts > 0, otherwise `failure`). | | shaft.trace.retainFailedAttempts | `true` | `true`, `false` | • Retain failed-attempt trace archives under attempt-indexed names when retries are enabled. | | shaft.trace.includeCodeContext | `true` | `true`, `false` | • Include the best matching project source-code frame and snippet. | | shaft.trace.includeFullPageSnapshots | `true` | `true`, `false` | • Include web page snapshots when available. | | shaft.trace.includeDomSnapshots | `true` | `true`, `false` | • Include DOM snapshots in the SHAFT failure trace bundle when available. | | shaft.trace.includeScreenshots | `true` | `true`, `false` | • Include screenshots in the SHAFT failure trace bundle when available. | | shaft.trace.includeNativePageSource | `true` | `true`, `false` | • Include native mobile page source when available. | | shaft.trace.includeNetwork | `true` | `true`, `false` | • Reserve network evidence in the trace metadata. | | shaft.trace.includeConsole | `true` | `true`, `false` | • Reserve console evidence in the trace metadata. | | shaft.trace.maxArtifactMb | `50` | Integer megabytes | • Maximum size for a single trace bundle entry. | | shaft.trace.maxSessionMb | `512` | Integer megabytes | • Maximum total size of the trace bundle for one test session. | ## Allure - These properties control the engine's built-in Allure reporting capabilities, such as when to open the report, whether to accumulate history and reports, and how to customize the report appearance. - *Since version 8.3.20240827, the Allure properties have been renamed and extended. The old property names are listed in the table below for reference.* ```java showLineNumbers import com.shaft.driver.SHAFT; /** get **/ boolean automaticallyOpen = SHAFT.Properties.allure.automaticallyOpen(); boolean accumulateHistory = SHAFT.Properties.allure.accumulateHistory(); boolean accumulateReports = SHAFT.Properties.allure.accumulateReports(); boolean cleanResultsDirectory = SHAFT.Properties.allure.cleanResultsDirectory(); boolean generateArchive = SHAFT.Properties.allure.generateArchive(); boolean generateReport = SHAFT.Properties.allure.generateReport(); String customLogo = SHAFT.Properties.allure.customLogo(); String customTitle = SHAFT.Properties.allure.customTitle(); boolean realtimeMonitoring = SHAFT.Properties.allure.realtimeMonitoring(); boolean singleFile = SHAFT.Properties.allure.singleFile(); String reportLanguage = SHAFT.Properties.allure.reportLanguage(); boolean open = SHAFT.Properties.allure.open(); String groupBy = SHAFT.Properties.allure.groupBy(); /** set **/ SHAFT.Properties.allure.set() .automaticallyOpen(true) .accumulateHistory(true) .accumulateReports(true) .cleanResultsDirectory(true) .generateArchive(false) .generateReport(true) .customLogo("https://example.com/logo.png") .customTitle("My Test Suite Report") .realtimeMonitoring(false) .singleFile(true) .reportLanguage("en") .open(false) .groupBy("package,testClass"); ``` Override one run with Maven, for example `mvn test '-DpropertyName=value'`. Replace `propertyName` with the key from the Default Values table below. ```properties showLineNumbers title="src/main/resources/properties/custom.properties" allure.automaticallyOpen=true allure.accumulateHistory=true allure.accumulateReports=true allure.cleanResultsDirectory=true allure.generateArchive=false allure.generateReport=true allure.customLogo=https://example.com/logo.png allure.customTitle=My Test Suite Report allure.realtimeMonitoring=false allure.singleFile=true allure.reportLanguage=en allure.open=false allure.groupBy=package,testClass ``` | Property Name | Default Value | Possible Values | Description | | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | allure.automaticallyOpen | `true` | `true`, `false` | • Open the Allure report in the default browser automatically after test execution. *(previously: `openAllureReportAfterExecution`)* | | allure.accumulateHistory | `true` | `true`, `false` | • When `true`, accumulates Allure history across runs to enable trend graphs and historical comparison. When `false`, old results are deleted before the new run. *(previously: `cleanAllureResultsDirectoryBeforeExecution`)* | | allure.accumulateReports | `true` | `true`, `false` | • When `true`, keeps previous Allure HTML report files in the report directory. When `false`, only the latest run report is retained. | | allure.cleanResultsDirectory | `true` | `true`, `false` | • Clean the Allure results directory before each test run. Disable when merging results from multiple partial runs. | | allure.generateArchive | `false` | `true`, `false` | • Generate a self-contained ZIP archive of the Allure report after test execution. Useful for sharing or archiving in CI pipelines. | | allure.generateReport | `true` | `true`, `false` | • Generate the Allure HTML report, convenience script, archive, and live watch process after test execution. Set to `false` to keep raw `allure-results` only. | | allure.customLogo | `https://github.com/ShaftHQ/SHAFT_ENGINE/blob/main/shaft-engine/src/main/resources/images/shaft_report_logo.png?raw=true` | URL or local file path | • URL or local path to a custom logo image to replace the default SHAFT logo in the Allure report. | | allure.customTitle | `SHAFT-powered test report` | Any string | • Custom title displayed in the header of the generated Allure report. | | allure.realtimeMonitoring | `false` | `true`, `false` | • Enables SHAFT-managed Allure 3 native real-time monitoring via `allure watch` while tests run. Ignored for Allure 2 or when the Allure CLI cannot be resolved. | | allure.singleFile | `true` | `true`, `false` | • Controls the Allure 3 Awesome plugin `singleFile` option. When `true`, the report is generated as a self-contained HTML file. | | allure.reportLanguage | `en` | Language code, such as `en`, `fr`, or `ru` | • Controls the Allure 3 Awesome plugin `reportLanguage` option used by the generated report UI. | | allure.open | `false` | `true`, `false` | • Controls the Allure 3 Awesome plugin `open` option passed to Allure CLI report generation. | | allure.groupBy | `package,testClass` | Comma-separated Allure label names | • Controls the Allure 3 Awesome plugin `groupBy` hierarchy. Common values include `epic,feature,story` and `parentSuite,suite,subSuite`. | | allure.theme | `auto` | `auto`, `light`, `dark` | • Controls the Allure 3 Awesome plugin report theme. | | allure.forceConfiguredCliVersion | `true` | `true`, `false` | • `true` enforces the configured Allure 3 CLI version; `false` falls back to legacy PATH-first CLI resolution. | ## Timeouts - These properties control the engine's built-in synchronization capabilities, such as waiting for lazy loading, element identification timeout, API connection timeout, database query timeout, and shell session timeout. ```java showLineNumbers import com.shaft.driver.SHAFT; /** get **/ var elementIdentificationTimeout = SHAFT.Properties.timeouts.defaultElementIdentificationTimeout(); var uiStateTimeout = SHAFT.Properties.timeouts.waitForUiStateTimeout(); /** set **/ SHAFT.Properties.timeouts.set() .defaultElementIdentificationTimeout(5) .waitForUiStateTimeout(600); ``` Override one run with Maven, for example `mvn test '-DpropertyName=value'`. Replace `propertyName` with the key from the Default Values table below. Add the key to the relevant `.properties` file, for example `propertyName=value`. Use the target file and exact key from the Default Values table below. | Property Name | Default Value | Possible Values | Description | | ----------------------------------- | ------------- | --------------- | ---------------------------------------------------------------------------------- | | waitForLazyLoading | `true` | `true`, `false` | Especially useful for modern/responsive web apps using React, Vue, Angular, ...etc | | waitForLazyLoadingTimeout | `30` | (seconds) | Timeout in seconds for SHAFT's browser lazy-loading synchronization. | | lazyLoadingNetworkIdleInitialObservationMillis | `200` | (milliseconds) | Initial network observation window in milliseconds when no requests were seen. | | lazyLoadingNetworkIdleQuietWindowMillis | `500` | (milliseconds) | Required network quiet window in milliseconds after observed activity. | | lazyLoadingPollingIntervalMillis | `200` | (milliseconds) | Polling interval in milliseconds for the browser-readiness `fluentWait` loop. | | lazyLoadingDomStabilityQuietWindowMillis | `0` | (milliseconds); `0` disables DOM-stability folding | Cheap per-action DOM-mutation quiet window. Default `0` keeps element/validation waits from folding DOM stability. | | lazyLoadingDomStabilityOnNavigationQuietWindowMillis | `300` | (milliseconds) | DOM-mutation quiet window applied after navigation and by public `driver.browser().waitForLazyLoading()`. Independent of the cheap global window. | | lazyLoadingScrollSweepMaxSteps | `20` | (steps) | Maximum number of progressive-scroll steps that `driver.browser().scrollToLoadAll()` performs while sweeping a page for scroll-triggered lazy content. | | browserNavigationTimeout | `30` | (seconds) | Timeout in seconds for browser navigation. | | pageLoadTimeout | `30` | (seconds) | Timeout in seconds for page load. | | scriptExecutionTimeout | `30` | (seconds) | Timeout in seconds for script execution. | | defaultElementIdentificationTimeout | `10` | (seconds) | Default timeout in seconds for element identification. | | waitForUiStateTimeout | `600` | (seconds) | Default timeout in seconds for UI state waits such as `waitUntil()`. | | apiSocketTimeout | `30` | (seconds) | Timeout in seconds for API socket reads. Read per request, per thread; `0` means no timeout, maximum 2147483. Invalid values fail with a Fix: line. | | apiConnectionTimeout | `30` | (seconds) | Timeout in seconds to establish API connections. Read per request, per thread; `0` means no timeout, maximum 2147483. Invalid values fail with a Fix: line. | | apiConnectionManagerTimeout | `30` | (seconds) | Timeout in seconds to acquire a pooled API connection. Read per request, per thread; `0` means no timeout, maximum 2147483. Invalid values fail with a Fix: line. | | shellSessionTimeout | `30` | (seconds) | Timeout in seconds for shell session. | | sshServerAliveInterval | `60` | seconds; `<= 0` disables keep-alive packets | JSch `ServerAliveInterval` in seconds for remote SSH sessions. | | dockerCommandTimeout | `30` | (seconds) | Deprecated. Legacy docker-wrapped terminal command timeout. | | databaseLoginTimeout | `30` | (seconds) | Timeout in seconds for database login. | | databaseNetworkTimeout | `30` | (seconds) | Timeout in seconds for database network operations. | | databaseQueryTimeout | `30` | (seconds) | Timeout in seconds for database queries. | | waitForRemoteServerToBeUp | `false` | `true`, `false` | Wait for remote server to be up before execution. | | timeoutForRemoteServerToBeUp | `1` | (seconds) | Timeout in seconds for remote server to be up. | | remoteServerInstanceCreationTimeout | `5` | (seconds) | Timeout in seconds for remote server instance creation. | | remoteServerConnectionAttemptTimeout | `120` | (seconds) | Timeout in seconds for a single HTTP connect/read attempt while creating a remote WebDriver session. | ## Visuals - These properties control the engine's built-in visual validation and AI capabilities, such as the visual matching threshold for AI powered element identification, when to take screenshots, screenshot types, videos, animated GIFs, and complete page snapshots. ```java showLineNumbers import com.shaft.driver.SHAFT; /** get **/ var visualMatchingThreshold = SHAFT.Properties.visuals.visualMatchingThreshold(); /** set **/ SHAFT.Properties.visuals.set().visualMatchingThreshold(0.7); ``` Override one run with Maven, for example `mvn test '-DpropertyName=value'`. Replace `propertyName` with the key from the Default Values table below. Add the key to the relevant `.properties` file, for example `propertyName=value`. Use the target file and exact key from the Default Values table below. | Property Name | Default Value | Possible Values | Description | | ---------------------------------------------- | ---------------------- | -------------------------------------------- | ---------------------------------------------------------------------- | | visualMatchingThreshold | `0.90` | any decimal value between `0.00` and `1.00` | Visual matching threshold for AI powered element identification. | | screenshotParams_scalingFactor | `1.0` | example: `1.0`, `0.5`, `2.0` | Scaling factor for screenshots. | | screenshotParams_whenToTakeAScreenshot | `ValidationPointsOnly` | `Always`, `ValidationPointsOnly`, `FailuresOnly`, `Never` | Granular screenshot policy. Profile levels except `CUSTOM` override this after property loading. | | screenshotParams_screenshotType | `fullPage` | `fullPage`, `regular`, `element` | Type of screenshot to capture. | | screenshotParams_highlightElements | `true` | `true`, `false` | Highlight elements in screenshots. | | screenshotParams_highlightMethod | `AI` | `AI`, `NATIVE` | Method for highlighting elements in screenshots. | | screenshotParams_skippedElementsFromScreenshot | ` ` | CSS selectors separated by commas | Elements to skip/hide when taking screenshots. | | screenshotParams_watermark | `true` | `true`, `false` | Add watermark to screenshots. | | screenshotParams_watermarkOpacity | `0.2` | `0.0` to `1.0` | Opacity level for screenshot watermark. | | createAnimatedGif | `false` | `true`, `false` | Granular GIF policy. Profile levels except `CUSTOM` override this after property loading; retry attempts can enable GIFs automatically. | | animatedGif_frameDelay | `500` | milliseconds | Delay between frames in animated GIF. | | videoParams_recordVideo | `false` | `true`, `false` | Granular video policy. Profile levels except `CUSTOM` override this after property loading. | | videoParams_scope | `DriverSession` | `DriverSession`, `TestMethod` | Scope for video recording. | | whenToTakePageSourceSnapshot | `Never` | `Never`, `Always`, `FailuresOnly` | Granular page-source policy. Profile levels except `CUSTOM` override this after property loading. | | shaft.updateSnapshots | `false` | `true`, `false` | When true, regenerates visual and ARIA-snapshot baselines instead of comparing new captures against the existing ones. | ## Jira - These properties control the engine's built-in Jira and Xray integrations for test management and defect reporting. ```java showLineNumbers import com.shaft.driver.SHAFT; /** get **/ var reportBugs = SHAFT.Properties.jira.reportBugs(); /** set **/ SHAFT.Properties.jira.set().reportBugs(true); ``` Override one run with Maven, for example `mvn test '-DpropertyName=value'`. Replace `propertyName` with the key from the Default Values table below. Add the key to the relevant `.properties` file, for example `propertyName=value`. Use the target file and exact key from the Default Values table below. | Property Name | Default Value | Possible Values | Description | | -------------------------- | -------------------------------------------- | --------------- | --------------------------------------------------------------- | | jiraInteraction | `false` | `true`, `false` | Enable Jira integration for test management. | | jiraUrl | `https://` | URL | Jira instance URL. | | projectKey | ` ` | String | Jira project key. | | authorization | `:` | username:token | Jira authorization credentials (username:token or username:password). | | authType | `basic` | `basic`, `bearer` | Authorization type for Jira API. | | reportTestCasesExecution | `false` | `true`, `false` | Report test case execution results to Jira/Xray. | | reportPath | `target/surefire-reports/testng-results.xml` | File path | Path to test results file for Jira/Xray reporting. | | ExecutionName | ` ` | String | Name for test execution in Jira/Xray. | | ExecutionDescription | ` ` | String | Description for test execution in Jira/Xray. | | ReportBugs | `false` | `true`, `false` | Automatically report bugs to Jira on test failures. | | assignee | ` ` | String | Default assignee for reported bugs. | | allure.link.tms.pattern | `https:///{}` | URL pattern | Pattern for TMS (Test Management System) links in Allure. | | allure.link.custom.pattern | `{}` | URL pattern | Pattern for custom links in Allure reports. | ## Cucumber - These properties control your cucumber settings, such as the path to your feature files, the package names for your step definition classes, and any plugins you want to enable. - You can only configure these properties by editing your `src/main/resources/properties/cucumber.properties` file. *(N/A)* Override one run with Maven, for example `mvn test '-DpropertyName=value'`. Replace `propertyName` with the key from the Default Values table below. ```properties showLineNumbers title="src/main/resources/properties/cucumber.properties" ############# # https://cucumber.io/docs/cucumber/api/#list-configuration-options ############## cucumber.ansi-colors.disabled=false #true or false. default: false cucumber.execution.dry-run=false #true or false. default: false cucumber.execution.limit= #number of scenarios to execute (CLI only). cucumber.execution.order=lexical #lexical, reverse, random or random:[seed] (CLI only). default: lexical cucumber.execution.strict=true #true or false. default: true. cucumber.execution.wip=false #true or false. default: false. cucumber.features=src/test/resources #comma separated paths to feature files. example: path/to/example.feature, path/to/other.feature cucumber.filter.name= #regex. example: .*Hello.* cucumber.filter.tags= #tag expression. example: @smoke and not @slow cucumber.glue=customCucumberSteps, com.shaft.cucumber # comma separated package names. example: com.example.glue cucumber.plugin=pretty, json:allure-results/cucumber.json, html:allure-results/cucumberReport.html, com.shaft.listeners.CucumberTestRunnerListener # comma separated plugin strings. example: pretty, json:path/to/report.json cucumber.object-factory= #object factory class name. example: com.example.MyObjectFactory cucumber.snippet-type=underscore #underscore or camelcase. default: underscore cucumber.publish.quiet=true CUCUMBER_PUBLISH_ENABLED=false ``` | Property Name | Default Value | Possible Values | Description | | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------- | ----------- | | cucumber.ansi-colors.disabled | `false` | `true`, `false` | Disable ANSI color output in Cucumber's console output. | | cucumber.execution.dry-run | `false` | `true`, `false` | Parse Cucumber features without executing step definitions. | | cucumber.execution.limit | `` | positive integer; empty for unlimited | Maximum number of scenarios to execute. | | cucumber.execution.order | `lexical` | `lexical`, `reverse`, `random`, `random:[seed]` | Scenario execution order. | | cucumber.execution.strict | `true` | `true`, `false` | Treat undefined and pending steps as failures. | | cucumber.execution.wip | `false` | `true`, `false` | Enable work-in-progress mode, which fails when a scenario passes. | | cucumber.features | `src/test/resources` | | Paths to Cucumber feature files or directories. | | cucumber.filter.name | `` | regular expression | Select scenarios by name; empty runs all scenarios. | | cucumber.filter.tags | `` | tag expression | Select scenarios by tag; empty runs all scenarios. | | cucumber.glue | `customCucumberSteps, com.shaft.cucumber` | comma-separated packages | Packages containing Cucumber step definitions and hooks. | | cucumber.plugin | `pretty, json:allure-results/cucumber.json, html:allure-results/cucumberReport.html, com.shaft.listeners.CucumberTestRunnerListener` | comma-separated plugins | Cucumber reporting and listener plugins. | | cucumber.object-factory | `` | class name | Custom Cucumber object factory; empty uses the default. | | cucumber.snippet-type | `underscore` | `underscore`, `camelcase` | Style for generated missing-step snippets. | | cucumber.publish.quiet | `true` | `true`, `false` | Suppress the Cucumber publish banner. | ## Healenium No typed code facade is published for this group. Prefer file-based defaults or CLI overrides unless the specific property appears in a code example above. Override one run with Maven, for example `mvn test '-DpropertyName=value'`. Replace `propertyName` with the key from the Default Values table below. Add the key to the relevant `.properties` file, for example `propertyName=value`. Use the target file and exact key from the Default Values table below. | Property Name | Default Value | Possible Values | Description | | -------------- | ------------- | --------------- | ----------- | | recovery-tries | `1` | positive integer | Number of Healenium self-healing recovery attempts before giving up on a broken locator. | | score-cap | `0.5` | `0.0` to `1.0` | Minimum Healenium similarity score required to accept a healed locator candidate. | | heal-enabled | `false` | `true`, `false` | Enable the Healenium self-healing proxy for this run. | | serverHost | `localhost` | hostname | Hostname of the Healenium server that scores locator-healing candidates. | | serverPort | `7878` | port number | Port of the Healenium server that scores locator-healing candidates. | | imitatePort | `8000` | port number | Local port SHAFT uses so Healenium can intercept browser traffic. | ## Healing - These properties control optional SHAFT Heal locator recovery through the `shaft-heal` module. ```java showLineNumbers SHAFT.Properties.healing.set() .strategy("shaft-heal") .minimumTrustPercentage(85) .minimumConfidence(0.75) .ambiguityMargin(0.10) .historyEnabled(true) .historyPath("target/shaft-heal/history.json") .visualEnabled(false) .aiEnabled(false); ``` ```powershell mvn test "-Dhealing.strategy=shaft-heal" "-Dhealing.minimumTrustPercentage=85" ``` ```properties showLineNumbers title="src/main/resources/properties/custom.properties" healing.strategy=shaft-heal healing.minimumTrustPercentage=85 healing.minimumConfidence=0.75 healing.ambiguityMargin=0.10 healing.evidenceCategories=accessibility,label,test-id,stable-id-name,semantic,dom-fingerprint,native-state,ancestor-context,history healing.testIdAttributes=data-testid,data-test,data-qa healing.history.enabled=true healing.history.path=target/shaft-heal/history.json healing.history.maxEntries=500 healing.history.retentionDays=30 healing.visual.enabled=false healing.ai.enabled=false healing.ai.trigger=ambiguous healing.sourcePatch.enabled=false ``` | Property Name | Default Value | Possible Values | Description | | -------------------------------- | ------------- | --------------- | ----------- | | healing.strategy | `disabled` | `disabled`, `healenium`, `shaft-heal`, `composite` | Selects the locator recovery strategy. | | healing.minimumTrustPercentage | `-1` | `-1`, `0`-`100` | Preferred human-readable trust gate. `-1` keeps using `healing.minimumConfidence`. | | healing.minimumConfidence | `0.75` | `0.0`-`1.0` | Minimum deterministic confidence when no trust percentage override is set. | | healing.ambiguityMargin | `0.10` | `0.0`-`1.0` | Required lead over the next eligible candidate. | | healing.evidenceCategories | `accessibility,label,test-id,stable-id-name,semantic,dom-fingerprint,native-state,ancestor-context,history` | comma-separated categories | Deterministic evidence categories allowed for recovery. | | healing.testIdAttributes | `data-testid,data-test,data-qa` | comma-separated attributes | Attributes treated as stable test IDs. | | healing.history.enabled | `true` | `true`, `false` | Enables bounded local recovery history. | | healing.history.path | `target/shaft-heal/history.json` | path | Local recovery history file. | | healing.history.maxEntries | `500` | integer | Maximum retained history entries. | | healing.history.retentionDays | `30` | integer | Maximum history age in days. | | healing.visual.enabled | `false` | `true`, `false` | Enables optional local visual evidence. | | healing.ai.enabled | `false` | `true`, `false` | Enables optional provider reranking after deterministic eligibility. | | healing.ai.trigger | `ambiguous` | `never`, `ambiguous`, `below-threshold`, `always` | Controls when enabled AI reranking runs after deterministic scoring. | | healing.sourcePatch.enabled | `false` | `true`, `false` | Reserved consent gate for reviewed source-patch proposals. | | healing.ladder.budgetSeconds | `0` | `0` or positive seconds | Hard total budget in seconds across the deterministic re-suggestion ladder; `0` preserves one-shot resolution. | ## Natural Actions - These properties control trust-gated natural-language browser, element, and touch actions through `SHAFT.GUI.WebDriver.act(...)`. ```java showLineNumbers SHAFT.Properties.naturalActions.set() .enabled(true) .minimumTrustPercentage(85) .planner("deterministic") .aiFallbackEnabled(false) .allowedActions("browser,element,touch"); ``` ```powershell mvn test "-DnaturalActions.enabled=true" "-DnaturalActions.minimumTrustPercentage=85" ``` ```properties showLineNumbers title="src/main/resources/properties/custom.properties" naturalActions.enabled=false naturalActions.minimumTrustPercentage=85 naturalActions.planner=deterministic naturalActions.aiFallback.enabled=false naturalActions.allowedActions=browser,element,touch ``` | Property Name | Default Value | Possible Values | Description | | ------------------------------------- | ------------- | --------------- | ----------- | | naturalActions.enabled | `false` | `true`, `false` | Enables `driver.act(...)`. Disabled plans fail before execution. | | naturalActions.minimumTrustPercentage | `85` | `0`-`100` | Minimum plan trust required before execution. | | naturalActions.planner | `deterministic` | `deterministic`, `auto`, or a ServiceLoader planner ID | Selects the natural-action planner. | | naturalActions.aiFallback.enabled | `false` | `true`, `false` | Allows optional provider-assisted planners after deterministic planning is unavailable. | | naturalActions.aiFallback.threshold | `0` | `0`-`1` | Confidence threshold below which AI fallback is triggered. | | naturalActions.allowedActions | `browser,element,touch` | comma-separated `browser`, `element`, `touch` | Restricts which action categories natural actions may execute. | ## Infrastructure - These properties select the local setup profile and bind the ownership, storage, network, reuse, and lifecycle policy reviewed in a setup plan. ```java showLineNumbers import com.shaft.driver.SHAFT; import com.shaft.infrastructure.SetupMode; import com.shaft.infrastructure.SetupProfile; SHAFT.Properties.infrastructure.set() .profile(SetupProfile.REPORTING) .mode(SetupMode.EXTERNAL) .offline(false) .autoStart(false); ``` ```powershell mvn test "-Dinfrastructure.profile=REPORTING" "-Dinfrastructure.mode=EXTERNAL" ``` ```properties showLineNumbers title="src/main/resources/properties/custom.properties" infrastructure.mode=EXTERNAL infrastructure.profile=REPORTING infrastructure.cacheDirectory= infrastructure.offline=false infrastructure.autoStart=false infrastructure.preferSystemTools=true infrastructure.reuseOwnedProcesses=true infrastructure.startupTimeout=PT2M infrastructure.shutdownTimeout=PT30S ``` | Property Name | Default Value | Possible Values | Description | | --- | --- | --- | --- | | infrastructure.mode | `EXTERNAL` | `EXTERNAL`, `MANAGED`, `HYBRID` | Selects setup ownership; `EXTERNAL` is non-mutating. | | infrastructure.profile | `REPORTING` | setup profile name | Selects the profile used by `SHAFT.Infrastructure`. | | infrastructure.cacheDirectory | ` ` | absolute path | Overrides SHAFT-owned setup storage when non-empty. | | infrastructure.offline | `false` | `true`, `false` | Requires verified cached artifacts and disables network access. | | infrastructure.autoStart | `false` | `true`, `false` | Requests startup for providers that own a service. | | infrastructure.preferSystemTools | `true` | `true`, `false` | Allows supporting providers to prefer compatible host tools. | | infrastructure.reuseOwnedProcesses | `true` | `true`, `false` | Allows supporting providers to reuse compatible SHAFT-owned processes. | | infrastructure.startupTimeout | `PT2M` | positive ISO-8601 duration | Sets the startup timeout for supporting lifecycle providers. | | infrastructure.shutdownTimeout | `PT30S` | positive ISO-8601 duration | Sets the shutdown timeout for supporting lifecycle providers. | ## Ocr - These properties control the SHAFT-owned OCR runtime cache and whether a missing verified runtime may be downloaded. ```java showLineNumbers SHAFT.Properties.ocr.set() .cacheDirectory("") .downloadEnabled(true); ``` ```powershell mvn test "-Dshaft.ocr.downloadEnabled=true" ``` ```properties showLineNumbers title="src/main/resources/properties/custom.properties" shaft.ocr.cacheDirectory= shaft.ocr.downloadEnabled=true ``` | Property Name | Default Value | Possible Values | Description | | --- | --- | --- | --- | | shaft.ocr.cacheDirectory | ` ` | absolute path or empty | Optional OCR runtime cache override. Empty uses the platform default. | | shaft.ocr.downloadEnabled | `true` | `true`, `false` | Allows a missing verified OCR runtime to be downloaded. | | shaft.ocr.document.renderDpi | `300` | Integer DPI | Resolution used to rasterize document pages (for example PDF) before OCR. | | shaft.ocr.document.maximumInputBytes | `536870912` | Integer bytes | Largest document file accepted for OCR (512 MB). | | shaft.ocr.document.maximumPages | `1000` | Integer | Maximum number of pages processed per document. | | shaft.ocr.document.maximumPixelsPerPage | `40000000` | Integer pixels | Maximum rasterized pixels per page; larger pages are rejected. | | shaft.ocr.document.pageTimeoutSeconds | `120` | Integer seconds | Time limit for OCR of a single page. | | shaft.ocr.document.maximumAllureArtifactBytes | `26214400` | Integer bytes | Largest OCR artifact attached to Allure (25 MB). | | shaft.ocr.document.batchParallelism | `4` | Integer | Number of pages processed in parallel. | | shaft.ocr.document.maximumInFlightRasterBytes | `268435456` | Integer bytes | Memory cap for rasterized pages held at once (256 MB). | ## ManagedLocalAi - These properties control SHAFT-managed local AI inference: whether it is enabled, which reviewed model it uses, where artifacts are cached, and its timeouts. | Property Name | Default Value | Possible Values | Description | | --- | --- | --- | --- | | managedLocalAi.enabled | `false` | `true`, `false` | Whether SHAFT-managed local inference is enabled for the current thread. | | managedLocalAi.transparentProvisioning | `true` | `true`, `false` | Whether an approved request may provision missing reviewed artifacts. | | managedLocalAi.model | `auto` | reviewed model id or `auto` | Reviewed model identifier, or `auto` to let SHAFT choose. | | managedLocalAi.cacheDirectory | ` ` | absolute path or empty | Explicit cache directory; empty uses the SHAFT per-user cache. | | managedLocalAi.downloadTimeoutSeconds | `900` | Integer seconds | Artifact download timeout. | | managedLocalAi.lockTimeoutSeconds | `30` | Integer seconds | Cross-process cache lock timeout. | | managedLocalAi.launchTimeoutSeconds | `120` | Integer seconds | Authenticated runtime launch timeout. | ## Pilot - These properties control SHAFT's optional Pilot AI provider integrations (OpenAI, Anthropic, Gemini, GitHub Models, and Ollama), including consent gates, request/response redaction, retry and circuit-breaker limits, and per-provider endpoints/models/credential lookup. - AI execution is disabled by default. Credentials are never configured directly; each provider resolves its API key from the named environment variable only after AI is explicitly enabled and the relevant processing location (local, on-prem, or remote) has been approved via the consent properties below. ```java showLineNumbers import com.shaft.driver.SHAFT; /** get **/ boolean enabled = SHAFT.Properties.pilot.enabled(); String provider = SHAFT.Properties.pilot.provider(); boolean localConsent = SHAFT.Properties.pilot.localConsent(); boolean onPremConsent = SHAFT.Properties.pilot.onPremConsent(); boolean remoteConsent = SHAFT.Properties.pilot.remoteConsent(); String allowedEvidenceCategories = SHAFT.Properties.pilot.allowedEvidenceCategories(); boolean telemetryEnabled = SHAFT.Properties.pilot.telemetryEnabled(); int timeoutSeconds = SHAFT.Properties.pilot.timeoutSeconds(); int maxRequestBytes = SHAFT.Properties.pilot.maxRequestBytes(); int maxInputTokens = SHAFT.Properties.pilot.maxInputTokens(); int maxOutputTokens = SHAFT.Properties.pilot.maxOutputTokens(); String maxCostUsd = SHAFT.Properties.pilot.maxCostUsd(); int retryMaxAttempts = SHAFT.Properties.pilot.retryMaxAttempts(); int maxConcurrency = SHAFT.Properties.pilot.maxConcurrency(); int circuitBreakerFailureThreshold = SHAFT.Properties.pilot.circuitBreakerFailureThreshold(); int circuitBreakerCooldownSeconds = SHAFT.Properties.pilot.circuitBreakerCooldownSeconds(); String redactionSelectors = SHAFT.Properties.pilot.redactionSelectors(); String redactionAttributes = SHAFT.Properties.pilot.redactionAttributes(); String redactionPatterns = SHAFT.Properties.pilot.redactionPatterns(); // Each provider (openai, anthropic, gemini, github, ollama) exposes the same // endpoint()/model()/apiKeyEnvironmentVariable()/processingLocation() shape, e.g.: String openAiEndpoint = SHAFT.Properties.pilot.openAiEndpoint(); String openAiModel = SHAFT.Properties.pilot.openAiModel(); String openAiApiKeyEnvironmentVariable = SHAFT.Properties.pilot.openAiApiKeyEnvironmentVariable(); String openAiProcessingLocation = SHAFT.Properties.pilot.openAiProcessingLocation(); /** set **/ SHAFT.Properties.pilot.set() .enabled(false) .provider("none") .localConsent(false) .onPremConsent(false) .remoteConsent(false) .allowedEvidenceCategories("") .telemetryEnabled(false) .timeoutSeconds(300) .maxRequestBytes(1048576) .maxInputTokens(16000) .maxOutputTokens(8000) .maxCostUsd("0") .retryMaxAttempts(2) .maxConcurrency(2) .circuitBreakerFailureThreshold(3) .circuitBreakerCooldownSeconds(60) .redactionSelectors("input[type=password],[autocomplete=current-password],[autocomplete=new-password]") .redactionAttributes("authorization,cookie,set-cookie,password,passwd,secret,token,api-key,apikey,access-key") .redactionPatterns("") .openAiEndpoint("https://api.openai.com/v1/responses") .openAiModel("") .openAiApiKeyEnvironmentVariable("") .openAiProcessingLocation("remote") .anthropicEndpoint("https://api.anthropic.com/v1/messages") .anthropicModel("") .anthropicApiKeyEnvironmentVariable("") .anthropicProcessingLocation("remote") .anthropicVersion("2023-06-01") .geminiEndpoint("https://generativelanguage.googleapis.com/v1beta/models") .geminiModel("gemini-3.5-flash") .geminiApiKeyEnvironmentVariable("") .geminiProcessingLocation("remote") .githubEndpoint("https://models.github.ai/inference/chat/completions") .githubModel("") .githubApiKeyEnvironmentVariable("") .githubProcessingLocation("remote") .ollamaEndpoint("http://127.0.0.1:11434/api/chat") .ollamaModel("") .ollamaProcessingLocation("local") .ollamaApiKeyEnvironmentVariable("") .ollamaApiKeyHeader("") .ollamaApiKeyPrefix(""); ``` ```powershell mvn -e test "-Dpilot.ai.enabled=true" "-Dpilot.ai.provider=openai" "-Dpilot.ai.openai.model=gpt-5" ``` ```properties showLineNumbers title="src/main/resources/properties/custom.properties" pilot.ai.enabled=false pilot.ai.provider=none pilot.ai.consent.local=false pilot.ai.consent.onPrem=false pilot.ai.consent.remote=false pilot.ai.allowedEvidenceCategories= pilot.ai.telemetry.enabled=false pilot.ai.timeoutSeconds=300 pilot.ai.maxRequestBytes=1048576 pilot.ai.maxInputTokens=16000 pilot.ai.maxOutputTokens=8000 pilot.ai.maxCostUsd=0 pilot.ai.retryMaxAttempts=2 pilot.ai.maxConcurrency=2 pilot.ai.circuitBreaker.failureThreshold=3 pilot.ai.circuitBreaker.cooldownSeconds=60 pilot.ai.redaction.selectors=input[type=password],[autocomplete=current-password],[autocomplete=new-password] pilot.ai.redaction.attributes=authorization,cookie,set-cookie,password,passwd,secret,token,api-key,apikey,access-key pilot.ai.redaction.patterns= pilot.ai.openai.endpoint=https://api.openai.com/v1/responses pilot.ai.openai.model= pilot.ai.openai.apiKeyEnvironmentVariable= pilot.ai.openai.processingLocation=remote pilot.ai.anthropic.endpoint=https://api.anthropic.com/v1/messages pilot.ai.anthropic.model= pilot.ai.anthropic.apiKeyEnvironmentVariable= pilot.ai.anthropic.processingLocation=remote pilot.ai.anthropic.version=2023-06-01 pilot.ai.gemini.endpoint=https://generativelanguage.googleapis.com/v1beta/models pilot.ai.gemini.model=gemini-3.5-flash pilot.ai.gemini.apiKeyEnvironmentVariable= pilot.ai.gemini.processingLocation=remote pilot.ai.github.endpoint=https://models.github.ai/inference/chat/completions pilot.ai.github.model= pilot.ai.github.apiKeyEnvironmentVariable= pilot.ai.github.processingLocation=remote pilot.ai.ollama.endpoint=http://127.0.0.1:11434/api/chat pilot.ai.ollama.model= pilot.ai.ollama.processingLocation=local pilot.ai.ollama.apiKeyEnvironmentVariable= pilot.ai.ollama.apiKeyHeader= ``` | Property Name | Default Value | Possible Values | Description | | ----------------------------------------------- | -------------- | ---------------- | ------------ | | pilot.ai.enabled | `false` | `true`, `false` | Whether optional AI execution is enabled. | | pilot.ai.provider | `none` | | Selected provider identifier. | | pilot.ai.consent.local | `false` | `true`, `false` | Whether local inference is approved. | | pilot.ai.consent.onPrem | `false` | `true`, `false` | Whether explicitly classified on-prem inference is approved. | | pilot.ai.consent.remote | `false` | `true`, `false` | Whether remote inference is approved. | | pilot.ai.allowedEvidenceCategories | ` ` | | Comma-separated approved evidence categories. | | pilot.ai.telemetry.enabled | `false` | `true`, `false` | Whether optional external telemetry is enabled. | | pilot.ai.timeoutSeconds | `300` | | Maximum provider timeout in seconds. | | pilot.ai.maxRequestBytes | `1048576` | | Maximum serialized request size. | | pilot.ai.maxInputTokens | `16000` | | Maximum estimated input tokens. | | pilot.ai.maxOutputTokens | `8000` | | Maximum output tokens. | | pilot.ai.maxCostUsd | `0` | | Maximum accepted provider-reported cost in USD. | | pilot.ai.retryMaxAttempts | `2` | | Maximum provider execution attempts. | | pilot.ai.maxConcurrency | `2` | | Maximum concurrent calls per provider. | | pilot.ai.circuitBreaker.failureThreshold | `3` | | Failures required to open the circuit. | | pilot.ai.circuitBreaker.cooldownSeconds | `60` | | Circuit-open cooldown in seconds. | | pilot.ai.redaction.selectors | `input[type=password],[autocomplete=current-password],[autocomplete=new-password]` | | Comma-separated CSS selectors to redact. | | pilot.ai.redaction.attributes | `authorization,cookie,set-cookie,password,passwd,secret,token,api-key,apikey,access-key` | | Comma-separated structured or DOM attributes to redact. | | pilot.ai.redaction.patterns | ` ` | | Double-semicolon-separated custom regular expressions. | | pilot.ai.openai.endpoint | `https://api.openai.com/v1/responses` | | OpenAI Responses endpoint. | | pilot.ai.openai.model | ` ` | | Configured OpenAI model. | | pilot.ai.openai.apiKeyEnvironmentVariable | `OPENAI_API_KEY` | | Environment variable containing the OpenAI credential. | | pilot.ai.openai.processingLocation | `remote` | | Explicit OpenAI endpoint processing location. | | pilot.ai.anthropic.endpoint | `https://api.anthropic.com/v1/messages` | | Anthropic Messages endpoint. | | pilot.ai.anthropic.model | ` ` | | Configured Anthropic model. | | pilot.ai.anthropic.apiKeyEnvironmentVariable | `ANTHROPIC_API_KEY` | | Environment variable containing the Anthropic credential. | | pilot.ai.anthropic.processingLocation | `remote` | | Explicit Anthropic endpoint processing location. | | pilot.ai.anthropic.version | `2023-06-01` | | Anthropic API contract version. | | pilot.ai.gemini.endpoint | `https://generativelanguage.googleapis.com/v1beta/models` | | Gemini models endpoint. | | pilot.ai.gemini.model | `gemini-3.5-flash` | | Configured Gemini model. | | pilot.ai.gemini.apiKeyEnvironmentVariable | `GEMINI_API_KEY` | | Environment variable containing the Gemini credential. | | pilot.ai.gemini.processingLocation | `remote` | | Explicit Gemini endpoint processing location. | | pilot.ai.github.endpoint | `https://models.github.ai/inference/chat/completions` | | GitHub Models chat-completions endpoint. | | pilot.ai.github.model | ` ` | | Configured GitHub Models model. | | pilot.ai.github.apiKeyEnvironmentVariable | `GITHUB_TOKEN` | | Environment variable containing the GitHub credential. | | pilot.ai.github.processingLocation | `remote` | | Explicit GitHub Models endpoint processing location. | | pilot.ai.ollama.endpoint | `http://127.0.0.1:11434/api/chat` | | Ollama chat endpoint. | | pilot.ai.ollama.model | ` ` | | Configured Ollama model. | | pilot.ai.ollama.processingLocation | `local` | | Explicit Ollama endpoint processing location. | | pilot.ai.ollama.apiKeyEnvironmentVariable | ` ` | | Optional environment variable containing an on-prem Ollama gateway credential. | | pilot.ai.ollama.apiKeyHeader | `Authorization` | | Optional Ollama gateway credential header. | | pilot.ai.ollama.apiKeyPrefix | `Bearer ` | | Optional Ollama gateway credential prefix. | | pilot.ai.lmstudio.endpoint | `http://127.0.0.1:1234/v1/responses` | | LM Studio OpenAI-compatible Responses endpoint. | | pilot.ai.lmstudio.model | ` ` | | Configured LM Studio model. | | pilot.ai.lmstudio.apiKeyEnvironmentVariable | ` ` | | Optional environment variable containing an LM Studio gateway credential. | ## Paths No typed code facade is published for this group. Prefer file-based defaults or CLI overrides unless the specific property appears in a code example above. Override one run with Maven, for example `mvn test '-DpropertyName=value'`. Replace `propertyName` with the key from the Default Values table below. Add the key to the relevant `.properties` file, for example `propertyName=value`. Use the target file and exact key from the Default Values table below. | Property Name | Default Value | Possible Values | Description | | -------------------------------- | --------------------------------------------- | --------------- | ----------- | | propertiesFolderPath | `src/main/resources/properties/` | | Path to the custom properties files folder. | | defaultPropertiesFolderPath | `src/main/resources/properties/default` | | Path to the default properties files folder. | | dynamicObjectRepositoryPath | `src/main/resources/dynamicObjectRepository/` | | Path to the dynamic object repository folder. | | ariaSnapshotFolderPath | `src/test/resources/aria/` | | Folder that holds saved ARIA-snapshot baselines used for accessibility-tree comparisons; regenerate with `-Dshaft.updateSnapshots=true`. | | testDataFolderPath | `src/test/resources/testDataFiles/` | | Path to the test data files folder. | | downloadsFolderPath | `target/downloadedFiles` | | Path to the folder for downloaded files. | | allureResultsFolderPath | `allure-results/` | | Path to the Allure results output folder. | | extentReportsFolderPath | `extent-reports/` | | Path to the Extent reports output folder. | | executionSummaryReportFolderPath | `execution-summary/` | | Path to the execution summary report folder. | | PerformanceReportFolderPath | `performanceReport/` | | Path to the performance (Lighthouse) report output folder. | | video.folder | `allure-results/videos` | | Path to the folder where recorded videos and generated animated GIF files are saved. | | servicesFolderPath | `src/test/resources/META-INF/services/` | | Path to the META-INF services folder for custom service implementations. | | authCacheFolderPath | `target/auth-cache/` | | Folder that holds `SHAFT.Auth.setup(name, flow)` storage-state cache files (one JSON file per name). | | mobileSessionCacheFolderPath | `target/mobile-session-cache/` | | Folder that holds `TouchActions.saveSessionCapabilities`/`saveAppState` JSON snapshots used to reuse Appium session capabilities and mobile app state across test runs. | | aiAgentWorkspaceRoot | `` | | Workspace root for SHAFT agent tooling. | | applitoolsApiKey | `` | | Applitools API key for visual AI testing integration. | ## Pattern No typed code facade is published for this group. Prefer file-based defaults or CLI overrides unless the specific property appears in a code example above. Override one run with Maven, for example `mvn test '-DpropertyName=value'`. Replace `propertyName` with the key from the Default Values table below. Add the key to the relevant `.properties` file, for example `propertyName=value`. Use the target file and exact key from the Default Values table below. | Property Name | Default Value | Possible Values | Description | | ------------------------- | ------------- | --------------- | ----------- | | testDataColumnNamePrefix | `Data` | | Prefix used when naming generated data-provider parameters in test reports. | | allure.link.issue.pattern | `` | | URL pattern used to turn `@Issue`/`@Issues` annotation values into clickable links in the Allure report; use `{}` as the issue-ID placeholder. | ## Tinkey No typed code facade is published for this group. Prefer file-based defaults or CLI overrides unless the specific property appears in a code example above. Override one run with Maven, for example `mvn test '-DpropertyName=value'`. Replace `propertyName` with the key from the Default Values table below. Add the key to the relevant `.properties` file, for example `propertyName=value`. Use the target file and exact key from the Default Values table below. | Property Name | Default Value | Possible Values | Description | | ------------------------- | ------------- | --------------- | ----------- | | tinkey.keysetFilename | `` | | Path to the Google Tink keyset file used to encrypt/decrypt sensitive test data. | | tinkey.kms.serverType | `` | | Key Management Service provider backing the Tink keyset (e.g. `gcp-kms`, `aws-kms`). | | tinkey.kms.credentialPath | `` | | Path to the KMS provider credential file used to access the remote master key. | | tinkey.kms.masterKeyUri | `` | | URI of the remote KMS master key used to encrypt/decrypt the local Tink keyset. | ## Internal - These read-only properties are primarily for engine metadata and SHAFT-managed tooling bootstrap. Override them only when you intentionally maintain a local engine/tooling policy. ```java showLineNumbers import com.shaft.driver.SHAFT; String shaftEngineVersion = SHAFT.Properties.internal.shaftEngineVersion(); String allure3Version = SHAFT.Properties.internal.allure3Version(); String nodeLtsVersion = SHAFT.Properties.internal.nodeLtsVersion(); int androidEmulatorApiLevel = SHAFT.Properties.internal.androidEmulatorApiLevel(); ``` ```powershell mvn -e test "-Dallure3Version=3.14.3" "-DnodeLtsVersion=24.18.0" ``` ```properties showLineNumbers title="src/main/resources/properties/internal.properties" shaftEngineVersion=10.3.20260801 allure3Version=3.14.3 nodeLtsVersion=24.18.1 appiumServerVersion=3.6.0 appiumInspectorPluginVersion=2026.7.1 appiumUiAutomator2DriverVersion=8.2.2 appiumXcuitestDriverVersion=12.1.4 androidCommandLineToolsVersion=15859902 androidEmulatorApiLevel=36 androidEmulatorDeviceProfile=pixel_8 androidEmulatorImageTag=google_apis androidEmulatorRamMb=4096 androidEmulatorCores=2 ga4MeasurementId=G-4L9L79WZBV ga4ApiSecret= ``` | Property Name | Default Value | Possible Values | Description | | ----------------------------------- | ------------- | --------------- | ----------- | | shaftEngineVersion | `10.3.20260801` | version string | Engine version used by update checks, telemetry metadata, and internal tooling. | | watermarkImagePath | `https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/shaft-engine/src/main/resources/images/shaft_white_bg.png` | URL or file path | Default watermark image used by screenshot processing. | | allure3Version | `3.14.3` | Allure 3 npm package version | Allure 3 CLI package version used when SHAFT resolves the CLI through `npx`. | | nodeLtsVersion | `24.18.1` | Node.js version | Portable Node.js version downloaded when neither `allure` nor `npx` is available on `PATH`. | | appiumServerVersion | `3.6.0` | Appium npm package version | Appium server version used by SHAFT MCP local mobile bootstrap. | | appiumInspectorPluginVersion | `2026.7.1` | Appium Inspector plugin version | Appium Inspector plugin version used by SHAFT MCP wrapped mobile recording. | | appiumUiAutomator2DriverVersion | `8.2.2` | Appium driver version | Appium UiAutomator2 driver version used by SHAFT MCP for Android. | | appiumXcuitestDriverVersion | `12.1.4` | Appium driver version | Appium XCUITest driver version used by SHAFT MCP for iOS. | | androidCommandLineToolsVersion | `15859902` | Android command-line tools build number | Android command-line tools version used when SHAFT MCP bootstraps Android SDK tools. | | androidEmulatorApiLevel | `36` | Android API level | Default Android API level proposed for SHAFT-managed emulator creation. | | androidEmulatorDeviceProfile | `pixel_8` | Android virtual device profile | Default Android virtual device profile proposed for SHAFT-managed emulators. | | androidEmulatorImageTag | `google_apis` | Android system image tag | Default Android system image tag proposed for SHAFT-managed emulators. | | androidEmulatorRamMb | `4096` | megabytes | Default Android emulator RAM size in MB. | | androidEmulatorCores | `2` | CPU core count | Default Android emulator CPU core count. | | ga4MeasurementId | `G-4L9L79WZBV` | GA4 Measurement ID | Google Analytics 4 Measurement ID used for anonymous telemetry. | | ga4ApiSecret | `` | GA4 API secret | Supply privately through `internal.properties`; do not commit real secrets. | ## BrowserStack - These properties control SHAFT's built-in integration with BrowserStack. - Use the canonical authentication keys `browserStack.userName` and `browserStack.accessKey`; the legacy aliases `browserStack.user` and `browserStack.key` are normalized for compatibility. - For native mobile apps, `browserStack.appUrl` is resolved before remote `mobile_app` values such as `bs://...`, `http://...`, or `https://...`. - You can find all supported Web-based (Desktop or Mobile) execution properties & values in BrowserStack's [Web Capability Generator](https://www.browserstack.com/docs/automate/capabilities) - You can find all supported Native Mobile App execution properties & values in BrowserStack's [Appium Capability Generator](https://www.browserstack.com/app-automate/capabilities?tag=w3c) No typed code facade is published for this group. Prefer file-based defaults or CLI overrides unless the specific property appears in a code example above. Override one run with Maven, for example `mvn test '-DpropertyName=value'`. Replace `propertyName` with the key from the Default Values table below. Add the key to the relevant `.properties` file, for example `propertyName=value`. Use the target file and exact key from the Default Values table below. | Property Name | Default Value | Possible Values | Description | | -------------------------------- | ------------- | --------------- | ----------------------------------------------------------------------------------------------------- | | browserStack.userName | `` | | BrowserStack username for authentication. | | browserStack.accessKey | `` | | BrowserStack access key for authentication. | | browserStack.platformVersion | `` | | Mobile platform version for BrowserStack execution. | | browserStack.deviceName | `` | | Mobile device name for BrowserStack execution. | | browserStack.appUrl | `` | | Use a BrowserStack app URL for a previously uploaded app; takes precedence over remote `mobile_app`. | | browserStack.customID | `` | | Use customID to test the latest uploaded version as the above url expires regularly. | | browserStack.appName | `` | | App name for uploading to BrowserStack. | | browserStack.appRelativeFilePath | `` | | Relative file path to the app for SHAFT/BrowserStack upload workflows. | | browserStack.osVersion | `` | | In case of Desktop web testing you must also set the `targetOperatingSystem`, and `targetBrowserName`.| | browserStack.browserVersion | `` | | Browser version, optional, uses random by default. | | browserStack.local | `false` | `true`, `false` | Enable BrowserStack local testing. | | browserStack.seleniumVersion | `4.40.0` | | Selenium version to use on BrowserStack. | | browserStack.acceptInsecureCerts | `true` | `true`, `false` | Accept insecure SSL certificates. | | browserStack.debug | `false` | `true`, `false` | Enable debug mode for BrowserStack. | | browserStack.networkLogs | `false` | `true`, `false` | Enable network logs capture on BrowserStack. | | browserStack.geoLocation | `` | | Set geolocation for BrowserStack tests. | | browserStack.appiumVersion | `3.1.0` | | Appium version to use on BrowserStack for mobile testing. | | browserStack.buildName | `` | | Build name to group test runs in the BrowserStack dashboard. | | browserStack.projectName | `` | | Project name to group builds in the BrowserStack dashboard. | | browserStack.parallelsPerPlatform | `1` | | Number of parallel sessions to run per platform when using the BrowserStack SDK. | | browserStack.browserstackAutomation | `true` | `true`, `false` | Enable BrowserStack Automation (SDK mode). Set to `false` to use legacy WebDriver URL mode. | | browserStack.platformsList | `` | | Comma-separated list of platforms for cross-browser/cross-device parallel runs (SDK mode). | | browserStack.customBrowserStackYmlPath | `` | | Path to a custom `browserstack.yml` configuration file. Overrides individual BrowserStack properties. | ## LambdaTest No typed code facade is published for this group. Prefer file-based defaults or CLI overrides unless the specific property appears in a code example above. Override one run with Maven, for example `mvn test '-DpropertyName=value'`. Replace `propertyName` with the key from the Default Values table below. Add the key to the relevant `.properties` file, for example `propertyName=value`. Use the target file and exact key from the Default Values table below. | Property Name | Default Value | Possible Values | Description | | ------------------------------- | -------------- | --------------- | ------------------------------------------------------------------------ | | LambdaTest.username | `` | | LambdaTest username for authentication. | | LambdaTest.accessKey | `` | | LambdaTest access key for authentication. | | LambdaTest.platformVersion | `` | | Mobile platform version for LambdaTest execution. | | LambdaTest.deviceName | `` | | Mobile device name for LambdaTest execution. | | LambdaTest.appUrl | `` | | Use appUrl to test a previously uploaded app file. | | LambdaTest.appProfiling | `false` | `true`, `false` | Enable app profiling during mobile testing. | | LambdaTest.osVersion | `` | | OS version for desktop browser testing on LambdaTest. | | LambdaTest.visual | `false` | `true`, `false` | Enable visual logs (screenshots) on LambdaTest. | | LambdaTest.video | `false` | `true`, `false` | Enable video recording on LambdaTest. | | LambdaTest.appName | `` | | Use appName and appRelativeFilePath to upload a new app file and test it | | LambdaTest.appRelativeFilePath | `` | | Use appName and appRelativeFilePath to upload a new app file and test it | | LambdaTest.resolution | `` | | Screen resolution for desktop browser testing (e.g. `1920x1080`). | | LambdaTest.headless | `false` | `true`, `false` | Enable headless browser execution on LambdaTest. | | LambdaTest.timezone | `` | | Timezone for test execution on LambdaTest (e.g. `UTC+5:30`). | | LambdaTest.project | `shaft-engine` | | Project name to group builds in the LambdaTest dashboard. | | LambdaTest.build | `Build Name` | | Build name to group test runs in the LambdaTest dashboard. | | LambdaTest.tunnel | `false` | `true`, `false` | Enable LambdaTest tunnel for testing locally hosted applications. | | LambdaTest.tunnelName | `false` | | Name of the LambdaTest tunnel to use when `LambdaTest.tunnel` is enabled.| | LambdaTest.buildName | `` | | Build name used in the LambdaTest SDK configuration. Use this instead of `LambdaTest.build` when using the LambdaTest SDK (automate-config.yml). | | LambdaTest.selenium_version | `` | | Selenium version to use on LambdaTest. | | LambdaTest.driver_version | `` | | Browser driver version to use on LambdaTest. | | LambdaTest.w3c | `true` | `true`, `false` | Enable W3C WebDriver protocol on LambdaTest. | | LambdaTest.browserVersion | `` | | Browser version, optional, uses random by default. | | LambdaTest.geoLocation | `` | | Geolocation for test execution (e.g. `US`, `IN`). | | LambdaTest.debug | `false` | `true`, `false` | Enable debug mode (command logs) on LambdaTest. | | LambdaTest.acceptInsecureCerts | `true` | `true`, `false` | Accept insecure SSL certificates on LambdaTest. | | LambdaTest.networkLogs | `false` | `true`, `false` | Enable network logs capture on LambdaTest. | | LambdaTest.appiumVersion | `3.0.2` | | Appium version to use on LambdaTest for mobile testing. | | LambdaTest.autoGrantPermissions | `true` | `true`, `false` | Automatically grant app permissions on mobile devices. | | LambdaTest.autoAcceptAlerts | `true` | `true`, `false` | Automatically accept alerts on mobile devices. | | LambdaTest.isRealMobile | `true` | `true`, `false` | Run tests on real mobile devices (as opposed to emulators/simulators). | | LambdaTest.console | `false` | `true`, `false` | Enable console log capture on LambdaTest. | | LambdaTest.customID | `` | | Custom app ID for testing a previously uploaded app by its custom identifier. | ## Performance ```java showLineNumbers import com.shaft.driver.SHAFT; /** get **/ boolean lighthouseEnabled = SHAFT.Properties.performance.isEnabled(); int lighthousePort = SHAFT.Properties.performance.port(); boolean generateReport = SHAFT.Properties.performance.isEnablePerformanceReport(); String apiBudgets = SHAFT.Properties.performance.apiEndpointPerformanceBudgets(); boolean failApiBudgets = SHAFT.Properties.performance.failOnApiPerformanceBudgetViolation(); String browserActionBudgets = SHAFT.Properties.performance.browserActionPerformanceBudgets(); String pageLoadBudgets = SHAFT.Properties.performance.pageLoadPerformanceBudgets(); boolean failBrowserBudgets = SHAFT.Properties.performance.failOnBrowserPerformanceBudgetViolation(); /** set **/ SHAFT.Properties.performance.set() .generatePerformanceReport(true) .apiEndpointPerformanceBudgets("users=500,orders/{id}=750") .failOnApiPerformanceBudgetViolation(true) .browserActionPerformanceBudgets("playwright.element.click=750,*=1000") .pageLoadPerformanceBudgets("https://example.com/checkout=3000,*=5000") .failOnBrowserPerformanceBudgetViolation(false); ``` ```powershell mvn -e test "-DgeneratePerformanceReport=true" "-DapiEndpointPerformanceBudgets=users=500" "-DbrowserActionPerformanceBudgets=playwright.element.click=750" ``` ```properties showLineNumbers title="src/main/resources/properties/custom.properties" lightHouseExecution=false lightHouseExecution.port=8888 generatePerformanceReport=true apiEndpointPerformanceBudgets=users=500,orders/{id}=750 failOnApiPerformanceBudgetViolation=false browserActionPerformanceBudgets=playwright.element.click=750,*=1000 pageLoadPerformanceBudgets=https://example.com/checkout=3000,*=5000 failOnBrowserPerformanceBudgetViolation=false ``` | Property Name | Default Value | Possible Values | Description | | ---------------------------------------- | ------------- | --------------- | ----------- | | lightHouseExecution | `false` | `true`, `false` | Enable WebDriver Lighthouse performance testing execution. | | lightHouseExecution.port | `8888` | port number | Port to use for Lighthouse performance testing. | | generatePerformanceReport | `true` | `true`, `false` | Generate API and browser performance report files when timing data exists. | | apiEndpointPerformanceBudgets | ` ` | `endpoint=milliseconds` list | Optional p95 API endpoint budgets using normalized endpoint names, separated by commas, semicolons, or new lines. | | failOnApiPerformanceBudgetViolation | `false` | `true`, `false` | Fail the run when an API endpoint p95 budget is exceeded; otherwise log a warning. | | browserActionPerformanceBudgets | ` ` | `metric=milliseconds` list | Optional p95 Playwright browser action budgets, such as `playwright.element.click=750` or `*=1000`. | | pageLoadPerformanceBudgets | ` ` | `page=milliseconds` list | Optional p95 Playwright page-load budgets using navigated URLs or `*` as a default budget. | | failOnBrowserPerformanceBudgetViolation | `false` | `true`, `false` | Fail the run when a Playwright browser action or page-load p95 budget is exceeded; otherwise log a warning. | ## TestNG - These properties control your TestNG parallelization settings. - You can only configure these properties by editing your `src/main/resources/properties/TestNG.properties` file. *(N/A)* Override one run with Maven, for example `mvn test '-DpropertyName=value'`. Replace `propertyName` with the key from the Default Values table below. ```properties showLineNumbers title="src/main/resources/properties/TestNG.properties" setParallel=NONE setThreadCount=1 setVerbose=0 setPreserveOrder=true setGroupByInstances=true setDataProviderThreadCount=1 #Test Suite Timeout in minutes testSuiteTimeout=1440 ``` | Property Name | Default Value | Possible Values | Description | | -------------------------- | ------------- | ------------------------------------ | ----------- | | setParallel | `NONE` | `METHODS, CLASSES, TESTS, INSTANCES` | TestNG parallel execution mode (maps to the generated suite's `parallel` attribute). | | setParallelMode | `STATIC` | `STATIC, DYNAMIC` | Selects how `setThreadCount` is applied: `STATIC` uses it as-is, `DYNAMIC` multiplies it by the available processor cores. | | setThreadCount | `1.0d` | | ThreadCount is used as-is in case of STATIC mode. Total ThreadCount is automatically calculated for DYNAMIC mode; (Total ThreadCount = Number of available processor cores * setThreadCount) | | setVerbose | `0` | | TestNG verbose logging level for the generated suite (0 = silent). | | setPreserveOrder | `false` | `true`, `false` | Preserve declaration order of test methods/classes instead of TestNG's default ordering. | | setGroupByInstances | `false` | `true`, `false` | Group parallel test methods by instance instead of running them independently. | | setDataProviderThreadCount | `1` | | Number of threads TestNG uses to run `@DataProvider`-fed test invocations in parallel. | | testSuiteTimeout | `1440` | minutes | Test suite timeout. Default is 1440 minutes, or 24 hours. | ## Log4j - These properties control your Log4j2 logging settings. - You can only configure these properties by editing your `src/main/resources/properties/log4j2.properties` file. *(N/A)* Override one run with Maven, for example `mvn test '-DpropertyName=value'`. Replace `propertyName` with the key from the Default Values table below. ```properties showLineNumbers title="src/main/resources/properties/log4j2.properties" name=PropertiesConfig appender.console.type=Console appender.console.name=STDOUT appender.console.layout.type=PatternLayout appender.console.layout.disableAnsi=false appender.console.layout.pattern=%highlight{[%p]}{FATAL=red blink, ERROR=red bold, WARN=yellow bold, INFO=fg_#0060a8 bold, DEBUG=fg_#43b02a bold, TRACE=black} %style{%m }%style{| %-logger}{bright_black} %style{- %-thread}{bright_black} %style{- %d{hh:mm:ss a}}{bright_black} %n appender.console.filter.threshold.type=ThresholdFilter appender.console.filter.threshold.level=info property.logFilePath=${sys:shaft.log.file:-target/logs/log4j.log} appender.file.type=RollingFile appender.file.name=LOGFILE appender.file.fileName=${logFilePath} appender.file.filePattern=${logFilePath}.%i appender.file.layout.type=PatternLayout appender.file.layout.pattern=[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n appender.file.filter.threshold.type=ThresholdFilter appender.file.filter.threshold.level=debug appender.asyncFile.type=Async appender.asyncFile.name=ASYNC_LOGFILE appender.asyncFile.appenderRef.file.ref=LOGFILE rootLogger=info, STDOUT, ASYNC_LOGFILE logger.app.name=org.apache.http.impl.client logger.app.level=WARN ``` | Property Name | Default Value | Possible Values | Description | | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | name | `PropertiesConfig` | | Log4j2 configuration name, used internally as this configuration's identifier. | | appender.console.type | `Console` | | Log4j2 appender type for the console output target (`Console`). | | appender.console.name | `STDOUT` | | Reference name of the console appender, used by `rootLogger` and other loggers to attach to it. | | appender.console.layout.type | `PatternLayout` | | Log4j2 layout implementation used to format console log lines (`PatternLayout`). | | appender.console.layout.disableAnsi | `false` | `true`, `false` | Disables ANSI color codes in console log output. | | appender.console.layout.noConsoleNoAnsi | `false` | `true`, `false` | Automatically disables ANSI colors when output isn't attached to a real console (e.g. redirected to a file). | | appender.console.layout.charset | `UTF-8` | | Character encoding used when writing console log output. | | appender.console.layout.pattern | `%highlight{[%p]}{FATAL=red blink, ERROR=red bold, WARN=yellow bold, INFO=fg_#0060a8 bold, DEBUG=fg_#43b02a bold, TRACE=black} %style{%d{HH:mm:ss}}{bright_black} %style{\|}{bright_black} %m%n` | | [Log4j2 pattern layout reference](https://logging.apache.org/log4j/2.x/manual/layouts.html#pattern-layout) | | appender.console.filter.threshold.type | `ThresholdFilter` | | Log4j2 filter type applied to the console appender (`ThresholdFilter`). | | appender.console.filter.threshold.level | `info` | `fatal, error, warn, info, debug, trace` | Minimum log level written to the console; messages below this level are suppressed. | | appender.file.type | `File` | | Log4j2 appender type for the rolling log file (`RollingFile`). | | appender.file.name | `LOGFILE` | | Reference name of the file appender, used by `rootLogger` and other loggers to attach to it. | | appender.file.fileName | `target/logs/log4j.log` | | Path to the active log file that SHAFT writes execution logs to. | | appender.file.layout.type | `PatternLayout` | | Log4j2 layout implementation used to format file log lines (`PatternLayout`). | | appender.file.layout.pattern | `[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n` | | Log4j2 PatternLayout string controlling the format of each file log line. | | appender.file.layout.charset | `UTF-8` | | Character encoding used when writing the log file. | | appender.file.filter.threshold.type | `ThresholdFilter` | | Log4j2 filter type applied to the file appender (`ThresholdFilter`). | | appender.file.filter.threshold.level | `debug` | `fatal, error, warn, info, debug, trace` | Minimum log level written to the log file; messages below this level are suppressed. | | rootLogger | `info, ASYNC_STDOUT, ASYNC_LOGFILE, ASYNC_REPORT_PORTAL` | | Uses asynchronous appenders for console, file, and ReportPortal logging. | | logger.app.name | `org.apache.http.impl.client` | | Fully qualified logger name whose level is overridden by `logger.app.level` (defaults to a noisy third-party logger). | | logger.app.level | `WARN` | | Log level applied to the logger named by `logger.app.name`, used to quiet noisy third-party libraries. | ## Related - [Property Types](/docs/reference/properties/PropertyTypes) - [Common Examples](/docs/reference/properties/CommonExamples) - [Programmatic Config](/docs/reference/properties/Programmatic_Config) --- # Cross-Platform Strategy: Android and iOS Canonical HTML: https://shafthq.github.io/docs/reference/guides/Cross_Platform_Strategy Markdown: https://shafthq.github.io/md/reference/guides/Cross_Platform_Strategy.md One of the most common questions in mobile test automation is: **should Android and iOS tests live in the same project or in separate projects?** The answer depends on how similar the apps are. --- ## Same Project: When and Why Combine both platforms in a **single project** when: - The Android and iOS apps share the **same user flows and screens**. - The business logic and test scenarios are **identical** across platforms. - You want to **maximize code reuse** for test logic, utilities, and data. ### How It Works Keep your test logic in shared classes and isolate platform differences in the **page object locators**: ```java title="LoginPage.java" public class LoginPage { private final SHAFT.GUI.WebDriver driver; public LoginPage(SHAFT.GUI.WebDriver driver) { this.driver = driver; } // Platform-specific locators private By getUsernameInput() { return isAndroid() ? By.id("com.example:id/username") : By.name("username_field"); } private By getPasswordInput() { return isAndroid() ? By.id("com.example:id/password") : By.name("password_field"); } private By getLoginButton() { return isAndroid() ? By.id("com.example:id/login_btn") : By.xpath("//XCUIElementTypeButton[@name='Login']"); } // Shared test logic public void login(String username, String password) { driver.element() .type(getUsernameInput(), username) .and().type(getPasswordInput(), password) .and().click(getLoginButton()); } private boolean isAndroid() { return SHAFT.Properties.platform.targetPlatform() .equals(Platform.ANDROID.name()); } } ``` Your test class stays completely platform-agnostic: ```java title="LoginTest.java" @Test(description = "Verify successful login") public void testLogin() { new LoginPage(driver).login("user@example.com", "password123"); // Validations work the same way on both platforms } ``` :::tip Switch platforms by changing a single property — no code changes needed: ```properties title="src/main/resources/properties/custom.properties" targetOperatingSystem=ANDROID ``` Or override it from the command line: ```bash mvn test -DtargetOperatingSystem=IOS ``` ::: --- ## Separate Projects: When and Why Use **separate projects** when: - The Android and iOS apps have **significantly different UIs, flows, or features**. - Each platform has **unique screens or interactions** that the other does not have. - The teams maintaining Android and iOS tests are **different** and work independently. - You need **different dependency versions** or build configurations for each platform. ### Shared Utilities as a Library Even with separate projects, you can share common utilities (API clients, data generators, helper methods) by extracting them into a shared Maven/Gradle module: ``` my-company-tests/ ├── shared-utils/ ← Common utilities module │ └── src/main/java/ ├── android-tests/ ← Android test project │ └── pom.xml (depends on shared-utils) └── ios-tests/ ← iOS test project └── pom.xml (depends on shared-utils) ``` --- ## Decision Guide | Factor | Same Project | Separate Projects | |---|---|---| | Identical user flows | ✅ Best fit | Overhead | | Different UIs per platform | Complexity | ✅ Best fit | | Shared test logic | ✅ Maximum reuse | Partial (via shared library) | | Independent team ownership | Coordination needed | ✅ Best fit | | CI/CD simplicity | ✅ One pipeline, parameterized | Two pipelines | --- ## Recommended Approach For most teams, starting with a **single project** is the best default. You get maximum code reuse and a single source of truth for your test scenarios. If the apps diverge significantly over time, you can always split later. :::info SHAFT Engine's property-based platform switching makes it easy to run the same tests against both platforms from a single project. Just parameterize `targetOperatingSystem` in your CI/CD pipeline. ::: ## Related - [Solution Design](/docs/reference/guides/Solution_Design) - [Test Structure](/docs/reference/guides/Test_Structure) - [Ci Cd Integration](/docs/reference/guides/CI_CD_Integration) - [Quick Start](/docs/start/quick-start) --- # Custom Properties Generator Canonical HTML: https://shafthq.github.io/docs/reference/properties/custom-properties-generator Markdown: https://shafthq.github.io/md/reference/properties/custom-properties-generator.md :::tip This generator is also embedded as the **Config Generator** tab on the [Complete Properties Reference](/docs/reference/properties/PropertiesList?PropertyTypes=generator) page, right next to the property tables it builds from — that's the best place to reach it while you're already reading about a specific property. This standalone page stays as a direct link/bookmark to the same tool. ::: Use this generator to choose SHAFT properties, edit their values, and download the matching `.properties` files. Save generated files under `src/main/resources/properties/` in your test project. SHAFT reads every `.properties` file in that folder during startup. For example, a generated web configuration can include: ```properties title="src/main/resources/properties/custom.properties" targetBrowserName=chrome headlessExecution=true createAnimatedGif=false ``` ## Related - [Property types](/docs/reference/properties/PropertyTypes) - [Properties reference](/docs/reference/properties/PropertiesList) - [Common configuration examples](/docs/reference/properties/CommonExamples) --- # Database Actions Canonical HTML: https://shafthq.github.io/docs/reference/actions/DB/DB_Actions Markdown: https://shafthq.github.io/md/reference/actions/DB/DB_Actions.md ## Connecting to a Database SHAFT provides two constructors and matching factory methods for creating a database connection. ### Using the DatabaseType Enum (Recommended) Use `DatabaseActions.DatabaseType` for built-in databases — SHAFT builds the JDBC URL automatically: ```java title="TypedConnection.java" // MySQL SHAFT.DB db = new SHAFT.DB( DatabaseActions.DatabaseType.MY_SQL, "localhost", "3306", "mydb", "dbuser", "dbpass" ); // PostgreSQL SHAFT.DB db = new SHAFT.DB( DatabaseActions.DatabaseType.POSTGRES_SQL, "localhost", "5432", "testdb", "admin", "admin123" ); // SQL Server SHAFT.DB db = new SHAFT.DB( DatabaseActions.DatabaseType.SQL_SERVER, "localhost", "1433", "testdb", "sa", "MyP@ssw0rd" ); // Oracle (SID) SHAFT.DB db = new SHAFT.DB( DatabaseActions.DatabaseType.ORACLE, "localhost", "1521", "ORCL", "system", "oracle" ); // Oracle (Service Name) SHAFT.DB db = new SHAFT.DB( DatabaseActions.DatabaseType.ORACLE_SERVICE_NAME, "localhost", "1521", "myservice.example.com", "system", "oracle" ); // IBM DB2 SHAFT.DB db = new SHAFT.DB( DatabaseActions.DatabaseType.IBM_DB2, "localhost", "50000", "TESTDB", "db2inst1", "password" ); ``` Supported `DatabaseType` values: | Value | Database | |-------|----------| | `MY_SQL` | MySQL / MariaDB | | `POSTGRES_SQL` | PostgreSQL | | `SQL_SERVER` | Microsoft SQL Server | | `ORACLE` | Oracle (SID) | | `ORACLE_SERVICE_NAME` | Oracle (Service Name) | | `IBM_DB2` | IBM DB2 | ### Using a Custom JDBC Connection String For databases not in the enum, or for advanced connection parameters: ```java title="CustomJdbcConnection.java" // Custom JDBC URL (e.g., MySQL with extra options) SHAFT.DB db = new SHAFT.DB("jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC"); // PostgreSQL with schema SHAFT.DB db = new SHAFT.DB("jdbc:postgresql://localhost:5432/testdb?currentSchema=myschema"); ``` ### Factory Methods Both constructors have corresponding factory methods for cases where you want a fluent, static creation style: ```java title="FactoryMethods.java" // Via factory — DatabaseType SHAFT.DB db = SHAFT.DB.getInstance( DatabaseActions.DatabaseType.MY_SQL, "localhost", "3306", "mydb", "user", "pass" ); // Via factory — custom JDBC URL SHAFT.DB db = SHAFT.DB.getInstance("jdbc:postgresql://localhost:5432/testdb"); ``` --- ## Executing Queries ### SELECT — executeSelectQuery() Returns a `ResultSet` for you to iterate: ```java title="SelectQuery.java" SHAFT.DB db = new SHAFT.DB( DatabaseActions.DatabaseType.POSTGRES_SQL, "localhost", "5432", "shopdb", "admin", "admin123" ); ResultSet rs = db.executeSelectQuery("SELECT id, name, email FROM customers WHERE active = true"); while (rs.next()) { String name = rs.getString("name"); String email = rs.getString("email"); SHAFT.Report.log("Customer: " + name + " "); } ``` ### Get a Single Cell Value — getResult() Extracts the first column of the first row as a string — ideal for scalar queries: ```java title="GetSingleResult.java" SHAFT.DB db = new SHAFT.DB( DatabaseActions.DatabaseType.MY_SQL, "localhost", "3306", "shopdb", "user", "pass" ); String total = db.getResult("SELECT COUNT(*) FROM orders WHERE status = 'pending'"); SHAFT.Validations.assertThat() .number(Integer.parseInt(total)) .isGreaterThan(0) .withCustomReportMessage("There should be at least one pending order"); ``` You can also use the static overload with an existing `ResultSet`: ```java title="StaticGetResult.java" ResultSet rs = db.executeSelectQuery("SELECT MAX(price) FROM products"); String maxPrice = SHAFT.DB.getResult(rs); ``` ### Get a Column by Name — getColumn() Extracts all values from a named column across all rows: ```java title="GetColumn.java" ResultSet rs = db.executeSelectQuery("SELECT email FROM users WHERE role = 'admin'"); String emails = SHAFT.DB.getColumn(rs, "email"); SHAFT.Validations.assertThat().object(emails).contains("admin@example.com"); ``` ### INSERT / UPDATE / DELETE — executeUpdateQuery() Returns the number of affected rows: ```java title="UpdateQuery.java" SHAFT.DB db = new SHAFT.DB( DatabaseActions.DatabaseType.POSTGRES_SQL, "localhost", "5432", "shopdb", "admin", "admin123" ); // Insert int inserted = db.executeUpdateQuery( "INSERT INTO orders (customer_id, total, status) VALUES (42, 99.99, 'new')" ); SHAFT.Validations.assertThat().number(inserted).isEqualTo(1); // Update int updated = db.executeUpdateQuery( "UPDATE orders SET status = 'confirmed' WHERE customer_id = 42 AND status = 'new'" ); SHAFT.Validations.assertThat().number(updated).isGreaterThanOrEquals(1); // Delete (cleanup) db.executeUpdateQuery("DELETE FROM orders WHERE customer_id = 42"); ``` --- ## Advanced Usage ### Parameterized Queries (Prevent SQL Injection) ```java title="PreparedStatement.java" SHAFT.DB db = new SHAFT.DB( DatabaseActions.DatabaseType.MY_SQL, "localhost", "3306", "shopdb", "user", "pass" ); String email = "user@example.com"; PreparedStatement stmt = db.getConnection() .prepareStatement("SELECT id, name FROM users WHERE email = ? AND active = ?"); stmt.setString(1, email); stmt.setBoolean(2, true); ResultSet rs = stmt.executeQuery(); ``` ### Transaction Management ```java title="Transaction.java" SHAFT.DB db = new SHAFT.DB( DatabaseActions.DatabaseType.POSTGRES_SQL, "localhost", "5432", "bankdb", "admin", "admin123" ); try { db.getConnection().setAutoCommit(false); db.executeUpdateQuery("UPDATE accounts SET balance = balance - 500 WHERE id = 1"); db.executeUpdateQuery("UPDATE accounts SET balance = balance + 500 WHERE id = 2"); db.getConnection().commit(); SHAFT.Report.report("Transfer committed successfully"); } catch (Exception e) { db.getConnection().rollback(); SHAFT.Report.report("Transfer rolled back due to: " + e.getMessage()); throw e; } finally { db.getConnection().setAutoCommit(true); } ``` ### Stored Procedures ```java title="StoredProcedure.java" SHAFT.DB db = new SHAFT.DB( DatabaseActions.DatabaseType.MY_SQL, "localhost", "3306", "shopdb", "user", "pass" ); CallableStatement stmt = db.getConnection().prepareCall("{CALL get_orders_by_status(?)}"); stmt.setString(1, "pending"); ResultSet rs = stmt.executeQuery(); while (rs.next()) { SHAFT.Report.log("Order ID: " + rs.getInt("id")); } ``` ### Auto-Close with Try-With-Resources `SHAFT.DB` implements `AutoCloseable` — use it in a try-with-resources block to ensure the connection is always closed: ```java title="TryWithResources.java" try (SHAFT.DB db = new SHAFT.DB( DatabaseActions.DatabaseType.POSTGRES_SQL, "localhost", "5432", "testdb", "admin", "admin123")) { String userCount = db.getResult("SELECT COUNT(*) FROM users"); SHAFT.Validations.assertThat().object(userCount).isNotNull(); } // Connection is automatically closed here ``` --- ## Complete Test Example ```java title="src/test/java/tests/UserDatabaseTest.java" public class UserDatabaseTest { private SHAFT.DB db; @BeforeClass public void connect() { db = new SHAFT.DB( DatabaseActions.DatabaseType.POSTGRES_SQL, "localhost", "5432", "userdb", "admin", "admin123" ); } @Test public void newUserIsInsertedCorrectly() throws Exception { // Insert int rows = db.executeUpdateQuery( "INSERT INTO users (name, email, role) VALUES ('Test User', 'test@shaft.io', 'viewer')" ); SHAFT.Validations.assertThat().number(rows).isEqualTo(1); // Verify String name = db.getResult("SELECT name FROM users WHERE email = 'test@shaft.io'"); SHAFT.Validations.assertThat() .object(name) .isEqualTo("Test User") .withCustomReportMessage("Inserted user name should match"); // Clean up db.executeUpdateQuery("DELETE FROM users WHERE email = 'test@shaft.io'"); } @AfterClass public void disconnect() { db.closeConnection(); } } ``` --- ## Connection Strings Reference See [Connection Strings →](./Connection_Strings) for common JDBC URL formats for each supported database. ## More database methods | Method | What it does | | --- | --- | | `executeInsertQuery(String sql)` / `executeDeleteQuery(String sql)` | Runs an INSERT or DELETE statement and returns the number of affected rows. | | `executeDDLStatement(String sql)` | Runs a DDL statement such as CREATE, ALTER or DROP. | | `getRow(String columnName, String knownCellValue)` | Returns the tab-separated rows from the latest result set whose column matches the value. | | `DatabaseActions.getRow(ResultSet resultSet, String columnName, String knownCellValue)` | Same lookup on a result set you pass in. | | `cleanup()` | Removes thread-local state held by the instance; call it at teardown in long or parallel runs. | ## Related - [Connection Strings](/docs/reference/actions/DB/Connection_Strings) - [Oracle Jdbc Setup](/docs/reference/actions/DB/Oracle_JDBC_Setup) - [Database](/docs/testing/database) --- # Database Connection Strings Canonical HTML: https://shafthq.github.io/docs/reference/actions/DB/Connection_Strings Markdown: https://shafthq.github.io/md/reference/actions/DB/Connection_Strings.md ## JDBC Connection Strings SHAFT uses JDBC (Java Database Connectivity) to connect to various databases. Each database type requires a specific connection string format. ## Standard Connection String Patterns ### MySQL MySQL is a popular open-source relational database. #### Basic Connection ```java SHAFT.DB db = new SHAFT.DB( "jdbc:mysql://localhost:3306/database_name", "username", "password" ); ``` #### With SSL ```java SHAFT.DB db = new SHAFT.DB( "jdbc:mysql://localhost:3306/database_name?useSSL=true&requireSSL=true", "username", "password" ); ``` #### With Additional Parameters ```java SHAFT.DB db = new SHAFT.DB( "jdbc:mysql://localhost:3306/database_name?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC", "username", "password" ); ``` **Connection String Pattern:** ``` jdbc:mysql://[host]:[port]/[database]?[parameters] ``` **Default Port:** 3306 --- ### PostgreSQL PostgreSQL is a powerful open-source object-relational database. #### Basic Connection ```java SHAFT.DB db = new SHAFT.DB( "jdbc:postgresql://localhost:5432/database_name", "username", "password" ); ``` #### With SSL ```java SHAFT.DB db = new SHAFT.DB( "jdbc:postgresql://localhost:5432/database_name?ssl=true&sslmode=require", "username", "password" ); ``` #### With Schema ```java SHAFT.DB db = new SHAFT.DB( "jdbc:postgresql://localhost:5432/database_name?currentSchema=myschema", "username", "password" ); ``` **Connection String Pattern:** ``` jdbc:postgresql://[host]:[port]/[database]?[parameters] ``` **Default Port:** 5432 --- ### Oracle Oracle Database is a multi-model database management system. #### Basic Connection (SID) ```java SHAFT.DB db = new SHAFT.DB( "jdbc:oracle:thin:@localhost:1521:ORCL", "username", "password" ); ``` #### Service Name ```java SHAFT.DB db = new SHAFT.DB( "jdbc:oracle:thin:@//localhost:1521/service_name", "username", "password" ); ``` #### TNS Names ```java SHAFT.DB db = new SHAFT.DB( "jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=ORCL)))", "username", "password" ); ``` **Connection String Patterns:** - **SID:** `jdbc:oracle:thin:@[host]:[port]:[SID]` - **Service Name:** `jdbc:oracle:thin:@//[host]:[port]/[service_name]` **Default Port:** 1521 **Note:** Oracle JDBC driver requires additional setup. See [Oracle JDBC Setup](Oracle_JDBC_Setup.md) for detailed instructions. --- ### Microsoft SQL Server Microsoft SQL Server is a relational database management system. #### Basic Connection ```java SHAFT.DB db = new SHAFT.DB( "jdbc:sqlserver://localhost:1433;databaseName=database_name", "username", "password" ); ``` #### Windows Authentication ```java SHAFT.DB db = new SHAFT.DB( "jdbc:sqlserver://localhost:1433;databaseName=database_name;integratedSecurity=true", "", "" ); ``` #### With Encryption ```java SHAFT.DB db = new SHAFT.DB( "jdbc:sqlserver://localhost:1433;databaseName=database_name;encrypt=true;trustServerCertificate=false", "username", "password" ); ``` **Connection String Pattern:** ``` jdbc:sqlserver://[host]:[port];databaseName=[database];[parameters] ``` **Default Port:** 1433 --- ### MariaDB MariaDB is a community-developed fork of MySQL. #### Basic Connection ```java SHAFT.DB db = new SHAFT.DB( "jdbc:mariadb://localhost:3306/database_name", "username", "password" ); ``` **Connection String Pattern:** ``` jdbc:mariadb://[host]:[port]/[database]?[parameters] ``` **Default Port:** 3306 --- ### SQLite SQLite is a lightweight, file-based database. #### Basic Connection ```java SHAFT.DB db = new SHAFT.DB( "jdbc:sqlite:/path/to/database.db", "", "" ); ``` #### In-Memory Database ```java SHAFT.DB db = new SHAFT.DB( "jdbc:sqlite::memory:", "", "" ); ``` **Connection String Pattern:** ``` jdbc:sqlite:[path_to_database_file] ``` **Note:** SQLite doesn't require username and password. --- ### IBM DB2 IBM DB2 is a family of data management products. #### Basic Connection ```java SHAFT.DB db = new SHAFT.DB( "jdbc:db2://localhost:50000/database_name", "username", "password" ); ``` **Connection String Pattern:** ``` jdbc:db2://[host]:[port]/[database] ``` **Default Port:** 50000 --- ## Custom Connection String Patterns ### Using Properties File You can externalize connection strings using SHAFT properties: ```properties # shaft.properties db.url=jdbc:mysql://localhost:3306/mydb db.username=testuser db.password=testpass ``` ```java String dbUrl = System.getProperty("db.url"); String dbUser = System.getProperty("db.username"); String dbPass = System.getProperty("db.password"); SHAFT.DB db = new SHAFT.DB(dbUrl, dbUser, dbPass); ``` ### Environment Variables Using environment variables for sensitive data: ```java String dbUrl = System.getenv("DB_URL"); String dbUser = System.getenv("DB_USERNAME"); String dbPass = System.getenv("DB_PASSWORD"); SHAFT.DB db = new SHAFT.DB(dbUrl, dbUser, dbPass); ``` ### Connection String Builder Create a helper method for building connection strings: ```java public class DBHelper { public static String buildConnectionString( String dbType, String host, int port, String database ) { switch (dbType.toLowerCase()) { case "mysql": return String.format("jdbc:mysql://%s:%d/%s", host, port, database); case "postgresql": return String.format("jdbc:postgresql://%s:%d/%s", host, port, database); case "oracle": return String.format("jdbc:oracle:thin:@//%s:%d/%s", host, port, database); case "sqlserver": return String.format("jdbc:sqlserver://%s:%d;databaseName=%s", host, port, database); default: throw new IllegalArgumentException("Unsupported database type: " + dbType); } } } // Usage String connectionString = DBHelper.buildConnectionString("mysql", "localhost", 3306, "mydb"); SHAFT.DB db = new SHAFT.DB(connectionString, "user", "pass"); ``` ### Multiple Database Connections Managing multiple database connections in your tests: ```java // Source database SHAFT.DB sourceDb = new SHAFT.DB( "jdbc:mysql://source-host:3306/source_db", "source_user", "source_pass" ); // Target database SHAFT.DB targetDb = new SHAFT.DB( "jdbc:postgresql://target-host:5432/target_db", "target_user", "target_pass" ); // Perform operations ResultSet sourceData = sourceDb.executeSelectQuery("SELECT * FROM products"); // ... process and insert into target ... sourceDb.closeConnection(); targetDb.closeConnection(); ``` ## Connection Parameters Reference ### Common JDBC Parameters | Parameter | Description | Example | |-----------|-------------|---------| | `useSSL` | Enable SSL connection | `?useSSL=true` | | `serverTimezone` | Set server timezone | `?serverTimezone=UTC` | | `characterEncoding` | Character encoding | `?characterEncoding=UTF-8` | | `autoReconnect` | Auto reconnect on connection loss | `?autoReconnect=true` | | `maxReconnects` | Maximum reconnection attempts | `?maxReconnects=3` | | `connectTimeout` | Connection timeout in milliseconds | `?connectTimeout=30000` | ### Combining Multiple Parameters Multiple parameters are separated by `&`: ```java SHAFT.DB db = new SHAFT.DB( "jdbc:mysql://localhost:3306/mydb?useSSL=true&serverTimezone=UTC&characterEncoding=UTF-8", "username", "password" ); ``` ## Best Practices ### Security - **Never hardcode credentials** in your test code - Use environment variables or secure vaults for sensitive data - Use SSL/TLS connections when available - Implement least privilege access for test database users ### Connection Management - Always close connections after use - Use try-with-resources for automatic connection management - Consider connection pooling for performance-intensive tests - Set appropriate timeout values ### Configuration - Externalize connection strings using properties files - Use different configurations for different environments (dev, test, prod) - Document required JDBC drivers and their versions - Keep JDBC drivers up to date for security and bug fixes ## Related - [DB Actions](/docs/reference/actions/DB/DB_Actions) - [Oracle Jdbc Setup](/docs/reference/actions/DB/Oracle_JDBC_Setup) - [Database](/docs/testing/database) --- # Docker Container Terminal Canonical HTML: https://shafthq.github.io/docs/reference/actions/CLI/Docker_Terminal Markdown: https://shafthq.github.io/md/reference/actions/CLI/Docker_Terminal.md Docker-wrapped `TerminalActions` execution is deprecated for removal. Existing tests can still use the Docker-specific constructor while migrating, but new code should execute `docker` commands through `SHAFT.CLI.terminal()` or use the target environment directly. --- ## Prerequisites - Docker must be installed and running on the test execution machine. - The target container must already be running before `TerminalActions` is instantiated. --- ## Create a Docker Terminal Pass the container name (or container ID) and the user to run commands as: ```java title="DockerTerminalSetup.java" // Connect to a running container named "my-app-container" as root TerminalActions docker = new TerminalActions("my-container-name", "root"); ``` This constructor is deprecated; keep it only for existing compatibility tests. You can verify that the terminal is operating inside a Docker container: ```java title="VerifyDockerContext.java" assertTrue(docker.isDockerizedTerminal(), "Terminal should be connected to the Docker container"); ``` --- ## Execute Commands ### Single Command ```java title="DockerSingleCommand.java" // List files in the application directory String listing = docker.performTerminalCommand("ls -la /app"); System.out.println(listing); // Read a configuration file String config = docker.performTerminalCommand("cat /app/config/application.yml"); // Check a running process String processes = docker.performTerminalCommand("ps aux | grep java"); ``` ### Multiple Commands in Sequence ```java title="DockerMultipleCommands.java" String result = docker.performTerminalCommands( List.of( "cd /app", "cat config.json", "ls logs/" ) ); System.out.println(result); ``` --- ## Common Use Cases ### Validate Application State Inside a Container ```java title="ValidateContainerState.java" @Test void verifyApplicationHealthInsideContainer() { TerminalActions docker = new TerminalActions("backend-service", "root"); // Check that the application process is running String processes = docker.performTerminalCommand("ps aux | grep java"); SHAFT.Validations.assertThat() .object(processes) .contains("java") .withCustomReportMessage("Java process must be running inside the backend container"); } ``` ### Verify a Log File ```java title="VerifyContainerLogs.java" TerminalActions docker = new TerminalActions("my-app", "root"); // Tail the last 50 lines of the application log String logs = docker.performTerminalCommand("tail -n 50 /var/log/app/application.log"); SHAFT.Validations.assertThat() .object(logs) .doesNotContain("ERROR") .withCustomReportMessage("No ERROR entries should appear in the last 50 log lines"); ``` ### Read a Generated File from the Container ```java title="ReadFileFromContainer.java" TerminalActions docker = new TerminalActions("report-generator", "root"); // Trigger report generation inside the container docker.performTerminalCommand("./generate-report.sh"); // Read the generated report content String reportContent = docker.performTerminalCommand("cat /tmp/report.json"); // Attach to Allure report for evidence SHAFT.Report.attach("application/json", "Generated Report", reportContent); ``` ### Database Seed / Cleanup via Container ```java title="ContainerDatabaseOps.java" TerminalActions docker = new TerminalActions("postgres-db", "postgres"); @BeforeMethod void seedDatabase() { docker.performTerminalCommand("psql -U testuser -d testdb -f /scripts/seed.sql"); } @AfterMethod void cleanupDatabase() { docker.performTerminalCommand("psql -U testuser -d testdb -c 'TRUNCATE TABLE users CASCADE;'"); } ``` --- ## Combining Docker Terminal with SHAFT.API Use Docker terminal commands to prepare the environment, then validate the results through the application API: ```java title="ContainerAndApiTest.java" @Test void createUserViaDbAndVerifyThroughApi() { // Insert a user directly into the database container TerminalActions db = new TerminalActions("postgres-db", "postgres"); db.performTerminalCommand( "psql -U testuser -d testdb -c " + "\"INSERT INTO users (name, email) VALUES ('Test User', 'test@example.com');\"" ); // Verify through the REST API SHAFT.API api = new SHAFT.API("https://api.example.com"); api.get("/users?email=test@example.com") .setTargetStatusCode(200) ; api.assertThatResponse() .extractedJsonValue("$[0].name") .isEqualTo("Test User") .withCustomReportMessage("Newly created user should be retrievable via the API"); } ``` --- ## Best Practices - **Use specific container names** — avoid relying on container IDs which change between runs; use named containers in your `docker-compose.yml`. - **Scope your user** — run as the least-privileged user that can execute the required commands. - **Capture and validate output** — always store command output in a variable and assert on it rather than ignoring the return value. - **Attach output as evidence** — use `SHAFT.Report.attach()` to include command output in the Allure report for traceability. - **Ensure idempotency** — teardown commands in `@AfterMethod` should be safe to run even if the test failed mid-way. --- ## Related - [Terminal Actions](./Terminal_Actions.md) — Local terminal execution overview and common patterns. - [SSH Remote Terminal](./SSH_Terminal.md) — Execute commands on remote servers via SSH. - [File Actions](./File_Actions.md) — Manage local files and directories. --- # Element Actions Canonical HTML: https://shafthq.github.io/docs/reference/actions/GUI/Element_Actions Markdown: https://shafthq.github.io/md/reference/actions/GUI/Element_Actions.md To interact with web elements, use `driver.element()` followed by the desired action. All element actions require a `By` locator to identify the target element. For trust-gated natural-language element workflows such as `driver.act("click Save")`, see [Natural Language Actions](./Natural_Language_Actions). ## Typing Typing actions show the value being entered in the Allure step title. SHAFT caps long or multiline values in the title so reports stay readable, keeps the locator in step metadata, and adds the resolved element name only when the engine captures one. Secure typing remains masked. If you're coming from plain Selenium, `type()` is SHAFT's equivalent of `WebElement.sendKeys()`. ### type() Clears the text field and types the specified text. ```java title="TypeExample.java" By usernameField = By.id("username_textbox"); driver.element().type(usernameField, "john.doe"); ``` ### typeAppend() Types text without clearing the field first — appends to existing content. ```java title="TypeAppendExample.java" By textBox = By.id("username_textbox"); driver.element().type(textBox, "SHAFT"); driver.element().typeAppend(textBox, " Engine"); // field now contains "SHAFT Engine" ``` ### typeSecure() Types text in a secure manner — the value is masked in reports for sensitive data like passwords. ```java title="TypeSecureExample.java" By passwordField = By.id("password_field"); driver.element().typeSecure(passwordField, "mySecretPassword"); ``` ### typeFileLocationForUpload() Sets a file path for file upload input elements. ```java title="FileUploadExample.java" By fileInput = By.xpath("//form//input[@type='file']"); driver.element().typeFileLocationForUpload(fileInput, "src/test/resources/testDataFiles/testUpload.txt"); ``` ### clear() Clears text from a text field or text area. ```java title="ClearExample.java" By textBox = By.id("username_textbox"); driver.element().clear(textBox); ``` ## Clicking ### click() Waits for the target element to be interactable, then clicks on it. Falls back to JavaScript click if the standard Selenium click fails. Click reports keep the resolved element name in the step title and keep the normalized locator, including Smart Locator labels, in step metadata. ```java title="ClickExample.java" By signInButton = By.id("sign_in_btn"); driver.element().click(signInButton); ``` ### clickAndHold() Waits for the element to be clickable, then clicks and holds it. ```java title="ClickAndHoldExample.java" driver.element().clickAndHold(elementLocator); ``` ### clickUsingJavascript() Clicks an element using JavaScript. Useful when the standard click fails due to element positioning or overlay issues. ```java title="ClickJsExample.java" driver.element().clickUsingJavascript(elementLocator); ``` ### doubleClick() Performs a double click on the target element. ```java title="DoubleClickExample.java" By doubleClickButton = By.className("double_click_btn"); driver.element().doubleClick(doubleClickButton); ``` ## Hovering ### hover() Hovers over the target element. ```java title="HoverExample.java" By menuItem = By.tagName("span"); driver.element().hover(menuItem); ``` ### hoverAndClick() Hovers over an element to reveal a dropdown or menu, then clicks on a visible option. ```java title="HoverAndClickExample.java" By hoverItem = By.linkText("Popular Toys"); By clickable = By.xpath("//a[contains(text(),'Video Games')]"); driver.element().hoverAndClick(hoverItem, clickable); ``` For multi-level hover menus, pass a list of locators to hover through before clicking: ```java title="MultiLevelHoverExample.java" List hoverLocators = new ArrayList<>(); hoverLocators.add(By.linkText("Popular Toys")); hoverLocators.add(By.xpath("//a[contains(text(),'Video Games')]")); By clickable = By.linkText("Car"); driver.element().hoverAndClick(hoverLocators, clickable); ``` ## Scrolling ### scrollToElement() Scrolls the page to bring the target element into view. ```java title="ScrollExample.java" By footer = By.xpath("//a[@href='https://twitter.com/saucelabs']"); driver.element().scrollToElement(footer); ``` ## Screenshots ### captureScreenshot() Captures a screenshot of a specific element and attaches it to the report. ```java title="ElementScreenshotExample.java" By element = By.id("chart"); driver.element().captureScreenshot(element); ``` ## Drag and Drop ### Drag to Another Element ```java title="DragToElementExample.java" By source = By.id("draggable"); By target = By.id("destination"); driver.element().dragAndDrop(source, target); ``` ### Drag to a Position ```java title="DragToPositionExample.java" By source = By.id("draggable"); driver.element().dragAndDrop(source, 500, 500); ``` ### Drag by Offset ```java title="DragByOffsetExample.java" By source = By.id("draggable"); driver.element().dragAndDropByOffset(source, 100, 50); ``` ## Element Information ### getTagName() Returns the HTML tag name of the target element. ```java title="GetTagNameExample.java" String tagName = driver.element().getTagName(elementLocator); ``` ### getAttribute() Returns the value of a specific attribute. ```java title="GetAttributeExample.java" By searchBox = By.cssSelector(".gLFyf.gsfi"); String nameAttr = driver.element().getAttribute(searchBox, "name"); ``` ### getCSSProperty() Returns the value of a CSS property. ```java title="GetCssPropertyExample.java" String width = driver.element().getCSSProperty(elementLocator, "width"); ``` ### getText() Returns the visible text content of the target element. ```java title="GetTextExample.java" String text = driver.element().getText(textElement); ``` ### getSize() Returns the size of the target element as a string. ```java title="GetSizeExample.java" String elementSize = driver.element().getSize(elementLocator); ``` ### getElementsCount() Returns the number of elements matching a locator. ```java title="GetElementsCountExample.java" int resultCount = driver.element().getElementsCount(By.cssSelector("h3.LC20lb")); ``` ### isElementDisplayed() Returns whether the element is currently displayed. ```java title="IsDisplayedExample.java" boolean isVisible = driver.element().isElementDisplayed(elementLocator); ``` ### isElementClickable() Returns whether the element is clickable (visible and enabled). ```java title="IsClickableExample.java" boolean isClickable = driver.element().isElementClickable(elementLocator); ``` ## Dropdowns ### select() Selects an option from a dropdown list by visible text. Select reports show the selected value in the step title and keep the dropdown locator in step metadata. ```java title="SelectExample.java" By dropdown = By.id("dropdown"); driver.element().select(dropdown, "Option 1"); ``` ### getSelectedText() Returns the currently selected option text from a dropdown. ```java title="GetSelectedTextExample.java" By dropdown = By.id("dropdown"); String selected = driver.element().getSelectedText(dropdown); ``` ## IFrames ### switchToIframe() Switches driver focus to an iframe. ```java title="SwitchToIframeExample.java" By iframeLocator = By.id("ifr_id"); driver.element().switchToIframe(iframeLocator); ``` ### switchToDefaultContent() Switches driver focus back to the main page content. ```java title="SwitchToDefaultExample.java" driver.element().switchToDefaultContent(); ``` ### getCurrentFrame() Returns the name or handle of the currently active iframe context. Useful for debugging iframe navigation state. ```java title="GetCurrentFrameExample.java" String currentFrame = driver.element().getCurrentFrame(); ``` ## Clipboard Actions Performs clipboard operations on a text field. Supported actions: `"copy"`, `"paste"`, `"cut"`, `"select all"`, `"unselect"`. ```java title="ClipboardExample.java" driver.element().clipboardActions(textFieldLocator, "select all"); driver.element().clipboardActions(textFieldLocator, "copy"); ``` ## JavaScript Actions ### setValueUsingJavaScript() Sets the value of an element using JavaScript — useful when the standard `type()` method does not work. ```java title="SetValueJsExample.java" By inputField = By.id("username"); driver.element().setValueUsingJavaScript(inputField, "myUsername"); ``` ### submitFormUsingJavaScript() Submits a form programmatically using JavaScript. ```java title="SubmitFormJsExample.java" By formElement = By.id("loginForm"); driver.element().submitFormUsingJavaScript(formElement); ``` ## Native Mobile Commands ### executeNativeMobileCommand() Executes a native Appium mobile command with custom parameters. Use this for advanced mobile interactions not covered by the standard element actions, such as `mobile: scroll`, `mobile: swipe`, or platform-specific gestures. ```java title="NativeMobileCommandExample.java" // Scroll down on iOS driver.element().executeNativeMobileCommand( "mobile: scroll", Map.of("direction", "down") ); // Swipe on Android driver.element().executeNativeMobileCommand( "mobile: swipeGesture", Map.of( "left", "100", "top", "500", "width", "200", "height", "200", "direction", "up", "percent", "0.75" ) ); ``` :::note This method is intended for mobile (Appium) test execution. The available commands and parameters depend on the Appium driver and the target platform (Android/iOS). ::: ## Wait methods Use `waitUntil(...)` with a Selenium condition or lambda when an element action depends on application state: ```java title="WaitMethodExamples.java" driver.element().waitUntil(ExpectedConditions.textToBePresentInElementLocated( By.id("status"), "Success")); driver.element().waitUntil(ExpectedConditions.elementToBeSelected( By.id("checkbox"))); driver.element().waitUntil(ExpectedConditions.numberOfElementsToBeMoreThan( By.cssSelector(".result"), 0)); ``` ## Table Data Extraction Extracts table row data into a list of maps, where each map key is the column header. ```java title="TableDataExample.java" List > tableData = driver.element().getTableRowsData(tableLocator); ``` :::note This works with standard HTML tables that use ` ` with ` ` elements for column headers and ` ` with ` ` elements for row data. ::: ## Fluent Chaining All element actions support fluent chaining with `.and()`: ```java title="FluentChainingExample.java" driver.element() .type(usernameField, "admin") .and().type(passwordField, "password") .and().click(loginButton); ``` ## Clipboard, CSS, selection and upload helpers These `driver.element()` methods cover keyboard-clipboard flows, computed styles, and drag-and-drop uploads. | Method | What it does | | --- | --- | | `clipboard()` | Returns clipboard actions scoped to the driver session. | | `clipboard().copyAll(By locator)` | Selects all text in the element and copies it to the system clipboard (Ctrl+A, Ctrl+C). | | `clipboard().cutAll(By locator)` | Selects all text, cuts it to the clipboard (Ctrl+A, Ctrl+X), and leaves the field empty. | | `clipboard().deleteAll(By locator)` | Selects all text in the element and deletes it (Ctrl+A, Backspace). | | `clipboard().paste(By locator)` | Pastes the current system clipboard contents into the element (Ctrl+V). | | `get().cssValue(By locator, String propertyName)` | Returns the computed CSS value of the named property, for example `color` or `display`. | | `get().selectedText(By locator)` | Returns the combined text of all selected ` ` elements in a ` `. | | `dropFileToUpload(By locator, String filePath)` | Uploads a file by simulating a drag-and-drop onto the target drop zone. | | `switchToParentFrame()` | Switches focus from the current iframe to its parent frame. | When a retained element action fails, SHAFT wraps the provider error in `ActionExecutionException`; call `originalFailure()` on it to get the underlying provider exception. ```java driver.element().clipboard().copyAll(source); driver.element().clipboard().paste(target); String color = driver.element().get().cssValue(button, "background-color"); driver.element().dropFileToUpload(dropZone, "src/test/resources/testDataFiles/sample.pdf"); ``` ## Accessibility snapshots `ariaSnapshot(...)` captures the accessible-name tree of an element serialized as YAML. It is available on the legacy WebDriver element actions (`ariaSnapshot(By)`) and on the Playwright backend, which also accepts a `ShaftLocator` or a native Playwright `Locator`. ```java String tree = driver.element().ariaSnapshot(By.id("main-nav")); ``` ## SikuliX image actions `rightClick(String pathToTargetElementImage)` and `rightClick(byte[] targetElement)` right-click the screen region that matches the target image. ## Related - [Browser Actions](/docs/reference/actions/GUI/Browser_Actions) - [Element Identification](/docs/reference/actions/GUI/Element_Identification) - [Web](/docs/testing/web) --- # Element Identification Canonical HTML: https://shafthq.github.io/docs/reference/actions/GUI/Element_Identification Markdown: https://shafthq.github.io/md/reference/actions/GUI/Element_Identification.md ## Overview Element identification is the foundation of GUI test automation. This guide covers all the methods available in SHAFT Engine to locate and interact with web elements, including traditional locators, the SHAFT Locator Builder, relative locators, shadow DOM elements, and elements within iframes. If you are choosing a locator strategy for a new test, start with the [web locator strategy](/docs/testing/web#locator-strategy), then use this page for exact locator syntax. ## Supported Locator Types SHAFT Engine supports all standard Selenium locator strategies through the `By` class. Raw Selenium `By` syntax below is a reference for existing locators. Generated and repository code follow the [generated locator policy](/docs/reference/actions/GUI/Locators_And_Self_Healing#generated-locator-policy): unique author-written id via the SHAFT locator builder, then ARIA role, then native relative xpath only. ### 1. ID Existing locators may still use Selenium `By.id`. Generated and repository code emit a unique author-written id through the SHAFT locator builder: ```java By elementLocator = SHAFT.GUI.Locator.hasAnyTagName().hasId("username").build(); ``` ### 2. Name Locates elements by their `name` attribute, commonly used for form fields. ```java By elementLocator = By.name("email"); ``` ### 3. Class Name Locates elements by their CSS class name. ```java By elementLocator = By.className("btn-primary"); ``` ### 4. Tag Name Locates elements by their HTML tag name (e.g., `button`, `input`, `div`). ```java By elementLocator = By.tagName("button"); ``` ### 5. CSS Selector Locates elements using CSS selector syntax - powerful and flexible. ```java By elementLocator = By.cssSelector(".form-control[type='text']"); By elementLocator = By.cssSelector("#login-form > input.username"); ``` ### 6. XPath Locates elements using XPath expressions. Generated and repository code use native relative `By.xpath(...)` only when the element has neither a unique author-written id nor a usable ARIA role. Do not target an id that should have taken the first rung. ```java By elementLocator = By.xpath(".//form//button[@type='submit']"); ``` ### 7. Link Text Locates anchor (` `) elements by their exact text content. ```java By elementLocator = By.linkText("Click Here"); ``` ### 8. Partial Link Text Locates anchor elements by partial text match. ```java By elementLocator = By.partialLinkText("Click"); ``` ## Traditional Locators vs. SHAFT Locator Builder SHAFT Engine provides a fluent API for building locators that's more readable and maintainable than traditional approaches. ### Example 1: Simple Button **HTML:** ```html Submit ``` **Native Selenium Approach:** ```java // Using ID By button = By.id("submit-btn"); // Using CSS Selector By button = By.cssSelector("button[data-test='submit']"); // Using XPath By button = By.xpath("//button[@data-test='submit']"); ``` **SHAFT Locator Builder Approach:** ```java By button = SHAFT.GUI.Locator.hasAnyTagName().hasId("submit-btn").build(); By button = SHAFT.GUI.Locator.hasRole(Role.BUTTON).hasNormalizedText("Submit").build(); ``` ### Example 2: Complex Element **HTML:** ```html $99.99 ``` **Native Selenium Approach:** ```java By priceElement = By.cssSelector(".product-card .price[data-currency='USD']"); By priceElement = By.xpath("//div[contains(@class,'product-card')]//span[@data-currency='USD']"); ``` **SHAFT Locator Builder Approach:** ```java By priceElement = SHAFT.GUI.Locator.hasTagName("span") .containsClass("price") .hasAttribute("data-currency", "USD") .build(); ``` ### Example 3: Element with Text **HTML:** ```html Add to Cart ``` **Native Selenium Approach:** ```java By button = By.xpath("//button[contains(text(),'Add to Cart')]"); By button = By.cssSelector("button.action-button"); // Can't filter by text with CSS ``` **SHAFT Locator Builder Approach:** ```java By button = SHAFT.GUI.Locator.hasRole(Role.BUTTON) .hasNormalizedText("Add to Cart") .build(); ``` ## SHAFT Locator Builder Methods The SHAFT Locator Builder provides a fluent API with the following methods: ### Tag-Based Methods ```java // Exact tag name SHAFT.GUI.Locator.hasTagName("button").build(); // Any tag name (when you want to filter by other attributes) SHAFT.GUI.Locator.hasAnyTagName().hasAttribute("data-test", "submit").build(); ``` ### Attribute-Based Methods ```java // Element with specific attribute SHAFT.GUI.Locator.hasAttribute("data-test").build(); // Element with attribute and value SHAFT.GUI.Locator.hasAttribute("data-test", "add-to-cart").build(); // Unique author-written id (generated and repository form) SHAFT.GUI.Locator.hasAnyTagName().hasId("username").build(); // Element containing a class SHAFT.GUI.Locator.containsClass("btn-primary").build(); ``` ### Text-Based Methods ```java // Element containing specific text SHAFT.GUI.Locator.containsText("Submit").build(); // Element containing ID substring SHAFT.GUI.Locator.containsId("user").build(); ``` ### Chaining Methods ```java // Combine multiple conditions By complexLocator = SHAFT.GUI.Locator.hasTagName("input") .hasAttribute("type", "text") .containsClass("form-control") .containsId("user") .build(); ``` ## Relative (Location-Based) Locators Selenium 4 introduced relative locators that allow you to locate elements based on their position relative to other elements. SHAFT Engine fully supports these. ### Available Relative Locator Methods - `above()` - Locates elements above the reference element - `below()` - Locates elements below the reference element - `toLeftOf()` - Locates elements to the left of the reference element - `toRightOf()` - Locates elements to the right of the reference element - `near()` - Locates elements near (within approximately 50 pixels) of the reference element ### Example: Login Form **HTML:** ```html Username: Password: Login ``` **Using Relative Locators:** ```java // Locate password field relative to username field By usernameField = By.id("username"); By passwordField = with(By.tagName("input")).below(usernameField); // Locate the login button below password field By loginButton = with(By.tagName("button")).below(passwordField); // Locate label to the left of username field By usernameLabel = with(By.tagName("label")).toLeftOf(usernameField); ``` ### Example: Product Grid **HTML:** ```html Product 1 Product 2 Product 3 ``` **Using Relative Locators:** ```java // Locate product-2 relative to product-1 By product1 = By.id("product-1"); By product2 = with(By.className("product")).toRightOf(product1); // Locate product near product-1 (within ~50px) By nearbyProduct = with(By.className("product")).near(product1); ``` ### Combining Relative Locators You can chain multiple relative locator conditions: ```java By referenceElement = By.id("reference"); By targetElement = with(By.tagName("input")) .below(referenceElement) .toRightOf(By.id("another-reference")); ``` ### Using Relative Locators with SHAFT ```java driver = new SHAFT.GUI.WebDriver(); driver.browser().navigateToURL("https://example.com"); // Define reference element By referenceElement = By.id("username"); // Use relative locator By relativeElement = with(By.tagName("input")).below(referenceElement); // Interact with the element driver.element().type(relativeElement, "test@example.com"); ``` ## Interacting with Shadow DOM Shadow DOM encapsulates parts of a web component's DOM tree. SHAFT Locator Builder provides special support for locating elements within shadow DOM. ### What is Shadow DOM? Shadow DOM allows developers to attach a hidden, separate DOM tree to an element. Elements within the shadow DOM are not accessible through normal DOM traversal methods. ### Locating Shadow DOM Elements **Example:** ```html #shadow-root Men's Outerwear ``` **Native Selenium Approach:** ```java // Requires JavaScript execution WebElement shadowHost = driver.findElement(By.tagName("shop-app")); WebElement shadowRoot = (WebElement) ((JavascriptExecutor) driver) .executeScript("return arguments[0].shadowRoot", shadowHost); WebElement link = shadowRoot.findElement(By.cssSelector("a[href='/list/mens_outerwear']")); ``` **SHAFT Locator Builder Approach:** ```java driver = new SHAFT.GUI.WebDriver(); driver.browser().navigateToURL("https://shop.polymer-project.org/"); // Define the shadow host By shadowHost = SHAFT.GUI.Locator.hasTagName("shop-app").build(); // Locate element inside shadow DOM By shadowElement = SHAFT.GUI.Locator.hasTagName("a") .hasAttribute("href", "/list/mens_outerwear") .insideShadowDom(shadowHost) .build(); // Interact with the element driver.element().click(shadowElement); ``` ### Nested Shadow DOM For deeply nested shadow DOM structures: ```java // First shadow host By shadowHost1 = SHAFT.GUI.Locator.hasTagName("outer-component").build(); // Second shadow host inside the first shadow DOM By shadowHost2 = SHAFT.GUI.Locator.hasTagName("inner-component") .insideShadowDom(shadowHost1) .build(); // Target element inside the nested shadow DOM By targetElement = SHAFT.GUI.Locator.hasTagName("button") .hasAttribute("id", "submit") .insideShadowDom(shadowHost2) .build(); driver.element().click(targetElement); ``` ### Complete Shadow DOM Example ```java public class ShadowDomTest { SHAFT.GUI.WebDriver driver; @Test public void testShadowDomElements() { driver = new SHAFT.GUI.WebDriver(); driver.browser().navigateToURL("https://shop.polymer-project.org/"); // Locate shadow host By shadowHost = SHAFT.GUI.Locator.hasTagName("shop-app").build(); // Locate element inside shadow DOM By menuLink = SHAFT.GUI.Locator.hasTagName("a") .containsText("Men's Outerwear") .insideShadowDom(shadowHost) .build(); // Click the element driver.element().click(menuLink); // Verify navigation driver.browser().assertThat().url().contains("/list/mens_outerwear"); driver.quit(); } } ``` For more examples, visit [ShadowDomTest](https://github.com/ShaftHQ/SHAFT_ENGINE/blob/main/shaft-engine/src/test/java/testPackage/locator/ShadowDomTest.java) on GitHub. ## Interacting with IFrames IFrames (Inline Frames) are HTML elements that embed another HTML page within the current page. To interact with elements inside an iframe, you must first switch the driver's focus to that iframe. ### Basic IFrame Handling **Switching to an IFrame:** ```java By iframeLocator = By.id("iframe-id"); driver.element().switchToIframe(iframeLocator); ``` **Switching Back to Main Content:** ```java driver.element().switchToDefaultContent(); ``` ### Example: Text Editor in IFrame **HTML:** ```html Editable content ``` **Native Selenium Approach:** ```java WebDriver driver = new ChromeDriver(); driver.get("https://the-internet.herokuapp.com/tinymce"); // Switch to iframe WebElement iframe = driver.findElement(By.id("mce_0_ifr")); driver.switchTo().frame(iframe); // Interact with element inside iframe WebElement textField = driver.findElement(By.id("tinymce")); textField.clear(); textField.sendKeys("New text"); // Switch back to main content driver.switchTo().defaultContent(); ``` **SHAFT Approach:** ```java driver = new SHAFT.GUI.WebDriver(); driver.browser().navigateToURL("https://the-internet.herokuapp.com/tinymce"); // Locate the iframe By textIframe = By.id("mce_0_ifr"); By textField = By.id("tinymce"); // Switch focus to iframe driver.element().switchToIframe(textIframe); // Interact with elements inside the iframe driver.element().typeAppend(textField, "This text is added"); driver.element().clipboardActions(textField, "select all"); driver.element().clipboardActions(textField, "copy"); // Switch back to default content driver.element().switchToDefaultContent(); ``` ### Nested IFrames For nested iframes, switch to each iframe sequentially: ```java // Switch to outer iframe By outerIframe = By.id("outer-iframe"); driver.element().switchToIframe(outerIframe); // Switch to inner iframe By innerIframe = By.id("inner-iframe"); driver.element().switchToIframe(innerIframe); // Interact with element in nested iframe By targetElement = By.id("target"); driver.element().click(targetElement); // Switch back to default content (exits all iframes) driver.element().switchToDefaultContent(); ``` ### IFrame Switching by Index You can also switch to an iframe by its index (zero-based): ```java // Switch to the first iframe on the page driver.element().switchToIframe(By.xpath("(//iframe)[1]")); ``` ### Complete IFrame Example ```java public class IFrameTest { SHAFT.GUI.WebDriver driver; @Test public void testIFrameInteraction() { driver = new SHAFT.GUI.WebDriver(); driver.browser().navigateToURL("https://the-internet.herokuapp.com/tinymce"); // Define locators By textIframe = By.id("mce_0_ifr"); By textField = By.id("tinymce"); // Switch to iframe driver.element().switchToIframe(textIframe); // Get current text String originalText = driver.element().getText(textField); System.out.println("Original text: " + originalText); // Modify text driver.element().type(textField, "New content added by SHAFT"); // Verify the change String newText = driver.element().getText(textField); System.out.println("New text: " + newText); // Switch back to main content driver.element().switchToDefaultContent(); driver.quit(); } } ``` ### IFrame Best Practices 1. **Always switch back to default content** when done interacting with iframe elements 2. **Use explicit locators** for iframes rather than relying on index-based switching when possible 3. **Handle iframe loading** by ensuring the iframe is present before switching 4. **Be aware of nested iframes** - you need to switch through each level 5. **Consider using unique IDs** for iframes when you control the HTML ## Combining Techniques You can combine different element identification techniques for complex scenarios: ### Example: Element in IFrame with SHAFT Locator Builder ```java By iframeLocator = SHAFT.GUI.Locator.hasTagName("iframe") .hasAttribute("id", "content-frame") .build(); driver.element().switchToIframe(iframeLocator); By buttonInIframe = SHAFT.GUI.Locator.hasTagName("button") .containsText("Submit") .hasAttribute("data-test", "submit-btn") .build(); driver.element().click(buttonInIframe); driver.element().switchToDefaultContent(); ``` ### Example: Relative Locator for Element in Shadow DOM ```java By shadowHost = SHAFT.GUI.Locator.hasTagName("custom-component").build(); By referenceElement = SHAFT.GUI.Locator.hasAnyTagName().hasId("reference") .insideShadowDom(shadowHost) .build(); // Use relative locator for element below the reference By targetElement = with(By.tagName("input")).below(referenceElement); driver.element().type(targetElement, "value"); ``` ## XPath Axis Navigation The SHAFT Locator Builder exposes a fluent XPath axis API that lets you navigate DOM relationships — parent, sibling, child, ancestor, and more — without writing raw XPath strings. ### Available Axes | Axis | Description | |:-----|:------------| | `parent()` | The direct parent element | | `ancestor(tag)` | The nearest ancestor with the given tag | | `child(tag)` | A direct child element with the given tag | | `followingSibling(tag)` | The next sibling element with the given tag | | `precedingSibling(tag)` | The previous sibling element with the given tag | | `following(tag)` | Any element that follows in document order | | `preceding(tag)` | Any element that precedes in document order | | `descendant(tag)` | Any descendant element with the given tag | ### Example: Locate Input via Associated Label ```java title="XPathAxisLabelToInput.java" // Find the input field that follows the "Email" label By emailInput = SHAFT.GUI.Locator.hasTagName("label") .hasText("Email") .byAxis().followingSibling("input") .build(); driver.element().type(emailInput, "user@example.com"); ``` ### Example: Navigate to an Ancestor ```java title="XPathAxisAncestor.java" // Find the wrapping that contains an error By errorWrapper = SHAFT.GUI.Locator.hasTagName("span") .containsText("Required field") .byAxis().ancestor("div") .build(); driver.assertThat().element(errorWrapper).attribute("class").contains("error"); ``` ### Example: Navigate to a Child Element ```java title="XPathAxisChild.java" // First inside the navigation By firstNavItem = SHAFT.GUI.Locator.hasTagName("ul") .containsClass("nav-menu") .byAxis().child("li") .build(); driver.element().click(firstNavItem); ``` ### Example: Preceding Sibling ```java title="XPathAxisPreceding.java" // The heading that appears before a specific section By sectionHeading = SHAFT.GUI.Locator.hasTagName("section") .hasId("pricing") .byAxis().precedingSibling("h2") .build(); String title = driver.element().getText(sectionHeading); ``` :::tip Axis navigation generates optimised XPath expressions internally. You still benefit from SHAFT's smart waits and retry logic when interacting with axis-resolved elements. ::: --- ## ARIA Role-Based Locators ARIA roles give you resilient, accessibility-driven locators that survive DOM refactors. Use `Locator.hasRole()` with the `Role` enum to target elements by their semantic purpose rather than fragile CSS classes or XPath. ### Available Roles ```java // Some commonly used ARIA roles Role.BUTTON // , role="button" Role.LINK // , role="link" Role.TEXTBOX // , role="textbox" Role.SEARCHBOX // , role="searchbox" Role.CHECKBOX // , role="checkbox" Role.RADIO // , role="radio" Role.COMBOBOX // , role="combobox" Role.NAVIGATION // , role="navigation" Role.MAIN // , role="main" Role.DIALOG // role="dialog" Role.ALERT // role="alert" Role.HEADING // – , role="heading" Role.LIST // / , role="list" Role.LISTITEM // , role="listitem" Role.TABLE // , role="table" ``` ### Example: Locate by Role ```java title="ARIARoleLocator.java" // Locate a submit button by role and visible text By submitButton = SHAFT.GUI.Locator.hasRole(Role.BUTTON).hasNormalizedText("Submit").build(); // Locate a search input by role By searchInput = SHAFT.GUI.Locator.hasRole(Role.SEARCHBOX).build(); // Locate the navigation landmark By mainNav = SHAFT.GUI.Locator.hasRole(Role.NAVIGATION).build(); driver.element().click(submitButton); driver.element().type(searchInput, "SHAFT Engine"); ``` ### Example: Role + Additional Conditions ```java title="ARIARoleWithConditions.java" // Dialog with a specific title By confirmDialog = SHAFT.GUI.Locator.hasRole(Role.DIALOG) .containsText("Confirm deletion") .build(); // Alert message containing error text By errorAlert = SHAFT.GUI.Locator.hasRole(Role.ALERT) .containsText("Invalid credentials") .build(); driver.assertThat().element(confirmDialog).exists(); driver.assertThat().element(errorAlert).attribute("class").contains("error"); ``` :::note Role-based locators improve accessibility coverage in your tests and make your test suite act as a living accessibility audit. ::: --- ## Smart Locators `inputField()` and `clickableField()` resolve an element from a visible label, placeholder, button text, or ARIA name. They exist for a human's throwaway exploration only. They are not the official form strategy, and generated or repository code must not use them. Follow the [generated locator policy](/docs/reference/actions/GUI/Locators_And_Self_Healing#generated-locator-policy) instead: unique author-written id via the SHAFT locator builder, then ARIA role, then native relative xpath only. `test_code_guardrails_check` flags `inputField` / `clickableField` as `SMART_LOCATOR`. ```java title="ThrowawayExploration.java" // Human exploration only. Do not generate or check this in. By email = SHAFT.GUI.Locator.inputField("Email"); By login = SHAFT.GUI.Locator.clickableField("Log In"); ``` --- ## Use `By` Objects, Not `@FindBy` The `@FindBy` annotation (from Selenium's `PageFactory`) has several drawbacks compared to plain `By` objects: | `@FindBy` | `By` Objects | |---|---| | Evaluated at runtime via reflection — errors appear late | Evaluated immediately — compile-time safety | | Cannot be reused across methods easily | Standard Java objects — pass, store, and compose freely | | Tied to `PageFactory.initElements()` lifecycle | No initialization ceremony required | | Hard to make dynamic or conditional | Easy to build dynamically with logic | :::warning `@FindBy` with `PageFactory` can lead to `StaleElementReferenceException` issues and adds unnecessary complexity. SHAFT's built-in element handling with `By` objects already provides smart waits, auto-scrolling, and retry logic. ::: Define your locators as `By` constants in your Page Object class instead: ```java title="LoginPage.java" public class LoginPage { private final By usernameInput = SHAFT.GUI.Locator.hasAnyTagName().hasId("username").build(); private final By passwordInput = SHAFT.GUI.Locator.hasAnyTagName().hasId("password").build(); private final By loginButton = SHAFT.GUI.Locator.hasRole(Role.BUTTON).hasNormalizedText("Log In").build(); } ``` ## Dynamic Locators Sometimes a locator depends on runtime data — a product name, a row index, or a user-provided value. Build `By` objects dynamically with a helper method: ```java title="DynamicLocators.java" public By getProductAddToCartButton(String productName) { return SHAFT.GUI.Locator.hasRole(Role.BUTTON) .hasAttribute("data-product", productName) .build(); } public By getTableCell(int row, int col) { return By.xpath(".//table//tr[" + row + "]//td[" + col + "]"); } ``` The [SHAFT Locator Builder](#shaft-locator-builder-methods) works the same way for dynamic locators, without raw XPath or CSS: ```java title="LocatorBuilderDynamic.java" public By getProductButton(String productName) { return SHAFT.GUI.Locator.hasTagName("button") .hasAttribute("data-product", productName) .build(); } ``` ## Cross-Platform Locators (Android and iOS) When testing the same app on both Android and iOS, locators are often different. Use a helper method that returns the correct locator based on the current platform: ```java title="CrossPlatformLocators.java" public class LoginPage { private By getUsernameInput() { if (SHAFT.Properties.platform.targetPlatform().equals(Platform.ANDROID.name())) { return By.id("com.example.app:id/username_input"); } else { return By.name("username_field"); } } } ``` :::tip For a cleaner approach, centralize platform-specific locators in a constants file or an enum-based strategy to avoid `if-else` blocks throughout your page objects. See [Cross-Platform Strategy](/docs/reference/guides/Cross_Platform_Strategy) for more details. ::: --- ## Best Practices 1. **Use a unique author-written id** through `SHAFT.GUI.Locator.hasAnyTagName().hasId(...)` when one exists. Never a framework-recycled id such as `:r1:`, `mat-input-3`, or `cdk-overlay-0`. 2. **Then use an ARIA role** through `hasRole(...)`, chained with `hasNormalizedText`, `hasAttribute`, or context until unique. 3. **Use native relative `By.xpath(...)` only** when the element has neither. Do not prefer CSS over that fallback, and do not emit `SHAFT.GUI.Locator.xpath(...)`. 4. **Keep Smart Locators for human exploration only.** Generated and repository code must not use `inputField` or `clickableField`. 5. **Keep locators simple** - complex locators are fragile and hard to maintain. 6. **Use data attributes** (e.g., `data-test`) specifically for testing when you control the HTML. 7. **Document complex locators** - add comments explaining why a particular strategy was chosen. 8. **Test locators in isolation** - verify your locators work before building tests around them. ## Related - [Web locator strategy](/docs/testing/web#locator-strategy) - [SHAFT Locator Builder guide](/docs/reference/actions/GUI/Locators_And_Self_Healing#shaft-locator-builder) - [SHAFT Locator Builder Examples](https://github.com/ShaftHQ/SHAFT_ENGINE/blob/main/shaft-engine/src/test/java/testPackage/locator/LocatorBuilderTest.java) - [Shadow DOM Examples](https://github.com/ShaftHQ/SHAFT_ENGINE/blob/main/shaft-engine/src/test/java/testPackage/locator/ShadowDomTest.java) - [Selenium By Methods Documentation](https://www.selenium.dev/selenium/docs/api/java/org/openqa/selenium/By.html) - [W3C WebDriver Specification](https://www.w3.org/TR/webdriver/) --- # File Actions Canonical HTML: https://shafthq.github.io/docs/reference/actions/CLI/File_Actions Markdown: https://shafthq.github.io/md/reference/actions/CLI/File_Actions.md ## Getting a File Actions Instance Use `SHAFT.CLI.file()` to get a `FileActions` instance: ```java title="FileActionsSetup.java" FileActions file = SHAFT.CLI.file(); ``` You can also use the legacy `FileActions.getInstance()` in existing projects. ## Read File ### readFile() Reads and returns the complete contents of a file as a string. ```java title="ReadFile.java" FileActions file = SHAFT.CLI.file(); String content = file.readFile("src/test/resources/testData/users.json"); ``` **Practical example — load test data before a test:** ```java title="LoadTestDataExample.java" public class UserImportTest { private SHAFT.API api; private String usersPayload; @BeforeClass public void loadTestData() { usersPayload = SHAFT.CLI.file() .readFile("src/test/resources/testDataFiles/users.json"); } @Test public void importUsersFromFile() { api.post("/users/import") .setRequestBody(usersPayload) .setContentType("application/json") .setTargetStatusCode(200); } @BeforeMethod public void setUp() { api = new SHAFT.API("https://api.example.com"); } } ``` ## Write to File ### writeToFile() Writes content to a file. Creates the file (and parent directories) if they do not exist; overwrites if the file already exists. ```java title="WriteFile.java" FileActions file = SHAFT.CLI.file(); file.writeToFile("target/reports/execution-summary.txt", "All 42 tests passed."); ``` SHAFT reports the target path and byte count for write operations; it does not attach raw byte payloads as action output. **Practical example — save API response for later comparison:** ```java title="SaveResponseExample.java" SHAFT.API api = new SHAFT.API("https://api.example.com"); api.get("/config").setTargetStatusCode(200); // Persist the baseline configuration SHAFT.CLI.file().writeToFile("src/test/resources/baseline/config.json", api.getResponseBody()); ``` ## Copy File ### copyFile() Copies a file from a source path to a destination path. ```java title="CopyFile.java" FileActions file = SHAFT.CLI.file(); // Back up a config file before the test modifies it file.copyFile("config/app.properties", "config/app.properties.bak"); ``` ## Delete File ### deleteFile() Deletes the file at the specified path. Safe to call even if the file does not exist. ```java title="DeleteFile.java" FileActions file = SHAFT.CLI.file(); // Clean up a temporary file after the test file.deleteFile("target/temp/downloaded-report.pdf"); ``` ## Complete Example: Download, Validate, and Clean Up ```java title="src/test/java/tests/ReportDownloadTest.java" public class ReportDownloadTest { private SHAFT.GUI.WebDriver driver; private FileActions file; private static final String DOWNLOAD_PATH = "target/downloads/report.pdf"; @Test public void downloadAndVerifyReport() { driver.browser().navigateToURL("https://example.com/reports") .and().element().click(By.id("download-report-btn")); // Give the download a moment to complete, then read and validate String content = file.readFile(DOWNLOAD_PATH); SHAFT.Validations.assertThat() .object(content) .isNotNull() .withCustomReportMessage("Downloaded report file should not be empty"); } @BeforeMethod public void setUp() { driver = new SHAFT.GUI.WebDriver(); file = SHAFT.CLI.file(); } @AfterMethod public void tearDown() { driver.quit(); file.deleteFile(DOWNLOAD_PATH); } } ``` ## File Path Handling | Style | Example | |-------|---------| | **Relative (from project root)** | `src/test/resources/data.json` | | **Relative (target output)** | `target/reports/result.txt` | | **Absolute — Linux / macOS** | `/home/runner/work/data/file.txt` | | **Absolute — Windows** | `C:\\Users\\user\\data\\file.txt` | Use forward slashes (`/`) for cross-platform compatibility; Java handles them on Windows too. ## Integration with File Validations File Actions pair naturally with SHAFT's file validation chain: ```java title="FileValidationIntegration.java" FileActions file = SHAFT.CLI.file(); // Create a file file.writeToFile("target/output.txt", "Hello SHAFT"); // Validate it exists SHAFT.Validations.assertThat() .file("target", "output.txt") .exists(); // Validate its content SHAFT.Validations.assertThat() .file("target", "output.txt") .content().contains("Hello SHAFT"); // Clean up file.deleteFile("target/output.txt"); ``` For the full file validation API see the [File validations →](../Validations#file-validations) reference. ## Best Practices - **Use relative paths** from the project root for portability across machines and CI environments. - **Back up before modifying** — use `copyFile()` to save a copy before overwriting configuration files. - **Clean up in teardown** — delete temporary files in `@AfterMethod` or `@AfterClass` to keep the workspace clean. - **Never store secrets in files committed to version control** — use environment variables instead. ## More file actions | Method | What it does | | --- | --- | | `createFile(String folderPath, String fileName)` / `createFolder(String folderPath)` | Creates an empty file or a folder. | | `renameFile(String filePath, String newFileName)` | Renames a file in place. | | `copyFolder(String sourceFolderPath, String destinationFolderPath)` / `deleteFolder(String folderPath)` | Copies or deletes a folder. | | `copyFileFromJar(String sourceFolderPath, String destinationFolderPath, String fileName)` / `copyFolderFromJar(String sourceFolderPath, String destinationFolderPath)` | Extracts a file or folder from the running JAR's resources to disk. | | `copyFileToLocalMachine(TerminalActions terminalSession, String targetFileFolderPath, String targetFileName, String... pathToTempDirectoryOnRemoteMachine)` | Copies a file from a remote or dockerized machine to the local machine and returns its local path. | | `doesFileExist(String targetFile)` / `doesFileExist(String fileFolderName, String fileName, int numberOfRetries)` | Checks whether a file exists, optionally retrying. | | `listFilesInDirectory(String targetDirectory)` / `listFilesInDirectory(String, TrueFileFilter)` / `listFilesInDirectory(TerminalActions, String)` | Returns the file names in a directory, locally or through a terminal session. | | `getFileList(String targetDirectory)` | Returns the files in a directory as a `Collection `. | | `readFileAsByteArray(String path)` | Reads a file as bytes. | | `readPDF(String relativeFilePath)` / `readPDF(String fileFolderName, String fileName)` | Extracts the text of a PDF file. | | `zipFiles(String srcFolder, String destZipFile)` | Zips a folder and returns whether it succeeded. | | `unpackArchive(URL url, String destinationFolderPath)` / `unpackArchive(File theFile, File targetDir)` | Downloads (when given a URL) and extracts an archive. | ## Related - [Terminal Actions](/docs/reference/actions/CLI/Terminal_Actions) - [Docker Terminal](/docs/reference/actions/CLI/Docker_Terminal) - [CLI](/docs/testing/cli) --- # Fluent Design: Chaining Actions and Validations Canonical HTML: https://shafthq.github.io/docs/reference/guides/Fluent_Design Markdown: https://shafthq.github.io/md/reference/guides/Fluent_Design.md SHAFT Engine provides a **fluent API** that lets you chain actions and validations in a single, readable statement. Instead of writing one action per line, you can express an entire test step as a flowing sentence. --- ## Why Fluent Design? Traditional test code often looks like a sequence of disconnected commands: ```java title="TraditionalStyle.java" driver.browser().navigateToURL("https://example.com/register"); driver.element().type(emailInput, "user@example.com"); driver.element().type(passwordInput, "securePass123"); driver.element().type(confirmPasswordInput, "securePass123"); driver.element().click(registerButton); ``` With SHAFT's fluent API, the same logic reads like a natural sentence: ```java title="FluentStyle.java" driver.browser().navigateToURL("https://example.com/register") .and().element() .type(emailInput, "user@example.com") .and().type(passwordInput, "securePass123") .and().type(confirmPasswordInput, "securePass123") .and().click(registerButton); ``` --- ## How It Works Every SHAFT action method returns the same action object, allowing you to call the next method directly on the result. The `.and()` method is syntactic sugar that returns `this` — it exists purely for readability. | Method | Returns | Purpose | |---|---|---| | `driver.element().click(locator)` | `Actions` | Perform click, continue chaining | | `driver.element().type(locator, text)` | `Actions` | Type text, continue chaining | | `.and()` | Same instance | Readability connector | | `driver.browser().navigateToURL(url)` | `BrowserActions` | Navigate, continue chaining | --- ## Chaining Actions with Validations The real power of fluent design is combining actions and validations in a single chain: ```java title="FluentActionsAndValidations.java" driver.browser().navigateToURL("https://example.com/register") .and().element() .type(emailInput, validEmail) .and().type(passwordInput, validPassword) .and().type(confirmPasswordInput, validPassword) .and().click(registerButton) .and().assertThat(confirmationLabel) .text().contains("Registration successful") .withCustomReportMessage("Verify registration completed with correct data"); ``` :::tip The `.withCustomReportMessage()` method adds a descriptive label in the Allure report, making it easy to understand what each validation checks. ::: --- ## Switching Between Action Types You can switch between browser actions, element actions, and validations within the same chain: ```java title="SwitchingActionTypes.java" driver.browser().navigateToURL("https://example.com/login") .and().element() .type(usernameInput, "admin") .and().type(passwordInput, "password") .and().click(loginButton) .and().browser() .navigateToURL("https://example.com/dashboard") .and().assertThat(welcomeMessage) .text().contains("Welcome, admin"); ``` --- ## Best Practices 1. **Use `.and()` for readability** — It does nothing functionally, but it makes chains read like English. 2. **Keep chains focused** — One chain should represent one logical test step or user action. 3. **Add custom report messages** — Use `.withCustomReportMessage()` on validations so your Allure report tells a clear story. 4. **Indent for clarity** — Align chained calls to visually group related actions together. :::info Fluent design is not just syntactic sugar — it encourages you to think about tests as user stories, which leads to more maintainable test suites. ::: ## Related - [Solution Design](/docs/reference/guides/Solution_Design) - [Test Structure](/docs/reference/guides/Test_Structure) - [Ci Cd Integration](/docs/reference/guides/CI_CD_Integration) - [Quick Start](/docs/start/quick-start) --- # GraphQL API Testing Canonical HTML: https://shafthq.github.io/docs/reference/actions/API/GraphQL_Testing Markdown: https://shafthq.github.io/md/reference/actions/API/GraphQL_Testing.md Use `SHAFT.API.sendGraphQlRequest()` for GraphQL queries and mutations. It returns the normal request builder, so variables, fragments, headers, status checks, and response assertions use the same fluent API as REST requests. --- ## Simple Query Send a basic GraphQL query and validate the response: ```java title="GraphQLTesting.java" SHAFT.API api = new SHAFT.API("https://api.example.com"); api.sendGraphQlRequest( "/graphql", "{ users { id name email } }" ); // Assert response content api.assertThatResponse().extractedJsonValue("$.data.users").isNotNull(); ``` --- ## Query with Variables Pass a variables JSON string alongside the query: ```java title="GraphQLTesting.java" // GraphQL query with variables String query = "query GetUser($id: ID!) { user(id: $id) { name email } }"; String variables = "{\"id\": \"123\"}"; SHAFT.API api = new SHAFT.API("https://api.example.com"); api.sendGraphQlRequest( "/graphql", query, variables ); ``` --- ## Query with Fragment Use GraphQL fragments for reusable field selections: ```java title="GraphQLTesting.java" String fragment = "fragment UserFields on User { id name email role }"; SHAFT.API api = new SHAFT.API("https://api.example.com"); api.sendGraphQlRequest( "/graphql", "{ users { ...UserFields } }", null, fragment ); ``` --- ## Query with Authentication Header Add a custom header (e.g., Bearer token) alongside the query: ```java title="GraphQLTesting.java" // GraphQL with fragment and auth header String fragment = "fragment UserFields on User { id name email role }"; SHAFT.API api = new SHAFT.API("https://api.example.com"); api.sendGraphQlRequest( "/graphql", "{ users { ...UserFields } }", null, fragment ).addHeader("Authorization", "Bearer mytoken123"); ``` --- ## Complete Test Example ```java title="GraphQLTest.java" public class GraphQLTest { @Test public void queryAllUsers() { SHAFT.API api = new SHAFT.API("https://api.example.com"); api.sendGraphQlRequest("/graphql", "{ users { id name email } }"); api.assertThatResponse().extractedJsonValue("$.data.users").isNotNull(); } @Test public void queryUserById() { SHAFT.API api = new SHAFT.API("https://api.example.com"); String query = "query GetUser($id: ID!) { user(id: $id) { name email } }"; String variables = "{\"id\": \"1\"}"; api.sendGraphQlRequest("/graphql", query, variables); api.assertThatResponse().extractedJsonValue("$.data.user.email").isNotNull(); } @Test public void createUserMutation() { SHAFT.API api = new SHAFT.API("https://api.example.com"); String mutation = "mutation CreateUser($input: CreateUserInput!) { " + "createUser(input: $input) { id name email } }"; String variables = "{\"input\": {\"name\": \"Jane\", \"email\": \"jane@example.com\"}}"; api.sendGraphQlRequest("/graphql", mutation, variables); api.assertThatResponse().extractedJsonValue("$.data.createUser.id").isNotNull(); } } ``` --- ## Asserting with SHAFT Validations You can also use SHAFT's built-in response validation for GraphQL responses: ```java title="GraphQLValidation.java" SHAFT.API api = new SHAFT.API("https://api.example.com"); api.post("/graphql") .setRequestBody("{\"query\": \"{ users { id name } }\"}") .addHeader("Content-Type", "application/json") .setTargetStatusCode(200) ; api.assertThatResponse() .extractedJsonValue("$.data.users[0].name") .isEqualTo("John Doe"); ``` --- :::tip Use the `variables` parameter instead of string interpolation in GraphQL queries to avoid injection issues and improve readability. ::: :::warning GraphQL APIs typically return HTTP 200 even when errors occur — always validate the `errors` field in the response body alongside the status code. ::: ## Related - [Request Builder](/docs/reference/actions/API/Request_Builder) - [Response Validations](/docs/reference/actions/API/Response_Validations) - [API Authentication](/docs/reference/actions/API/API_Authentication) - [API](/docs/testing/api) --- # gRPC API Testing Canonical HTML: https://shafthq.github.io/docs/reference/actions/API/gRPC_Testing Markdown: https://shafthq.github.io/md/reference/actions/API/gRPC_Testing.md Use `SHAFT.API.grpc(target)` to make unary gRPC calls without generated stubs. SHAFT resolves message types through server reflection, or through a descriptor set you provide. Requests and responses are JSON, so the usual JSON assertions work on the result. ## Unary call with server reflection ```java GrpcActions.Response response = SHAFT.API.grpc("localhost:50051") .unary("grpc.health.v1.Health/Check", "{\"service\":\"\"}"); SHAFT.Validations.assertThat().object(response.json("$.status")).isEqualTo("SERVING"); ``` ## Descriptor set instead of reflection Build the set with `protoc --include_imports --descriptor_set_out=services.desc ...`, then: ```java SHAFT.API.grpc("localhost:50051") .withDescriptorSet(Path.of("services.desc")) .withTimeout(10) .unary("shop.Orders/Get", "{\"id\":42}"); ``` ## Status codes and errors A non-OK status is returned rather than thrown: check `response.statusCode()` (for example `NOT_FOUND`) and `response.description()`. Each call is reported as an Allure step with the request, the response and the status code. For TLS or in-process channels, pass your own `io.grpc.Channel` to `SHAFT.API.grpc(channel)`. ## Related - [Request Builder](/docs/reference/actions/API/Request_Builder) - [Response Validations](/docs/reference/actions/API/Response_Validations) - [GraphQL Testing](/docs/reference/actions/API/GraphQL_Testing) - [API](/docs/testing/api) --- # JUnit Integration Canonical HTML: https://shafthq.github.io/docs/reference/guides/JUnit_Integration Markdown: https://shafthq.github.io/md/reference/guides/JUnit_Integration.md SHAFT Engine supports **JUnit (Jupiter)** as a first-class test runner alongside TestNG. All SHAFT APIs, reporting, and properties configuration work identically regardless of which test framework you choose. --- ## Maven Dependency Add the JUnit Jupiter engine to your `pom.xml`: ```xml title="pom.xml" org.junit.jupiter junit-jupiter 5.10.2 test ``` :::info SHAFT Engine's `pom.xml` may already include JUnit transitively. Check your dependency tree with `mvn dependency:tree` before adding it manually. ::: --- ## Runtime Registration SHAFT Engine registers its JUnit support through the Java ServiceLoader mechanism: - `com.shaft.listeners.JunitListener` is registered as a JUnit Platform `LauncherSessionListener`. - `com.shaft.listeners.JunitExtension` is registered as a Jupiter extension. - `junit.jupiter.extensions.autodetection.enabled=true` is supplied through `junit-platform.properties`. Generated JUnit projects also create or update `src/test/resources/junit-platform.properties` without replacing existing settings. If you maintain that file yourself, keep this line in it: ```properties title="src/test/resources/junit-platform.properties" junit.jupiter.extensions.autodetection.enabled=true ``` You do not need to add `@ExtendWith` to ordinary SHAFT JUnit tests. The listener and extension together provide the same SHAFT lifecycle behavior that TestNG users get: engine setup and teardown, Allure attachments, execution summary rows, telemetry, Jira/Xray handoff, retries, fail-fast skips, linked issue skips, setup/teardown failure reporting, and soft-verification failures that fail the JUnit run. JUnit retries use the same SHAFT flag as TestNG retries: `retryMaximumNumberOfAttempts`. Configure it in `custom.properties`, pass it as `-DretryMaximumNumberOfAttempts=2`, or update it at runtime with `SHAFT.Properties.flags.set().retryMaximumNumberOfAttempts(2)`. The value is the number of additional attempts after the first failure. For example, `retryMaximumNumberOfAttempts=1` runs the original attempt plus one retry. In JUnit, SHAFT schedules the retry after the failed attempt's `@AfterEach` lifecycle has completed and executes the retry through JUnit again, so the retry receives a fresh `@BeforeEach` setup. Put browser creation in `@BeforeEach` and driver cleanup in `@AfterEach` when retries need independent browser sessions. :::note TestNG remains supported. TestNG XML suite shaping and TestNG retry analyzer binding are still TestNG runner features. For JUnit cross-browser coverage, use JUnit parameterized tests and SHAFT per-thread properties instead of relying on TestNG XML cloning. ::: --- ## Test Class Structure ```java title="WebTestJUnit.java" @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class WebTestJUnit { private static SHAFT.GUI.WebDriver driver; @BeforeEach void beforeEach() { driver = new SHAFT.GUI.WebDriver(); } @Test @Order(1) void navigateAndAssertTitle() { driver.browser().navigateToURL("https://duckduckgo.com/") .and().assertThat().browser().title().contains("DuckDuckGo"); } @Test @Order(2) void searchAndVerifyResults() { driver.browser().navigateToURL("https://duckduckgo.com/"); driver.element().type(By.id("searchbox_input"), "SHAFT Engine"); driver.element().click(By.id("search_button_homepage")); driver.assertThat().browser().url().contains("q=SHAFT"); } @AfterEach void afterEach() { driver.quit(); } } ``` --- ## JUnit vs TestNG Comparison | Feature | JUnit | TestNG | |---|---|---| | Setup annotation | `@BeforeEach` | `@BeforeMethod` | | Teardown annotation | `@AfterEach` | `@AfterMethod` | | Class-level setup | `@BeforeAll` | `@BeforeClass` | | Test ordering | `@TestMethodOrder` + `@Order` | `priority` on `@Test` | | Test annotation | `@Test` (Jupiter) | `@Test` (TestNG) | | Parameterized tests | `@ParameterizedTest` | `@DataProvider` | --- ## Class-Level Driver (Shared Instance) Use `@BeforeAll` / `@AfterAll` with a `static` driver for tests that share a single browser session: ```java title="SharedDriverJUnit.java" @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class SharedDriverJUnit { private static SHAFT.GUI.WebDriver driver; @BeforeAll static void setupAll() { driver = new SHAFT.GUI.WebDriver(); driver.browser().navigateToURL("https://example.com/login"); driver.element() .type(By.id("email"), "test@example.com") .type(By.id("password"), "secret") .click(By.id("loginBtn")); } @Test @Order(1) void verifyDashboardLoaded() { driver.assertThat().browser().url().contains("/dashboard"); } @Test @Order(2) void verifyUserNameDisplayed() { driver.assertThat(By.id("userName")).text().contains("Test User"); } @AfterAll static void teardownAll() { driver.quit(); } } ``` --- :::tip Use `@BeforeEach` / `@AfterEach` (fresh driver per test) for **independent test isolation**. Use `@BeforeAll` / `@AfterAll` (shared driver) for **ordered workflow tests** where each step builds on the previous one. ::: :::note SHAFT's JUnit listener and extension are automatically registered via the Java ServiceLoader mechanism. ::: ## Related - [Solution Design](/docs/reference/guides/Solution_Design) - [Test Structure](/docs/reference/guides/Test_Structure) - [Ci Cd Integration](/docs/reference/guides/CI_CD_Integration) - [Quick Start](/docs/start/quick-start) --- # Natural Language Actions Canonical HTML: https://shafthq.github.io/docs/reference/actions/GUI/Natural_Language_Actions Markdown: https://shafthq.github.io/md/reference/actions/GUI/Natural_Language_Actions.md Natural-language actions let a test express a browser, element, or touch intent through `SHAFT.GUI.WebDriver.act(...)`. The engine plans the request, validates the plan against the configured trust threshold, and executes it only when the plan is trusted enough. The feature is disabled by default. Enable it only in tests where readable workflow intent is more valuable than direct locator calls: ```java title="EnableNaturalActions.java" SHAFT.Properties.naturalActions.set() .enabled(true) .minimumTrustPercentage(85) .planner("deterministic") .aiFallbackEnabled(false) .allowedActions("browser,element,touch"); ``` ## Basic usage ```java title="NaturalLogin.java" String username = "standard_user"; String password = System.getenv("APP_PASSWORD"); driver.act("Login with valid credentials", username, password); ``` `act(...)` returns the same `SHAFT.GUI.WebDriver` instance so it can be used in fluent flows. Arguments are passed separately from the natural-language intent. SHAFT logs argument counts and intent metadata, not raw argument values. ## Deterministic planner The default planner is local and deterministic. It recognizes a small set of high-signal patterns and maps them to existing SHAFT actions: | Intent pattern | Action | | --- | --- | | `refresh`, `refresh page`, `reload page` | `driver.browser().refreshCurrentPage()` | | `go back`, `navigate back` | `driver.browser().navigateBack()` | | `go forward`, `navigate forward` | `driver.browser().navigateForward()` | | `open ...` or `navigate to ...` with a URL argument | `driver.browser().navigateToURL(url)` | | `Login with valid credentials` with username and password arguments | types username, types password securely, then clicks login/sign in/submit | | `click Save` | clicks a semantic clickable element | | `type into Email` with one argument | types into a semantic input field | | `clear Search` | clears a semantic input field | | `tap Continue` | taps a semantic clickable element | Element and touch plans use SHAFT smart locators under the hood. If no deterministic pattern matches, the action fails without changing the page. ## Trust gate Every plan has a trust score. SHAFT executes the action only when the score is greater than or equal to `naturalActions.minimumTrustPercentage`. ```properties title="src/main/resources/properties/custom.properties" naturalActions.enabled=true naturalActions.minimumTrustPercentage=90 naturalActions.planner=deterministic naturalActions.aiFallback.enabled=false naturalActions.allowedActions=browser,element,touch ``` Use `allowedActions` to narrow what natural actions may do in a test class. For example, `allowedActions=browser` allows navigation-style actions but rejects element and touch actions before execution. ## Optional planner fallback Set `naturalActions.planner=auto` and `naturalActions.aiFallback.enabled=true` only when an optional planner should be allowed after the deterministic planner cannot produce a plan. SHAFT discovers additional planners through Java `ServiceLoader`; `shaft-pilot-core` provides a Pilot-backed natural-action planner that is also disabled unless `pilot.enabled=true`. Provider-assisted planning is still plan-only. The returned steps must pass the same local validation, allowed-action filter, and trust threshold before SHAFT touches the browser. ## MCP usage `shaft-mcp` previously exposed this behavior through a `natural_act` tool (and a mobile `mobile_natural_act` counterpart). The [SHAFT tool architecture sweep](https://github.com/ShaftHQ/SHAFT_ENGINE/issues/3866) deleted both tools outright, with no deprecation shim: `driver.act(...)` remains a supported Java API, but MCP clients can no longer trigger natural-language execution through a dedicated tool call. Enable `naturalActions` in test code as shown above and drive the browser/mobile session through the deterministic `element_*`/ `browser_*`/`mobile_*` MCP tools instead. See [Connect shaft-mcp](/docs/agentic/mcp) for transport setup and MCP client approval guidance. ## Related - [Browser Actions](/docs/reference/actions/GUI/Browser_Actions) - [Element Actions](/docs/reference/actions/GUI/Element_Actions) - [Element Identification](/docs/reference/actions/GUI/Element_Identification) - [Web](/docs/testing/web) --- # Oracle JDBC Setup Canonical HTML: https://shafthq.github.io/docs/reference/actions/DB/Oracle_JDBC_Setup Markdown: https://shafthq.github.io/md/reference/actions/DB/Oracle_JDBC_Setup.md ## Oracle JDBC Driver Setup Oracle JDBC drivers are not available in Maven Central Repository due to Oracle's licensing terms. This guide walks you through the process of downloading and configuring the Oracle JDBC driver for use with SHAFT. ## Prerequisites - Java Development Kit (JDK) installed - Maven or Gradle build tool - Oracle Database access (for testing) ## Step 1: Download Oracle JDBC Driver ### Option A: Download from Oracle Website 1. Navigate to the [Oracle JDBC Downloads page](https://www.oracle.com/database/technologies/appdev/jdbc-downloads.html) 2. Select the appropriate JDBC driver version based on your: - **Oracle Database version** (e.g., 19c, 21c, 23c) - **Java runtime for this project**. SHAFT projects use the Java baseline listed in [Install SHAFT](/docs/start/installation); Oracle's driver compatibility matrix may mention older Java runtimes for non-SHAFT projects. 3. Common driver files: - `ojdbc8.jar` - For Java 8 - `ojdbc10.jar` - For Java 10 and 11 - `ojdbc11.jar` - For Java 11 and higher 4. Accept the Oracle Technology Network License Agreement 5. Download the JAR file to your local machine ### Option B: Download via SQL Developer or Oracle Instant Client If you have Oracle SQL Developer or Oracle Instant Client installed, the JDBC driver is already included in their installation directories. ## Step 2: Add Driver to Your Project ### Maven Setup #### Method 1: Install to Local Maven Repository (Recommended) Install the downloaded JAR file to your local Maven repository: ```bash mvn install:install-file \ -Dfile=/path/to/ojdbc11.jar \ -DgroupId=com.oracle.database.jdbc \ -DartifactId=ojdbc11 \ -Dversion=23.3.0.23.09 \ -Dpackaging=jar ``` **Parameters Explanation:** - `-Dfile`: Path to your downloaded Oracle JDBC JAR file - `-DgroupId`: Oracle's group ID (use `com.oracle.database.jdbc`) - `-DartifactId`: Artifact ID matching your JAR name (`ojdbc8`, `ojdbc10`, or `ojdbc11`) - `-Dversion`: Version number (check the JAR file name or Oracle documentation) - `-Dpackaging`: Always `jar` Then add the dependency to your `pom.xml`: ```xml com.oracle.database.jdbc ojdbc11 23.3.0.23.09 ``` #### Method 2: System Scope Dependency Place the Oracle JDBC JAR file in your project directory (e.g., `lib/ojdbc11.jar`) and reference it using system scope: ```xml com.oracle.database.jdbc ojdbc11 23.3.0.23.09 system ${project.basedir}/lib/ojdbc11.jar ``` **Note:** This method is not recommended for production as it's not portable across different environments. #### Method 3: Using Oracle Maven Repository Oracle provides its own Maven repository. Add it to your `pom.xml`: ```xml maven.oracle.com Oracle Maven Repository https://maven.oracle.com default true com.oracle.database.jdbc ojdbc11 23.3.0.23.09 ``` **Note:** You may need to accept Oracle's terms and configure authentication for this repository. ### Gradle Setup #### Method 1: Add to Local Repository First, install to local Maven repository as shown above, then add to `build.gradle`: ```groovy dependencies { implementation 'com.oracle.database.jdbc:ojdbc11:23.3.0.23.09' } ``` #### Method 2: File Dependency Place the JAR in your project's `libs` directory and add: ```groovy dependencies { implementation files('libs/ojdbc11.jar') } ``` #### Method 3: Using Oracle Maven Repository ```groovy repositories { mavenCentral() maven { url "https://maven.oracle.com" } } dependencies { implementation 'com.oracle.database.jdbc:ojdbc11:23.3.0.23.09' } ``` ## Step 3: Verify Installation Create a simple test to verify the driver is correctly installed: ```java public class OracleDBTest { @Test public void testOracleConnection() { // Replace with your Oracle database connection details String jdbcUrl = "jdbc:oracle:thin:@localhost:1521:ORCL"; String username = "your_username"; String password = "your_password"; try { SHAFT.DB db = new SHAFT.DB(jdbcUrl, username, password); // Execute a simple query to verify connection String query = "SELECT 'Connection Successful' AS message FROM dual"; String result = db.getResult(query); SHAFT.Validations.assertThat() .object(result) .isEqualTo("Connection Successful"); db.closeConnection(); } catch (Exception e) { SHAFT.Report.fail("Failed to connect to Oracle database", e); } } } ``` Run the test to verify the Oracle JDBC driver is working correctly. ## Step 4: Common Connection Examples ### Basic Oracle Connection ```java SHAFT.DB db = new SHAFT.DB( "jdbc:oracle:thin:@localhost:1521:ORCL", "username", "password" ); ``` ### Oracle with Service Name ```java SHAFT.DB db = new SHAFT.DB( "jdbc:oracle:thin:@//localhost:1521/ORCLPDB", "username", "password" ); ``` ### Oracle with TNS Name ```java SHAFT.DB db = new SHAFT.DB( "jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=oracle-host)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=ORCL)))", "username", "password" ); ``` ### Oracle RAC (Real Application Clusters) ```java SHAFT.DB db = new SHAFT.DB( "jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=rac-node1)(PORT=1521))(ADDRESS=(PROTOCOL=TCP)(HOST=rac-node2)(PORT=1521)))(CONNECT_DATA=(SERVICE_NAME=ORCL)))", "username", "password" ); ``` ## Version Compatibility ### Oracle JDBC Driver Versions | JDBC Driver | Driver Java Compatibility | Oracle DB Support | |-------------|--------------|-------------------| | ojdbc8.jar | Java 8, 11 | 12.2.0.1+ | | ojdbc10.jar | Java 10, 11 | 19c, 21c | | ojdbc11.jar | Java 11+ | 19c, 21c, 23c | ### Choosing the Right Version - For SHAFT projects, choose a driver compatible with the Java baseline listed in [Install SHAFT](/docs/start/installation). - The driver is backward compatible with older Oracle Database versions - Check [Oracle JDBC Compatibility](https://www.oracle.com/database/technologies/jdbc-drivers-12c-downloads.html) for specific version support ## Troubleshooting ### ClassNotFoundException: oracle.jdbc.driver.OracleDriver **Problem:** Oracle JDBC driver is not in the classpath. **Solution:** - Verify the JAR file is correctly added to your project dependencies - Clean and rebuild your project (`mvn clean install` or `gradle clean build`) - Check that the JAR file path is correct ### SQLException: Listener refused the connection **Problem:** Oracle listener is not running or connection details are incorrect. **Solution:** - Verify Oracle Database is running - Check hostname, port, and SID/service name - Test connection using SQL*Plus or SQL Developer first - Ensure firewall allows connections on Oracle port (default 1521) ### SQLException: ORA-12505, TNS:listener does not currently know of SID **Problem:** SID or service name is incorrect. **Solution:** - Verify the SID or service name with your DBA - Try using service name format instead of SID format or vice versa - Check `tnsnames.ora` file for correct service names ### Network Adapter Error **Problem:** Network connectivity issues. **Solution:** - Verify network connectivity to Oracle server - Check if Oracle listener is running: `lsnrctl status` - Ensure correct hostname/IP address - Test with `tnsping` command ## Additional Dependencies For advanced Oracle features, you may need additional JAR files: ### Oracle Internationalization (orai18n.jar) Required for character set support beyond US7ASCII and WE8ISO8859P1: ```xml com.oracle.database.nls orai18n 23.3.0.23.09 ``` ### Oracle UCP (Universal Connection Pool) For connection pooling: ```xml com.oracle.database.jdbc ucp 23.3.0.23.09 ``` ## Best Practices ### Security - Store Oracle credentials securely (environment variables, vault, etc.) - Never commit credentials to version control - Use Oracle Wallet for credential management in production ### Performance - Use connection pooling for multiple database operations - Close connections properly to avoid resource leaks - Use batch operations for bulk data operations ### Testing - Use a dedicated test database/schema - Never run tests against production databases - Reset test data between test runs - Use transactions and rollbacks for data integrity ## Resources - [Oracle JDBC Downloads](https://www.oracle.com/database/technologies/appdev/jdbc-downloads.html) - [Oracle JDBC Developer's Guide](https://docs.oracle.com/en/database/oracle/oracle-database/21/jjdbc/) - [Oracle JDBC FAQ](https://www.oracle.com/database/technologies/faq-jdbc.html) - [SHAFT Database Connection Strings](Connection_Strings.md) - [SHAFT Database Actions](DB_Actions.md) ## Related - [DB Actions](/docs/reference/actions/DB/DB_Actions) - [Connection Strings](/docs/reference/actions/DB/Connection_Strings) - [Database](/docs/testing/database) --- # Parallel Execution Configuration Canonical HTML: https://shafthq.github.io/docs/reference/configuration/parallelExecution Markdown: https://shafthq.github.io/md/reference/configuration/parallelExecution.md SHAFT supports multiple types of parallel execution to help you run tests faster and more efficiently. This guide covers all the different parallel execution options available and how to configure them. ## Overview SHAFT provides three main types of parallel execution: 1. **TestNG Parallel Execution** - Run multiple test methods, classes, or suites in parallel 2. **Cross-Browser Parallel Execution** - Run the same tests across multiple browsers simultaneously 3. **ThreadLocal Driver Pattern** - Safely manage WebDriver instances in multi-threaded environments --- ## 1. TestNG Parallel Execution TestNG provides built-in support for running tests in parallel. SHAFT makes it easy to configure this through properties. ### Configuration You can configure TestNG parallel execution in your `TestNG.properties` file or programmatically. #### File-based Configuration ```properties title="src/main/resources/properties/TestNG.properties" # Set the parallel mode: METHODS, CLASSES, TESTS, or INSTANCES setParallel=METHODS # Set the parallelization mode: STATIC or DYNAMIC setParallelMode=DYNAMIC # Number of threads to use # In STATIC mode: Used as-is # In DYNAMIC mode: Total threads = CPU cores × setThreadCount setThreadCount=2 # Control test execution order setPreserveOrder=false # Group test instances together setGroupByInstances=false # Number of threads for data providers setDataProviderThreadCount=1 # Test suite timeout in minutes testSuiteTimeout=1440 ``` ### Parallel Modes Explained | Mode | Description | Use Case | |------|-------------|----------| | `METHODS` | Runs test methods in parallel | Best for independent test methods | | `CLASSES` | Runs test classes in parallel | Good when each class has setup/teardown | | `TESTS` | Runs test tags/suites in parallel | Useful for running different test suites | | `INSTANCES` | Runs test instances in parallel | For tests with multiple instances | | `NONE` | No parallelization (default) | Sequential execution | ### Thread Count Calculation - **STATIC Mode**: Uses the exact `setThreadCount` value - **DYNAMIC Mode**: Calculates total threads as: ``` Total Threads = Number of CPU Cores × setThreadCount ``` ### Example Configuration Scenarios #### Run test methods in parallel with 4 threads ```properties setParallel=METHODS setParallelMode=STATIC setThreadCount=4 ``` #### Run test classes in parallel (dynamic based on CPU cores) ```properties setParallel=CLASSES setParallelMode=DYNAMIC setThreadCount=2 ``` #### Run tests sequentially (no parallelization) ```properties setParallel=NONE setThreadCount=1 ``` ### Using a testng.xml File In addition to the properties-based configuration, you can use a `testng.xml` file to define your test suite, choose exactly which classes and methods to run, and control parallel execution with fine-grained options. #### Why Use testng.xml? - **Explicit test selection** — Choose exactly which classes and methods to run - **Multiple test groups** — Define separate ` ` blocks with different configurations - **Suite-level configuration** — Set parameters, listeners, and groups at the suite level - **Better CI integration** — Reference a single file to run your entire suite from one place #### Where to Place testng.xml Place the file in your project root or under `src/test/resources`: ``` project-root/ ├── src/ │ ├── main/resources/properties/ │ │ ├── custom.properties │ │ └── TestNG.properties │ └── test/ │ ├── java/com/example/tests/ │ │ └── SearchTest.java │ └── resources/ │ └── testng.xml ← recommended location └── testng.xml ← or project root ``` #### Basic testng.xml Structure ```xml title="src/test/resources/testng.xml" ``` #### Parallel Execution Attributes | Attribute | Values | Description | |-----------|--------|-------------| | `parallel` | `methods` | Run test methods in parallel (most common) | | `parallel` | `classes` | Run test classes in parallel | | `parallel` | `tests` | Run ` ` blocks in parallel | | `parallel` | `instances` | Run test instances in parallel | | `parallel` | `none` | No parallelization (default) | | `thread-count` | integer | Number of parallel threads | #### Example: Run Methods in Parallel (4 Threads) ```xml title="src/test/resources/testng.xml" ``` #### Example: Run Test Blocks in Parallel Each ` ` block runs in its own thread — useful for isolating smoke tests from regression tests: ```xml title="src/test/resources/testng.xml" ``` #### Configure Maven to Use testng.xml Update the Maven Surefire Plugin in your `pom.xml` to point to your `testng.xml`: ```xml title="pom.xml" org.apache.maven.plugins maven-surefire-plugin 3.2.5 src/test/resources/testng.xml ``` #### Running Tests Once Maven is configured, run as normal: ```bash mvn test ``` To override the XML file path from the command line without changing `pom.xml`: ```bash mvn test -DsuiteXmlFile=src/test/resources/smoke-testng.xml ``` :::note testng.xml vs TestNG.properties - **`testng.xml`** controls _which_ tests to run and _how_ to run them (suite structure, parallel mode, thread count per suite). - **`TestNG.properties`** applies the same settings globally across all runs. When both are present, the `testng.xml` suite-level settings (parallel mode and thread count) take precedence. ::: --- ## 2. Cross-Browser Parallel Execution SHAFT can automatically run your tests across Chrome, Firefox, and Safari either sequentially or in parallel. ### Prerequisites - **Docker Desktop** must be installed and configured to use Linux images - Docker must be running before test execution ### Configuration ```properties title="src/main/resources/properties/custom.properties" # Run tests on Chrome, Firefox, and Safari in parallel SHAFT.CrossBrowserMode=parallelized # Or run them sequentially # SHAFT.CrossBrowserMode=sequential # Or disable cross-browser mode (default) # SHAFT.CrossBrowserMode=off ``` ```bash # Parallel execution across browsers mvn test -DSHAFT.CrossBrowserMode=parallelized # Sequential execution across browsers mvn test -DSHAFT.CrossBrowserMode=sequential ``` ```java @BeforeClass public void beforeClass() { // Enable parallel cross-browser execution SHAFT.Properties.platform.set().crossBrowserMode("parallelized"); // Or sequential // SHAFT.Properties.platform.set().crossBrowserMode("sequential"); } ``` ### Cross-Browser Modes | Mode | Description | |------|-------------| | `off` | Normal execution with your configured browser (default) | | `sequential` | Tests run on Chrome, Firefox, and Safari one after another | | `parallelized` | Tests run on all three browsers at the same time | :::note Docker Requirement Cross-browser mode requires Docker Desktop to be installed and running. SHAFT will use Docker to spin up browser containers for parallel execution. ::: ### Example: Cross-Browser + TestNG Parallel You can combine cross-browser execution with TestNG parallelization for maximum speed: ```properties title="src/main/resources/properties/custom.properties" SHAFT.CrossBrowserMode=parallelized ``` ```properties title="src/main/resources/properties/TestNG.properties" setParallel=CLASSES setThreadCount=3 ``` This will run each test class on all three browsers in parallel. --- ## 3. ThreadLocal Driver Pattern When running tests in parallel, each thread needs its own WebDriver instance to avoid conflicts. SHAFT recommends using ThreadLocal to manage driver instances safely. ### Why ThreadLocal? - **Thread Safety**: Each thread gets its own WebDriver instance - **No Conflicts**: Prevents race conditions when tests run in parallel - **Clean Isolation**: Each test has complete isolation from others ### Implementation Here's a complete example of using ThreadLocal with SHAFT: ```java title="ParallelThreadLocalTest.java" public class ParallelThreadLocalTest { // ThreadLocal variable to store driver per thread private ThreadLocal driver = new ThreadLocal<>(); private SHAFT.TestData.JSON testData; @Test public void loginTest() { new LoginPage(driver.get()) .login(testData.getTestData("email"), testData.getTestData("password")) .verifyUserIsLoggedIn(testData.getTestData("welcomeText")); } @Test public void searchTest() { new HomePage(driver.get()) .search(testData.getTestData("searchQuery")) .verifySearchResults(testData.getTestData("expectedResults")); } @BeforeClass public void beforeClassSetup() { // Load test data once per class testData = new SHAFT.TestData.JSON("TestData.json"); } @BeforeMethod public void beforeMethodSetUp() { // Create a new driver instance for each test method driver.set(new SHAFT.GUI.WebDriver()); driver.get().browser().navigateToURL("https://example.com"); } @AfterMethod public void afterMethodTearDown() { // Quit the driver after each test method driver.get().quit(); } } ``` ### Key Points for ThreadLocal Pattern 1. **Declare ThreadLocal**: Create a `ThreadLocal ` field 2. **Set in @BeforeMethod**: Initialize driver with `driver.set(new SHAFT.GUI.WebDriver())` 3. **Access with .get()**: Use `driver.get()` to access the driver instance 4. **Clean up in @AfterMethod**: Always quit the driver with `driver.get().quit()` ### Configure TestNG for Parallel Execution ```properties title="src/main/resources/properties/TestNG.properties" setParallel=METHODS setThreadCount=3 ``` --- ## Best Practices and Tips ### 1. Start Small - Begin with `setThreadCount=2` and gradually increase - Monitor system resources (CPU, memory) during execution - Find the optimal thread count for your machine and tests ### 2. Ensure Test Independence - Each test should be completely independent - Don't rely on test execution order - Clean up all test data in `@AfterMethod` or `@AfterClass` ### 3. Use ThreadLocal Consistently - Always use `driver.get()` to access the driver - Never share driver instances between threads - Initialize driver in `@BeforeMethod`, quit in `@AfterMethod` ### 4. Handle Shared Resources Carefully - Use synchronization for shared test data - Consider using separate test data for each thread - Be cautious with database connections and file I/O ### 5. Configure Appropriate Timeouts ```properties defaultElementIdentificationTimeout=30 browserNavigationTimeout=60 pageLoadTimeout=60 ``` ### 6. Consider Headless Execution Running in headless mode is faster and uses fewer resources: ```properties headlessExecution=true ``` ### 7. Enable Maximum Performance Mode For even faster execution, consider enabling maximum performance mode: ```properties maximumPerformanceMode=2 ``` This provides up to 400% performance boost by disabling complementary features. --- ## Troubleshooting ### Tests Failing Only in Parallel Mode **Problem**: Tests pass when running individually but fail in parallel. **Solutions**: - Check for shared state between tests - Ensure each test cleans up after itself - Verify ThreadLocal driver is used correctly - Look for race conditions in your page objects ### Out of Memory Errors **Problem**: `OutOfMemoryError` when running many tests in parallel. **Solutions**: - Reduce `setThreadCount` - Increase JVM heap size: `mvn test -Xmx2g` - Enable headless execution - Ensure drivers are properly quit after each test ### Docker Not Found for Cross-Browser **Problem**: Cross-browser mode fails with Docker errors. **Solutions**: - Install Docker Desktop - Start Docker before running tests - Verify Docker is configured for Linux containers - Check Docker has sufficient resources allocated ### WebDriver Exceptions **Problem**: `NoSuchSessionException` or `WebDriverException` in parallel tests. **Solutions**: - Verify ThreadLocal pattern is implemented correctly - Check that `driver.set()` is called in `@BeforeMethod` - Ensure `driver.get().quit()` is in `@AfterMethod` - Don't reuse driver instances across methods --- ## Performance Comparison Here's an example of performance gains with parallel execution: | Configuration | Execution Time | Improvement | |---------------|----------------|-------------| | Sequential (1 thread) | 60 minutes | Baseline | | Parallel (2 threads) | 35 minutes | 42% faster | | Parallel (4 threads) | 20 minutes | 67% faster | | Parallel (4 threads) + Headless | 15 minutes | 75% faster | | Parallel (4 threads) + Max Performance | 12 minutes | 80% faster | :::note Actual results vary based on test suite, system resources, and test complexity. ::: --- ## Complete Configuration Example Here's a complete configuration for maximum parallel execution performance: ```properties title="src/main/resources/properties/custom.properties" # Target browser targetBrowserName=chrome # Enable headless execution for better performance headlessExecution=true # Enable maximum performance mode maximumPerformanceMode=2 # Cross-browser parallel execution SHAFT.CrossBrowserMode=parallelized # Optimize timeouts defaultElementIdentificationTimeout=30 browserNavigationTimeout=60 pageLoadTimeout=60 ``` ```properties title="src/main/resources/properties/TestNG.properties" # Run test methods in parallel setParallel=METHODS # Dynamic thread calculation based on CPU cores setParallelMode=DYNAMIC # 2 threads per CPU core setThreadCount=2 # Don't preserve test order for maximum speed setPreserveOrder=false ``` ```java title="Test Class with ThreadLocal" public class MyParallelTest { private ThreadLocal driver = new ThreadLocal<>(); @Test public void testOne() { driver.get().browser().navigateToURL("https://example.com"); // Your test code here } @BeforeMethod public void setup() { driver.set(new SHAFT.GUI.WebDriver()); } @AfterMethod public void teardown() { driver.get().quit(); } } ``` --- ## Summary SHAFT provides powerful parallel execution capabilities: 1. **TestNG Parallel Execution**: Configure through `TestNG.properties` with multiple modes (METHODS, CLASSES, TESTS, INSTANCES) 2. **Cross-Browser Execution**: Run tests across Chrome, Firefox, and Safari sequentially or in parallel 3. **ThreadLocal Pattern**: Safely manage WebDriver instances in multi-threaded environments These features cover the common parallel-execution cases. Start with TestNG parallel settings, then add cross-browser execution or `ThreadLocal` only when the test runner and driver lifecycle require them. ## Related - [Properties List](/docs/reference/properties/PropertiesList) - [Property Types](/docs/reference/properties/PropertyTypes) - [Common Examples](/docs/reference/properties/CommonExamples) - [Web testing](/docs/testing/web) --- # Playwright Backend Canonical HTML: https://shafthq.github.io/docs/reference/actions/GUI/Playwright_Backend Markdown: https://shafthq.github.io/md/reference/actions/GUI/Playwright_Backend.md SHAFT can run GUI tests through Microsoft Playwright while keeping the same high-level SHAFT browser, element, alert, and assertion entry points. ```java title="PlaywrightTest.java" public class PlaywrightTest { private SHAFT.GUI.Driver driver; @BeforeMethod public void openBrowser() { driver = new SHAFT.GUI.Playwright(); } @Test public void search() { driver.browser().navigateToURL("https://example.com"); driver.assertThat().browser().title().contains("Example"); } @AfterMethod(alwaysRun = true) public void closeBrowser() { driver.quit(); } } ``` Use `SHAFT.GUI.WebDriver` for the Selenium/Appium backend and `SHAFT.GUI.Playwright` for the Playwright backend. Both implement `SHAFT.GUI.Driver`, so test classes can choose the backend at setup time. ## Configuration Common browser settings continue to use the existing web properties: - `targetBrowserName` - `headlessExecution` - `baseURL` - `browserWindowWidth` - `browserWindowHeight` - `storageStatePath` — auto-loads a storage-state JSON file on driver init (see [Auto-load storage state on driver init](/docs/reference/actions/GUI/Browser_Actions#auto-load-storage-state-on-driver-init)); the file format is shared with the WebDriver backend - mobile emulation viewport and user-agent settings Playwright-specific properties use the `playwright.` prefix: - `playwright.browserName`: optional override for the Playwright browser engine. - `playwright.deviceName`: optional Playwright device descriptor for the browser context. - `playwright.connectionMode`: `local`, `connect`, or `connectOverCDP`. - `playwright.endpoint`: WebSocket or CDP endpoint for remote Playwright sessions. - `playwright.channel`: optional Chromium channel. - `playwright.slowMo`: Playwright slow motion in milliseconds. - `playwright.launchTimeoutMilliseconds` - `playwright.defaultTimeoutMilliseconds` - `playwright.navigationTimeoutMilliseconds` - `playwright.artifactsDirectory` - `playwright.downloadsDirectory` - `playwright.acceptDownloads` - `playwright.tracing.enabled` - `playwright.tracing.onRetryOnly` - `playwright.tracing.screenshots` - `playwright.tracing.snapshots` - `playwright.tracing.sources` `playwright.deviceName` accepts names from Microsoft Playwright's device descriptor registry. SHAFT reads that registry when creating the context and also provides current-device aliases for `Galaxy S26 Ultra` and `iPhone 17 Pro Max`. When `playwright.browserName` is empty, SHAFT uses the descriptor's default browser type; an explicit `playwright.browserName` still wins. For reviewed local browser ownership, configure `infrastructure.profile` as `PLAYWRIGHT` and use the [managed Playwright browser setup flow](/docs/start/local-infrastructure/services#install-managed-playwright-browsers). SHAFT requires a compatible receipt before local driver creation. Remote connections, explicit browser channels, and an existing `PLAYWRIGHT_BROWSERS_PATH` keep their caller-owned precedence. ```properties title="src/main/resources/properties/custom.properties" targetBrowserName=chrome headlessExecution=true baseURL=https://example.com browserWindowWidth=1280 browserWindowHeight=720 playwright.browserName=chromium playwright.deviceName= playwright.connectionMode=local playwright.endpoint= playwright.channel= playwright.slowMo=0 playwright.launchTimeoutMilliseconds=30000 playwright.defaultTimeoutMilliseconds=30000 playwright.navigationTimeoutMilliseconds=30000 playwright.artifactsDirectory=target/playwright-artifacts playwright.downloadsDirectory= playwright.acceptDownloads=true playwright.tracing.enabled=false playwright.tracing.onRetryOnly=true playwright.tracing.screenshots=true playwright.tracing.snapshots=true playwright.tracing.sources=true ``` The same settings are available programmatically: ```java title="PlaywrightConfig.java" SHAFT.Properties.playwright.set() .browserName("chromium") .connectionMode("local") .launchTimeoutMilliseconds(30000) .defaultTimeoutMilliseconds(30000) .navigationTimeoutMilliseconds(30000) .artifactsDirectory("target/playwright-artifacts") .acceptDownloads(true) .tracingOnRetryOnly(true); ``` Remote sessions use `playwright.connectionMode` and `playwright.endpoint`: ```properties title="src/main/resources/properties/custom.properties" playwright.connectionMode=connect playwright.endpoint=ws://localhost:3000 ``` Tracing is disabled by default. When SHAFT retry evidence capture is enabled, `playwright.tracing.onRetryOnly=true` enables tracing for the retry attempt and attaches the resulting Playwright trace zip to the SHAFT report. ## Native Access ```java title="NativePlaywright.java" SHAFT.GUI.Playwright driver = new SHAFT.GUI.Playwright(); com.microsoft.playwright.Page page = driver.getDriver(); com.microsoft.playwright.BrowserContext context = driver.getNativeContext(); com.microsoft.playwright.Playwright playwright = driver.getPlaywright(); ``` Element actions accept SHAFT locators and concrete Playwright actions also accept native `com.microsoft.playwright.Locator` objects. ```java title="NativeLocator.java" SHAFT.GUI.Playwright driver = new SHAFT.GUI.Playwright(); var saveButton = driver.getDriver().getByRole( com.microsoft.playwright.options.AriaRole.BUTTON, new com.microsoft.playwright.Page.GetByRoleOptions().setName("Save")); driver.element().click(saveButton); driver.assertThat().element(saveButton).isVisible(); ``` The SHAFT locator builder keeps `build()` for Selenium and adds Playwright support: ```java title="PortableLocator.java" var shaftLocator = SHAFT.GUI.Locator.hasTagName("button").hasText("Save").buildPortable(); var playwrightLocator = SHAFT.GUI.Locator.hasTagName("button").hasText("Save") .buildForPlaywright(driver.getDriver()); ``` ## Mapping Tree Legend: - `Supported`: implemented for Playwright. - `Native`: available through a native Playwright overload or native context. - `WebDriver-only`: intentionally remains on Selenium/Appium because the feature depends on Selenium, Appium, CDP wrappers, or Lighthouse. ### Driver | WebDriver entry point | Playwright mapping | | --- | --- | | `new SHAFT.GUI.WebDriver()` | `new SHAFT.GUI.Playwright()` | | `quit()` | Supported; closes page, context, browser, Playwright runtime, and trace | | `browser()` | Supported: `com.shaft.gui.playwright.browser.BrowserActions` | | `element()` | Supported: `com.shaft.gui.playwright.element.ElementActions` | | `alert()` | Supported through Playwright dialog event bridge | | `assertThat()` / `verifyThat()` | Supported through SHAFT validations | | `getDriver()` | Supported; returns native `Page` | | WebDriver native object | `getNativeContext()` returns `BrowserContext`; `getPlaywright()` returns runtime | | `touch()` | WebDriver-only/Appium-only | | `async()` | WebDriver-only | | `act(...)` natural GUI actions | Supported through the trust-gated natural-action planner; Appium touch intents still return an unsupported touch-action reason | ### Browser Actions | WebDriver browser action | Playwright mapping | | --- | --- | | `navigateToURL(url)` | Supported: `Page.navigate` | | `navigateToURL(url, WindowType)` | Supported: new Playwright page | | `navigateToURL(url, expectedUrl)` | Supported: navigate then wait for URL | | `navigateToURLWithBasicAuthentication(...)` | Supported by embedding credentials in URL | | `navigateBack()` / `navigateForward()` | Supported | | `refreshCurrentPage()` | Supported | | `closeCurrentWindow()` | Supported; closes current page | | `maximizeWindow()` / `setWindowSize(w,h)` / `fullScreenWindow()` | Supported as viewport size | | `getCurrentURL()` / `getCurrentWindowTitle()` / `getPageSource()` | Supported | | `getWindowHandle()` / `getWindowHandles()` | Supported as page URL handles | | `switchToWindow(nameOrHandle)` | Supported by URL, title, or index | | `getWindowPosition()` | Supported as fixed `0,0` viewport origin | | `getWindowSize()` / `getWindowWidth()` / `getWindowHeight()` | Supported from viewport | | `addCookie()` / `getCookie()` / `getAllCookies()` / cookie getters | Supported through `BrowserContext.cookies()` | | `deleteCookie()` / `deleteAllCookies()` | Supported | | `saveStorageState(path)` / `loadStorageState(path)` | Supported; same JSON schema as WebDriver, files are interchangeable between backends | | `captureScreenshot()` / `captureScreenshot(type)` | Supported; attached to SHAFT report | | `capturePageSnapshot()` / `captureSnapshot()` | Supported as HTML attachment | | `waitForLazyLoading()` | Playwright load-state wait (not the Selenium navigation DOM-quiet window) | | `getContext()` / `setContext()` / `getContextHandles()` | Supported for the Playwright page context | | `mock()` / `intercept()` / `interceptRequest()` / `clearNetworkInterceptors()` / contract recording and replay | Supported through Playwright `BrowserContext` routing while preserving SHAFT's Selenium HTTP request/response contract | | `routeFromHar(harPath)` | Supported; same HAR 1.2 replay behavior as WebDriver | | `generateLightHouseReport()` | WebDriver-only | | `accessibility()` | Supported through bundled axe-core injection into the active Playwright page | ### Element Actions | WebDriver element action | Playwright mapping | | --- | --- | | `click()` | Supported | | `clickUsingJavascript()` | Supported with element JS evaluation | | `hover()` / `hoverAndClick()` | Supported | | `doubleClick()` | Supported | | `clickAndHold()` | Supported with Playwright mouse | | `dragAndDrop()` / `dragAndDropByOffset()` | Supported | | `type()` / `typeAppend()` / `typeSecure()` / `clear()` | Supported | | `typeFileLocationForUpload()` / file drop upload | Supported for input file locators | | `select()` | Supported | | `setValueUsingJavaScript()` / `submitFormUsingJavaScript()` | Supported | | `scrollToElement()` | Supported | | `captureScreenshot()` | Supported and attached to report | | `getElementsCount()` | Supported | | `getTableRowsData()` | Supported | | `switchToIframe()` / `switchToDefaultContent()` / `getCurrentFrame()` | WebDriver-style frame switching is WebDriver-only; use native Playwright frame locators | | `executeNativeMobileCommand()` | WebDriver-only/Appium-only | | Clipboard helpers | WebDriver-only in the first Playwright backend | ### Assertions And Verifications Playwright-backed browser and element assertions use Playwright's native auto-waiting assertion engine where available, then report through SHAFT validations so checkpoints, validation screenshots, and WebDriver-style validation metadata are preserved. | WebDriver assertion surface | Playwright mapping | | --- | --- | | Driver `assertThat().browser()` / `verifyThat().browser()` | Supported through SHAFT validations | | Browser `url()` / `title()` / `text()` / `attribute(name)` | Supported | | Driver `assertThat().element(locator)` / `verifyThat().element(locator)` | Supported for SHAFT locators and native Playwright locators | | Element `exists()` / `doesNotExist()` | Supported | | Element `attribute()` / `domAttribute()` / `domProperty()` / `property()` | Supported | | Element `text()` / `textTrimmed()` / `cssProperty()` | Supported | | Element state checks: selected, checked, visible, enabled, hidden, disabled | Supported | | Element visual reference assertions | Supported with `shaft-visual`; captures `Locator.screenshot()` bytes | | Standalone object/file/number/API assertions | Unchanged | Playwright visual reference assertions use the same fluent element API: ```java title="PlaywrightVisualAssertion.java" driver.assertThat().element(By.id("logo")).matchesReferenceImage(); driver.verifyThat().element(By.id("logo")) .doesNotMatchReferenceImage(ValidationEnums.VisualValidationEngine.EXACT_OPENCV); ``` The no-argument Playwright overload uses OpenCV because Selenium Shutterbug requires a WebDriver session. Explicit OpenCV and Applitools Eyes engines compare the Playwright screenshot bytes through `shaft-visual`; explicit Shutterbug requests fall back to OpenCV for Playwright. ### Accessibility And Network Parity Playwright browser actions expose the same network mocking and validation entry points as WebDriver: ```java title="PlaywrightNetwork.java" driver.browser() .mock(request -> request.getUri().contains("/inventory"), response) .and() .clearNetworkInterceptors(); ``` `interceptRequest()` supports response assertions/verifications through the existing SHAFT validation callbacks. Routes are scoped to the active Playwright browser context and can be cleared with `clearNetworkInterceptors()`. Contract replay uses the same route owner, so Playwright-backed tests can record or validate live browser traffic and replay captured responses: ```java title="PlaywrightContractReplay.java" driver.browser().startContractRecording( "src/test/resources/contracts/inventory.json", "/inventory"); driver.browser().navigateToURL("https://example.com/inventory"); SHAFT.Contracts.stopRecording(); driver.browser().replayContract("src/test/resources/contracts/inventory.json"); ``` Accessibility scans run axe-core in the active Playwright page and attach the same SHAFT accessibility artifacts used by WebDriver checks: ```java title="PlaywrightAccessibility.java" driver.browser() .accessibility() .setPageName("checkout") .assertNoCriticalViolations() .backToBrowserContract() .captureScreenshot(); ``` ### Performance Budgets API performance reports now include p50, p90, p95, and p99 endpoint latency columns plus a JSON export beside the HTML report. API endpoint budgets use the normalized endpoint names shown in the report: ```properties title="src/main/resources/properties/custom.properties" apiEndpointPerformanceBudgets=users=500,orders/{id}=750 failOnApiPerformanceBudgetViolation=true ``` Playwright browser action timings are recorded for browser actions, element actions, and Playwright-backed validations when performance reporting or browser budgets are enabled. Page-load timings are recorded for Playwright navigation and load-state waits. ```properties title="src/main/resources/properties/custom.properties" browserActionPerformanceBudgets=playwright.element.click=750,playwright.validation.elementVisible=1000 pageLoadPerformanceBudgets=https://example.com/checkout=3000,*=5000 failOnBrowserPerformanceBudgetViolation=false ``` The browser budget report is written as `BrowserPerformanceReport_*.html` and `BrowserPerformanceReport_*.json` in `PerformanceReportFolderPath`. Budget metrics use p95. Set the fail flag to `true` when CI should fail on violations; leave it `false` to report warnings only. ## Related - [Web testing](/docs/testing/web) - [Browser Actions](/docs/reference/actions/GUI/Browser_Actions) - [Element Actions](/docs/reference/actions/GUI/Element_Actions) - [Element validations](/docs/reference/actions/Validations#element-validations) - [SHAFT Locator Builder](/docs/reference/actions/GUI/Locators_And_Self_Healing#shaft-locator-builder) --- # Programmatic Properties Configuration Canonical HTML: https://shafthq.github.io/docs/reference/properties/Programmatic_Config Markdown: https://shafthq.github.io/md/reference/properties/Programmatic_Config.md SHAFT Engine properties can be configured **programmatically in code** instead of (or in addition to) property files. Programmatic configuration is **thread-safe**, takes effect immediately, and overrides any values set in property files or system properties. :::info **Precedence order**: Programmatic (`SHAFT.Properties.x.set()`) > Property files (`.properties`) > System properties (`-Dproperty=value`) > Built-in defaults. ::: --- ## Web / Browser Properties ```java title="ProgrammaticConfig.java" // Configure browser and web properties SHAFT.Properties.web.set() .targetBrowserName("chrome") .headlessExecution(true) .baseURL("https://staging.example.com"); ``` --- ## Timeout Properties ```java title="ProgrammaticConfig.java" // Configure timeouts (values in seconds) SHAFT.Properties.timeouts.set() .defaultElementIdentificationTimeout(30) .waitForUiStateTimeout(600) .browserNavigationTimeout(60); ``` --- ## Visual Reporting Properties ```java title="ProgrammaticConfig.java" // Configure screenshots and video recording SHAFT.Properties.visuals.set() .screenshotParamsWhenToTakeAScreenshot("Always") .videoParamsRecordVideo(true); ``` --- ## Behaviour Flags ```java title="ProgrammaticConfig.java" // Configure test behaviour flags SHAFT.Properties.flags.set() .retryMaximumNumberOfAttempts(2) .autoMaximizeBrowserWindow(true) .automaticallyAssertResponseStatusCode(true); ``` `retryMaximumNumberOfAttempts(2)` allows two retry attempts after the first failure. In JUnit, each retry is launched after `@AfterEach` cleanup and gets a fresh `@BeforeEach` setup. --- ## Reading Current Values Access the current value of any property by calling the getter **without** `.set()`: ```java title="ProgrammaticConfig.java" // Read current property values String browser = SHAFT.Properties.web.targetBrowserName(); int timeout = SHAFT.Properties.timeouts.defaultElementIdentificationTimeout(); boolean isHeadless = SHAFT.Properties.web.headlessExecution(); ``` --- ## Local Infrastructure Properties Configure setup ownership and policy before calling `SHAFT.Infrastructure`: ```java title="ProgrammaticConfig.java" SHAFT.Properties.infrastructure.set() .profile(SetupProfile.REPORTING) .mode(SetupMode.MANAGED) .offline(false) .autoStart(false) .startupTimeout("PT2M") .shutdownTimeout("PT30S"); ``` The default `EXTERNAL` mode performs diagnosis without local mutation. Read the [local infrastructure setup guide](/docs/start/local-infrastructure) before approving a managed plan. --- ## Complete Example ```java title="ProgrammaticConfigTest.java" public class ProgrammaticConfigTest { private SHAFT.GUI.WebDriver driver; @BeforeMethod public void setup() { // Configure all properties before creating the driver SHAFT.Properties.web.set() .targetBrowserName("chrome") .headlessExecution(true) .baseURL("https://staging.example.com"); SHAFT.Properties.timeouts.set() .defaultElementIdentificationTimeout(30) .waitForUiStateTimeout(600) .browserNavigationTimeout(60); SHAFT.Properties.visuals.set() .screenshotParamsWhenToTakeAScreenshot("Always") .videoParamsRecordVideo(true); SHAFT.Properties.flags.set() .retryMaximumNumberOfAttempts(2) .autoMaximizeBrowserWindow(true); driver = new SHAFT.GUI.WebDriver(); } @Test public void navigateAndVerify() { driver.browser().navigateToURL("/login"); driver.element().type(By.id("email"), "test@example.com"); driver.assertThat(By.id("email")).attribute("value").isEqualTo("test@example.com"); } @AfterMethod public void teardown() { driver.quit(); } } ``` --- ## Dynamic Configuration Use Case Programmatic configuration is ideal for parameterized test runs where the target environment is determined at runtime: ```java title="DynamicConfigExample.java" String targetEnv = System.getenv("TARGET_ENV"); // "staging" or "production" String baseUrl = "staging".equals(targetEnv) ? "https://staging.example.com" : "https://example.com"; SHAFT.Properties.web.set().baseURL(baseUrl); ``` --- :::tip Set all properties in `@BeforeMethod` (or `@BeforeClass` for class-scoped drivers) **before** creating the `SHAFT.GUI.WebDriver` instance. Properties configured after driver creation only apply to subsequent driver instances. ::: :::warning Avoid setting properties in parallel test methods without careful thread-isolation — use `@BeforeMethod` (which is per-thread in TestNG) to keep configuration thread-safe. ::: ## Related - [Property Types](/docs/reference/properties/PropertyTypes) - [Properties List](/docs/reference/properties/PropertiesList) - [Common Examples](/docs/reference/properties/CommonExamples) - [Set up local infrastructure](/docs/start/local-infrastructure) --- # Property Types Canonical HTML: https://shafthq.github.io/docs/reference/properties/PropertyTypes Markdown: https://shafthq.github.io/md/reference/properties/PropertyTypes.md ## Priority hierarchy Since there are many ways to configure SHAFT properties you need to know how the priorities work. Simply put, the values to the left override the values to the right. [Code-based](#code-based) > [CLI-based](#cli-based) > [File-based](#file-based) > [Default Values](PropertiesList) ## File-based The traditional way of configuring SHAFT properties, using this approach you can simply create your own `custom.properties` file. :::tip You should use this approach if you have some properties that are global and not expected to change during execution, or properties that you wish to later override from your CLI execution (CI/CD server). ::: Here's what it can look like: **Example**: Setting up properties for basic web browser automation ```properties showLineNumbers title="src/main/resources/properties/custom.properties" baseURL=http://www.mytestdomain.com executionAddress=local targetOperatingSystem=WINDOWS targetBrowserName=firefox headlessExecution=true evidenceLevel=CUSTOM createAnimatedGif=true videoParams_recordVideo=true ``` You can add all your custom properties in one or more files as you see fit. For more info you can refer to our [full list of supported properties](PropertiesList). ## Code-based You can read/write any property programmatically to provide more flexibility and control during runtime. :::tip You should use this approach if you have some properties that are specific for a certain test class/method or to a specific part of your test. For global properties it is recommended to use the [File-based approach](#file-based). ::: **Example**: Setting up properties for basic mobile automation ```java showLineNumbers title="src/test/java/testPackage/TestClass.java" private SHAFT.GUI.WebDriver driver; @BeforeMethod public void setup(){ SHAFT.Properties.platform.set().targetPlatform(Platform.ANDROID.name()); SHAFT.Properties.mobile.set().automationName(AutomationName.ANDROID_UIAUTOMATOR2); SHAFT.Properties.platform.set().executionAddress("localhost:4723"); SHAFT.Properties.mobile.set().app("src/test/resources/testDataFiles/apps/ApiDemos-debug.apk"); driver = new SHAFT.GUI.WebDriver(); } ``` **Example**: Reading values used for BrowserStack integration ```java showLineNumbers title="src/test/java/testPackage/TestClass.java" @BeforeClass public void beforeClass(){ var username = SHAFT.Properties.browserStack.userName(); var accessKey = SHAFT.Properties.browserStack.accessKey(); var platformVersion = SHAFT.Properties.browserStack.platformVersion(); var deviceName = SHAFT.Properties.browserStack.deviceName(); var appUrl = SHAFT.Properties.browserStack.appUrl(); var customID = SHAFT.Properties.browserStack.customID(); var appName = SHAFT.Properties.browserStack.appName(); } ``` :::note Note that per the above examples we prefer to use [AutomationName](https://appium.github.io/java-client/io/appium/java_client/remote/AutomationName.html) and [Platform](https://www.selenium.dev/selenium/docs/api/java/org/openqa/selenium/Platform.html) to ensure setting the relevant values correctly. You can also use [Browser](https://www.selenium.dev/selenium/docs/api/java/org/openqa/selenium/remote/Browser.html) to set the `targetBrowserName` property programmatically. ```java showLineNumbers title="src/test/java/testPackage/TestClass.java" SHAFT.Properties.web.set().targetBrowserName(Browser.FIREFOX.browserName()); ``` ::: ## CLI-based You can set some properties via the CLI command that you use to execute your tests. :::tip You should use this approach if you want to override some specific properties for the current test run from CLI. This is usually relevant for unattended CI/CD pipeline execution. You would usually need to use the [File-based approach](#file-based) to set these properties first, and then override them via CLI. ::: Here's a sample command that you can execute from your terminal window to parameterize your test command: ```powershell mvn -e test "-DretryMaximumNumberOfAttempts=2" "-DexecutionAddress=localhost:4444" "-DtargetOperatingSystem=LINUX" "-DtargetBrowserName=firefox" "-DheadlessExecution=true" "-Dallure.generateArchive=true" "-Dtest=${GLOBAL_TESTING_SCOPE}" ``` :::note Note that you can refer to [the official Maven Surefire guide](https://maven.apache.org/surefire/maven-surefire-plugin/examples/single-test.html) to learn how to run certain test classes or packages. ::: ## Related - [Properties List](/docs/reference/properties/PropertiesList) - [Common Examples](/docs/reference/properties/CommonExamples) - [Programmatic Config](/docs/reference/properties/Programmatic_Config) --- # Reporting Canonical HTML: https://shafthq.github.io/docs/reference/reporting Markdown: https://shafthq.github.io/md/reference/reporting.md SHAFT Engine ships with two complementary report formats, each suited to a different audience and workflow. You can enable or disable them independently using [properties](/docs/reference/properties/PropertyTypes). --- ## Report Types at a Glance | Report | Best For | Default State | |---|---|---| | **Allure Report** | Deep debugging — step-by-step logs, screenshots, videos | ✅ Opens automatically after execution | | **Execution Summary** | Quick supplemental feedback — fast, portable, no setup | ⚙️ Opt-in via property | SHAFT-generated HTML report attachments use a shared SHAFT report theme across execution summaries, performance reports, accessibility reports, locator health, failure briefs, failure traces, and flake profiles. The same theme gives each surface consistent headers, status chips, metric cards, table wrapping, and dark-mode behavior whether opened directly or from Allure. The SHAFT offline HTML theme and the injected Allure theme share one unified brand palette (light and dark modes) to ensure consistent SHAFT branding across both standalone reports and the Allure report. --- ## Allure Report The **Allure Report** is the primary report in SHAFT. It opens automatically in your default browser at the end of every test run and provides a rich, single-file HTML report with: - Step-by-step action logs with failure screenshots attached by default - Video recordings (when enabled) - Animated GIFs of browser sessions - Test history and trend graphs across multiple runs - Environment information and categories ### Source code attachments Set `whenToAttachSourceCode` to attach the Java call site directly to an action or validation step. The default is `Never`. Each enabled attachment shows the relative source path, method, and up to five numbered lines: the executed line plus two lines above and below. An arrow and a highlighted background identify the active line. The surrounding lines provide context; they are not marked as executed. Choose one configuration method: ```properties title="src/main/resources/properties/Reporting.properties" whenToAttachSourceCode=FailuresOnly ``` ```java SHAFT.Properties.reporting.set().whenToAttachSourceCode("FailuresOnly"); ``` | Value | Steps that receive an attachment | | --- | --- | | `Never` | None, including failures. This feature does not resolve source files. | | `FailuresOnly` | Failed or broken actions and validations. Earlier successful steps remain unchanged if the test later fails. | | `ValidationPointsOnly` | Successful and failed assertions/verifications, plus failed or broken actions. | | `Always` | Every eligible executed action and validation, whether it passes or fails. | Run the tests, open the Allure report, and expand an action or validation step. Select its **Source code** attachment to preview the HTML or download the file. The attachment contains selectable text and works without external assets. Skipped steps, internal logging, and attachment-helper steps do not receive source attachments. This setting is independent of `evidenceLevel`: you do not need `CUSTOM`, and profiles such as `FAST` or `FULL` do not override your choice. It is also separate from `shaft.trace.includeCodeContext`, which controls source evidence inside the failure trace. Setting `whenToAttachSourceCode=Never` does not remove code context from other enabled artifacts. Keep the matching Java source files on the test runner in standard Maven source roots. The call site can be in a test, page object, or helper. When source or line metadata is unavailable, the attachment shows the known location and an explanation instead of failing the test. Several calls on one line share that line number, and the fixed context window may not include an entire multiline statement. :::danger Source disclosure Enable this attachment only when the report may include your source code. SHAFT applies its existing source-secret redaction, but arbitrary hardcoded secrets in surrounding lines may not be detected. Review the code and report before sharing it. ::: ![Allure validation step showing five numbered Java source lines, with the executed line marked by an arrow and a yellow highlight.](/img/evidence/allure-source-code-attachment.png) *A passing validation with `whenToAttachSourceCode=Always`, rendered in Allure Report 3.22.1.* ### Failure trace viewer When a test fails, SHAFT attaches a failure trace viewer to Allure by default. The viewer collects the ordered Selenium action timeline, exception and stacktrace, source-code frame fallback, available page or native source snapshot, locator health summary, and related artifacts without generating heavy trace artifacts for passing tests. Allure receives two complementary SHAFT trace attachments on failed tests: - `shaft-trace.html`, a self-contained one-click viewer that embeds the trace data and references no sibling files - `shaft-trace.zip`, the full-fidelity offline archive containing `shaft-trace.json`, `SHAFT Trace Report.html`, `shaft-network.har`, per-action screenshots, and the native Playwright trace ZIP when Playwright tracing is enabled and available Open the direct HTML attachment for in-report debugging, or open `SHAFT Trace Report.html` from the archive for the downloadable offline workflow. Neither viewer needs an upload or sibling resource. The shared navigator combines two range controls, **Show all**, and an action screenshot filmstrip. Select an action from the filmstrip or action list to update the URL deep link and inspect the same action and time range across the detail panels. Use Arrow Left and Arrow Right while the filmstrip has focus to move between actions. Use **Timeline** to review actions, validations, network exchanges, and console messages in chronological order. Use **Comparison** to inspect the available before-action DOM, action screenshot, and after-action DOM together. The selected action also owns its exception, DOM snapshot, screenshot, and detail data such as locator, parameters, result, and metadata. Trace-level **Source**, **Snapshot**, **Locator Health**, **Observability**, **Environment**, **Attachments**, **Test Log**, and **JSON** panels remain available beside that selection. Missing screenshots or snapshots produce explicit empty states instead of fabricated evidence. Use **Network** and **Console** to filter, search, and sort the evidence inside the selected range. Open a row's native **View request details** or **View message details** button to read structured details. Untimed legacy entries remain visible as `Unknown` and are not classified as inside the selected range. Missing sortable values stay last in both sort directions. Use **Mobile** to filter Appium trace actions by **App**, **Context**, **Device**, **Logs**, **Performance**, **Recording**, or **Evidence**. The **All** category also keeps unrecognized `mobile/*` categories visible as **Other**, which preserves older and newer trace data without claiming support that SHAFT did not record. Use **Artifacts** to inspect the archive-relative path, kind, media type, and status for every schema artifact. The schema and `index.json` carry the same artifact references. Available entries and explicit omissions remain distinct; omission reasons distinguish unavailable, unreadable, and oversized native trace evidence, while traces with no artifact graph show a legacy empty state. When a native Playwright trace is available, extract its archive-relative ZIP entry and pass that entry to Playwright `show-trace`. SHAFT's cross-backend timeline remains the canonical view. ### Browser and provider fidelity Read parity as one investigation workflow, not identical browser internals. Every supported browser writes the same bounded SHAFT archive and opens in the same offline viewer. The evidence inside that archive depends on the active automation backend and the protocol features exposed by the browser or remote provider. | Route | Browser proof | Evidence in the SHAFT viewer | Expected degradation | |---|---|---|---| | Playwright local | Native-trace acceptance runs on Chromium, Firefox, and WebKit. Chrome and Edge channels use the Chromium engine and also run the Playwright action suite. | SHAFT actions plus imported native actions, action logs and errors, before/input/after snapshots, console and network evidence, and the original native trace ZIP when it fits the artifact budget. | Native action source is shown when the trace contains a stack sidecar. When it does not, the action records `sourceStatus: unavailable` and explains why. | | Selenium Chrome or Edge | WebDriver capture acceptance runs on Chromium; the regular browser suite also exercises Chrome and Edge. | SHAFT actions, screenshots, structural DOM snapshots, console, network, and WebSocket evidence exposed through CDP. | No Playwright-native action or trace ZIP is advertised. | | Selenium Firefox or another WebDriver provider | The regular browser suite exercises Firefox, while unit contracts bind BiDi and unsupported-driver states. | SHAFT actions, screenshots, structural DOM snapshots, plus console or network evidence when the session exposes the required BiDi capability. | Missing protocol features produce an explicit warning or an absent evidence namespace. They do not fall back to a different test session or claim native Playwright fidelity. | | Appium native or mobile web | Android and iOS evidence contracts exercise the mobile action namespaces and native source path. | Mobile actions, screenshots, bounded native source, device/context metadata, performance data, recording, and provider evidence when available. | Rejected, unreadable, unsupported, and oversized provider artifacts remain explicit omissions. | Enable native Playwright tracing before you create the driver. Select `chromium`, `firefox`, or `webkit` as the browser engine: ```properties title="src/main/resources/properties/custom.properties" playwright.browserName=firefox playwright.connectionMode=local playwright.tracing.enabled=true playwright.tracing.screenshots=true playwright.tracing.snapshots=true playwright.tracing.sources=true shaft.trace.enabled=true shaft.trace.mode=failure shaft.trace.includeFullPageSnapshots=true shaft.trace.includeNativePageSource=true shaft.trace.includeNetwork=true shaft.trace.includeConsole=true ``` To launch an installed branded Chromium browser, keep `playwright.browserName=chromium` and add exactly one channel: ```properties title="Chrome or Edge channel override" # Google Chrome playwright.channel=chrome # Microsoft Edge (use this instead of the chrome line) # playwright.channel=msedge ``` Run the same test with another engine by changing only `playwright.browserName` to `chromium` or `webkit`. Review the `environment.browser` value in `shaft-trace.json` before comparing archives. It records the active Playwright engine instead of the WebDriver default. Use the [Playwright browser guide](https://playwright.dev/java/docs/browsers) to install the matching browser binary. Use the [Playwright trace viewer guide](https://playwright.dev/java/docs/trace-viewer) only for the native ZIP handoff; keep `SHAFT Trace Report.html` as the portable cross-backend view. Selenium protocol evidence follows the browser's [WebDriver BiDi support](https://www.selenium.dev/documentation/webdriver/bidi/). The standalone viewer applies a restrictive content security policy, keeps DOM snapshots in sandboxed frames, strips active and resource-bearing markup, and does not make external requests. Captured evidence is bounded and redaction-aware, not guaranteed to be free of application data. :::danger Treat both `shaft-trace.html` and `shaft-trace.zip` as sensitive test evidence. The HTML embeds trace data, screenshots, and DOM snapshots. The ZIP can also contain native source, logs, request details, console messages, metadata, recording artifacts, and the native Playwright trace. Store and share either attachment only with people who may inspect the tested application data. ::: The archived `shaft-trace.json` includes an `actions` array with stable fields for automation agents. Each action also records `caller` — the first non-framework stack frame, tying the action back to the exact test or page-object line — and each `network` entry carries an epoch `timestamp` for timeline correlation: ```json { "id": "action-3", "category": "element", "name": "CLICK", "status": "failed", "startTime": "2026-06-26T10:13:13.000Z", "durationMs": 184, "locator": "By.id: pay", "url": "https://shop.example/checkout", "caller": "com.example.CheckoutTest.payShouldSucceed(CheckoutTest.java:42)", "message": "Click \"Pay\"", "exception": { "type": "org.openqa.selenium.ElementClickInterceptedException", "message": "element click intercepted" }, "attachments": ["screenshot - Click (48213 bytes)"], "metadata": { "elementName": "Pay" }, "actionability": { "matchCount": 1, "displayed": true, "enabled": true, "rect": {"x": 42, "y": 318, "width": 120, "height": 36}, "centerPoint": {"x": 102, "y": 336}, "css": {"display": "block", "visibility": "visible", "pointerEvents": "auto"}, "obscuringElement": {"selector": "div.modal-backdrop"}, "recommendation": "Wait for or close the blocking element before retrying the action." } } ``` For failed Selenium element actions, the optional `actionability` object explains why the target was not actionable. It includes locator match count, element state, rectangle and center point, viewport/CSS details, any center-point blocking element found with `document.elementFromPoint`, warnings from best-effort diagnostics, and a deterministic recommendation. SHAFT collects this only for failed traced actions and applies the same redaction rules used by the trace JSON. ### SHAFT Overview Report SHAFT attaches a branded **SHAFT Overview** suite summary to the Allure report. The overview displays: - **Checkpoint totals** — count of total, passed, and failed checkpoints across the run - **By-type breakdown** — a per-type table splitting **Assertion** (hard) vs **Verification** (soft), each with its own passed / failed / total - **Pass ratio donut** — visual pass-rate indicator - **Traces captured** — count of SHAFT traces under `target/shaft-traces/` (retry-aware: counts each failed attempt when retries are configured) - **Fail-only filter** — a "Show failures only" toggle on the checkpoint details table that hides passing rows The overview provides a quick snapshot of test outcomes and captured diagnostics alongside Allure's native statistics. Alongside the HTML overview, SHAFT attaches a machine-readable **`Checkpoints`** JSON artifact (`application/json`) so tooling — SHAFT Doctor, the MCP server, and the IntelliJ plugin widgets — can consume structured checkpoint fields instead of scraping the HTML. It carries the same by-type breakdown the overview shows: ```json { "total": 4, "passed": 1, "failed": 3, "byType": { "assertion": { "passed": 1, "failed": 0 }, "verification": { "passed": 0, "failed": 3 } }, "checkpoints": [ { "id": 1, "type": "ASSERTION", "message": "...", "status": "PASS" }, { "id": 2, "type": "VERIFICATION", "message": "...", "status": "FAIL" } ] } ``` The same overview also renders as an **embedded panel** inside the generated Allure report itself: a floating **SHAFT Overview** toggle button (bottom-right of the report page) opens a modal with the identical Summary, By Type, and Details view described above, without leaving the report: ![SHAFT Overview panel embedded in the Allure report](/img/allure-shaft-overview-panel.png) Both the standalone attachment and the embedded panel render from the same underlying data, so they always agree. The attachment stays useful as an individually downloadable artifact for tooling that consumes Allure attachments directly; the panel gives anyone viewing the report quick access to the same summary without downloading anything. ### Assertion and Verification Evidence Cards Every hard assertion (Assertion) and soft assertion (Verification) automatically attaches a self-contained HTML evidence card as the **first attachment** on the validation's Allure report step. The card displays: - **Category and status** — "Assertion" or "Verification" label with PASSED/FAILED badge. - **Data-shape badge** — JSON, Text, or Value, derived from the compared values. - **Expected and Actual values with diffs:** - JSON values are pretty-printed with a line-by-line diff. - Multi-line or long text gets a unified line-diff view. - Short scalar values show compact Expected/Actual rows side-by-side. - **Automatic redaction** — passwords, tokens, Authorization headers, and secret-looking JSON/attribute values are masked to `********` before rendering. - **Dark-mode aware** — the card matches the SHAFT report theme. - **Size-capped** — very large values are truncated with a visible marker. The legacy plain-text "Expected Value" and "Actual Value" attachments remain unchanged for backward compatibility. Element-state and visual (image-comparison) validations do not receive the text card — their evidence is the screenshot or image diff attached separately. ### Soft Verification Visibility Soft assertions (`verifyThat`, reported as **Verification**) let a test keep running after a failure and only fail at the end. To make them easy to follow in the report: - Each soft failure's outcome step is titled **`Soft failure #N — ...`** with a running count, so soft failures are easy to tally at a glance and are visibly distinct from a hard assertion's single failure step. - When the test is force-failed at the end, a **Soft verification summary** step lists every accumulated verification failure in the order they occurred, giving one place to review them all instead of scanning the step list. For failed Appium touch actions, the same `actions` array includes mobile session metadata when the driver exposes it: ```json { "category": "touch", "name": "swipeElementIntoView", "status": "failed", "locator": "By.id: checkout_list", "metadata": { "gestureParameters": "targetText=Pay now, movement=VERTICAL", "platformName": "Android", "automationName": "UiAutomator2", "appPackage": "com.example.checkout", "appActivity": ".CheckoutActivity", "context": "NATIVE_APP", "orientation": "PORTRAIT", "windowSize": "1080x1920", "nativePageSourceExcerpt": " ..." } } ``` Context changes from `driver.browser().setContext(...)` and MCP `mobile_switch_context` are also recorded as `mobile-context` trace events with `contextBefore`, `contextAfter`, and `requestedContext`. Unsupported Appium metadata fields use deterministic fallback text instead of surfacing raw provider errors in the trace. When `shaft.trace.includeNetwork=true`, Selenium browser sessions also add redacted `network` entries to `shaft-trace.json` and a HAR-like `shaft-network.har` file to the archive. The **Network**, **Console**, and **Observability** panels let you inspect HTTP method, URL, status, duration, bounded body preview, browser console logs, and unsupported-driver warnings. Common headers, cookies, passwords, tokens, and sensitive URL values are masked before attachment. SHAFT also persists the archive and a local index under `target/shaft-traces/ /`: - `shaft-trace.zip` - `index.json` The default `shaft.trace.mode=auto` is retry-aware: with retries configured, every failed attempt keeps a trace automatically; without retries, it behaves as `failure`. Set `failure`, `retry`, or `always` explicitly to override the default behavior. ```properties title="src/main/resources/properties/custom.properties" shaft.trace.enabled=true shaft.trace.mode=auto shaft.trace.includeCodeContext=true shaft.trace.includeFullPageSnapshots=true shaft.trace.includeNativePageSource=true shaft.trace.includeNetwork=true shaft.trace.includeConsole=true shaft.trace.maxArtifactMb=50 ``` When retries are enabled and `shaft.trace.retainFailedAttempts=true` (the default), each failed attempt's trace is persisted as `shaft-trace-attempt- .zip` alongside the final `shaft-trace.zip`, so passing retries never erase the failed attempt's evidence. In CI, worst-case artifact size scales roughly with the number of failed attempts multiplied by `shaft.trace.maxArtifactMb`. ### Failure diagnostics bundle Failed and broken tests attach `shaft-diagnostics.zip` when `shaft.diagnostics.enabled=true` (default). The zip contains `diagnostics.json` schema version **2**: - **correlation** — `runId` (test id), `bundleId` (hash of stable redacted fields), optional `captureSessionId` when a Capture session JSON was already attached - **provenance** — writer adapter `shaft-engine-failure-diagnostics` - **channels** — `framework`, `browser`, `network`, `console`, `screenshot`, `agent-action`, each `present` or `omitted` with a reason - **omitted** — explicit list (`budget`, `partial-capture`, `unsupported-browser`, `secret-bearing`, `success-skip`, `retention`) so missing evidence is never treated as complete - **cluster.fingerprint** — stable hash of exception type, top project frame, and redacted message shape (no timestamps or host paths). Doctor uses this fingerprint to cluster recurring failures without model judgment - existing v1 fields (`test`, `failure`, `codeContext`, `logs`, `artifacts`, `redaction`) stay so older parsers still work Secrets in headers, cookies, tokens, and password-like assignments are redacted before the zip is written. Heavy artifacts stay references; they are not dumped as raw HAR or DOM. Successful tests do not attach the zip. Open the zip from Allure or pass `allure-results` to `shaft-doctor analyze` / `doctor_analyze_failed_allure` — no transcript required. ```properties title="src/main/resources/properties/custom.properties" shaft.diagnostics.enabled=true shaft.diagnostics.maxArtifactMb=50 ``` ### Evidence Level Profiles `evidenceLevel` is the profile-level evidence switch. Its default is `FAILURE_ONLY`, so passing tests keep reports small while failed and broken tests still get failure screenshots, page source, diagnostics, and trace artifacts. Profiles are applied after file, CLI, and default granular evidence properties are loaded. The code-based setter also applies the selected profile immediately. For `FAILURE_ONLY`, `BALANCED`, `FAST`, and `FULL`, the profile overrides granular controls such as `screenshotParams_whenToTakeAScreenshot`, `whenToTakePageSourceSnapshot`, `createAnimatedGif`, `videoParams_recordVideo`, `captureWebDriverLogs`, `attachFullLog`, `shaft.diagnostics.enabled`, and `shaft.trace.mode`. Set `evidenceLevel=CUSTOM` when you want those granular controls to win. In code, call `evidenceLevel(...)` after granular setters when you want a profile to win; call `evidenceLevel("CUSTOM")` before granular setters when you want custom values to win. | Level | Effect | |---|---| | `FAILURE_ONLY` | Default. Screenshots and page source only on failures; GIF, video, WebDriver logs, and full log off; diagnostics and trace enabled for failures. | | `BALANCED` | Validation screenshots plus failure page source, diagnostics, and trace; GIF, video, WebDriver logs, and full log off. | | `FAST` | Minimal evidence: screenshots, page source, GIF, video, WebDriver logs, full log, diagnostics, and trace off. | | `FULL` | Rich evidence: screenshots and page source always, GIF and video on, WebDriver logs and full log on, diagnostics on, and trace mode `always`. | | `CUSTOM` | No profile override. Use this before setting granular evidence controls directly. | ### Allure Categories Allure 3 runs receive a `categories.json` file in `allure-results/` with failure groupings. SHAFT provides default categories for common assertion, locator, timeout, API, accessibility, visual, and infrastructure failures, plus three SHAFT-native outcome categories: - **SHAFT: flaky (passed on retry)** — test passed after one or more retry attempts - **SHAFT: self-healed locator** — test passed when SHAFT Heal recovered a broken locator - **SHAFT: soft verification failure** — soft assertion (verification) failed but test continued These categories are plain Allure metadata and do not require a custom Allure plugin. They help you distinguish SHAFT-specific recovery outcomes from conventional failures in the Allure Categories tab. ```properties title="src/main/resources/properties/custom.properties" evidenceLevel=FAILURE_ONLY ``` ```bash mvn test -DevidenceLevel=FAST ``` ```java SHAFT.Properties.reporting.set().evidenceLevel("FULL"); ``` ### Open Allure Report Manually SHAFT writes convenience scripts to your project root during the first run. Use them to regenerate and serve the Allure report at any time: ```bash title="Windows" generate_allure_report.bat ``` ```bash title="macOS / Linux" ./generate_allure_report.sh ``` ### Disable Auto-Opening the Allure Report The Allure report opens automatically by default (`allure.automaticallyOpen=true`). To disable this: ```properties title="src/main/resources/properties/custom.properties" allure.automaticallyOpen=false ``` ```bash mvn test -Dallure.automaticallyOpen=false ``` ```java SHAFT.Properties.allure.set().automaticallyOpen(false); ``` ### Skip Allure Report Generation If you only need raw `allure-results` and do not want SHAFT to create the HTML report, helper script, archive, or live watch process, disable report generation: ```properties title="src/main/resources/properties/custom.properties" allure.generateReport=false ``` ```bash mvn test -DheadlessExecution=true -Dallure.generateReport=false ``` ```java SHAFT.Properties.allure.set().generateReport(false); ``` ### Generate an Allure Report Archive (for CI/CD) To generate a portable ZIP archive of the Allure report that you can publish as a CI/CD artifact: ```properties title="src/main/resources/properties/custom.properties" allure.generateArchive=true ``` ```bash mvn test -Dallure.generateArchive=true ``` ```java SHAFT.Properties.allure.set().generateArchive(true); ``` ### Customize the Allure Report You can customize the report title and logo displayed in the generated HTML: ```properties title="src/main/resources/properties/custom.properties" allure.customTitle=My Regression Suite allure.customLogo=https://example.com/my-logo.png ``` ```java SHAFT.Properties.allure.set() .customTitle("My Regression Suite") .customLogo("https://example.com/my-logo.png"); ``` ### Tune Allure 3 Awesome Report Output When SHAFT generates an Allure 3 report, it writes the corresponding `allurerc.yaml` file for the Allure Awesome plugin. In addition to the title, output directory, logo, and history settings, you can control the Awesome plugin options that affect packaging, theme mode, language, browser opening, and the hierarchy shown in the report tree. SHAFT-generated Allure 3 reports use the same light and dark color tokens as this user guide, and keep the SHAFT mark plus the User Guide and GitHub actions available while you move between report routes. Allure 2 reports keep their existing UI. :::note These options apply to Allure 3 report generation. `allure.automaticallyOpen` controls whether SHAFT opens the final report after test execution, while `allure.open` is passed to the Allure CLI configuration itself. ::: ```properties title="src/main/resources/properties/custom.properties" # Generate a self-contained HTML report file (default: true) allure.singleFile=true # Allure 3 color mode (default: auto) allure.theme=auto # Allure UI language code (default: en) allure.reportLanguage=en # Let the Allure CLI open the report after generation (default: false) allure.open=false # Comma-separated Allure label hierarchy (default: package,testClass) allure.groupBy=package,testClass ``` ```bash mvn test \ -Dallure.singleFile=false \ -Dallure.theme=auto \ -Dallure.reportLanguage=fr \ -Dallure.open=false \ -Dallure.groupBy=package,testClass ``` ```java SHAFT.Properties.allure.set() .singleFile(false) .theme("auto") .reportLanguage("fr") .open(false) .groupBy("package,testClass"); ``` ### Monitor Allure 3 Results While Tests Run When `allure.realtimeMonitoring=true` and SHAFT resolves an Allure 3 CLI, SHAFT starts native `allure watch` against `allure-results` while tests run. The watch process is stopped before final report generation and archiving. This is disabled by default, and is ignored when Allure 2 compatibility mode is active or the Allure CLI cannot be resolved. ```properties title="src/main/resources/properties/custom.properties" allure.realtimeMonitoring=true ``` ```bash mvn test -Dallure.realtimeMonitoring=true ``` ```java SHAFT.Properties.allure.set().realtimeMonitoring(true); ``` Common `allure.groupBy` presets include: | Preset | Value | Best For | |---|---|---| | Code hierarchy | `package,testClass` | Default Java package and class navigation | | Suite hierarchy | `parentSuite,suite,subSuite` | TestNG or suite-based organization | | BDD hierarchy | `epic,feature,story` | Business-readable Allure annotation reports | | Module hierarchy | `module,parentSuite,suite,subSuite` | Multi-module projects | Full-page image attachments open at the available modal width in SHAFT-generated Allure 3 reports. Use the modal scrollbar to inspect the rest of tall screenshots. HTML attachments created through SHAFT's attachment reporter are also fitted to the available Allure preview width. SHAFT injects lightweight containment rules so wide tables, preformatted blocks, and long values wrap instead of forcing a horizontal page scroll. ### Troubleshoot Allure CLI Generation SHAFT runs Allure report generation synchronously so the generated command, exit code, standard output, and standard error are visible in execution logs. If report generation fails, check the log entries around `Executing Allure report generation command`, `Allure generate stdout`, `Allure generate stderr`, and `Allure report generation command exited with code ...` before changing report settings. ### Accumulate History and Reports Across Runs By default, SHAFT keeps Allure history to enable trend graphs across runs. You can control this behavior: ```properties title="src/main/resources/properties/custom.properties" # Accumulate history to show trends across runs (default: true) allure.accumulateHistory=true # Keep previous HTML report files in the report directory (default: true) allure.accumulateReports=true # Clean the results directory before each run (default: true) allure.cleanResultsDirectory=true ``` ```java SHAFT.Properties.allure.set() .accumulateHistory(true) .accumulateReports(true) .cleanResultsDirectory(true); ``` --- ## Execution Summary Report The **Execution Summary** report is a lightweight supplement to the Allure report. It is disabled by default and is most useful when you want a quick, portable HTML summary without opening the full Allure report. - **No extra setup required** — just enable the property. - **Portable** — a fast self-contained HTML file you can share or archive. ### Enable the Execution Summary Report ```properties title="src/main/resources/properties/custom.properties" openExecutionSummaryReportAfterExecution=true ``` ```bash mvn test -DopenExecutionSummaryReportAfterExecution=true ``` ```java SHAFT.Properties.reporting.set().openExecutionSummaryReportAfterExecution(true); ``` --- ## Locator Health Report The **Locator Health Report** is disabled by default. Enable it when you want to find slow, flaky, ambiguous, stale, or healed web locators across a full run. SHAFT records lightweight metrics during element identification and writes HTML and JSON reports under `execution-summary/locator-health/`. The JSON export is attached to Allure, and the HTML dashboard is attached when `shaft.locatorHealth.attachDashboard=true`. Recorded metrics include lookup count, unique-match, no-match, multi-match, and stale rates, average and p95 lookup time, polling attempts, timeout count, slow lookups, SHAFT Heal attempts, accepted recoveries, selected replacement locator, and confidence when the provider exposes it. Each locator also gets a health score, selector-smell labels, and plain-language recommendations for risky patterns such as absolute XPath, index-heavy XPath, generated IDs, text-only selectors, and deep CSS chains. When the failure trace viewer is enabled, failed-test trace JSON also includes the current locator health snapshot. ```properties title="src/main/resources/properties/custom.properties" shaft.locatorHealth.enabled=true shaft.locatorHealth.warnBelowScore=70 shaft.locatorHealth.attachDashboard=true shaft.locatorHealth.failBelowScore=-1 slowLocatorThresholdMillis=750 ``` ```bash mvn test -Dshaft.locatorHealth.enabled=true -Dshaft.locatorHealth.warnBelowScore=70 -Dshaft.locatorHealth.failBelowScore=-1 -DslowLocatorThresholdMillis=750 ``` ```java SHAFT.Properties.reporting.set() .locatorHealthEnabled(true) .locatorHealthWarnBelowScore(70) .locatorHealthAttachDashboard(true) .locatorHealthFailBelowScore(-1) .slowLocatorThresholdMillis(750) .failOnLocatorHealthWarnings(false); ``` Keep `shaft.locatorHealth.failBelowScore=-1` while introducing the report. Set it to a score threshold only after the suite has a stable locator-health baseline. The legacy `locatorHealthReportEnabled=true` and `failOnLocatorHealthWarnings=false` keys remain supported. --- ## Flake and Auto-Wait Profiler The **Flake and Auto-Wait Profiler** is disabled by default. Enable it when you want Allure attachments that explain slow element actions, assertions, retries, wait polling, locator churn, and evidence overhead. SHAFT attaches `flake-profile.json` and `Flake Profile` HTML for tests that produce profiler signals. The suite-level profile is attached during engine tear down. Element action duration excludes screenshot capture and report attachment time; those costs are recorded separately as screenshot, page snapshot, and report attachment evidence. Assertion and verification duration is measured around the validation step itself. ```properties title="src/main/resources/properties/custom.properties" shaft.flakeProfiler.enabled=true shaft.flakeProfiler.attachPerTest=true shaft.flakeProfiler.failOnSevereFlakeRisk=false shaft.flakeProfiler.slowActionThresholdMs=2000 ``` ```bash mvn test -Dshaft.flakeProfiler.enabled=true -Dshaft.flakeProfiler.slowActionThresholdMs=2000 ``` ```java SHAFT.Properties.reporting.set() .flakeProfilerEnabled(true) .flakeProfilerAttachPerTest(true) .flakeProfilerFailOnSevereFlakeRisk(false) .flakeProfilerSlowActionThresholdMs(2000); ``` Keep `shaft.flakeProfiler.failOnSevereFlakeRisk=false` while collecting a baseline. Turn it on only after the suite has a realistic slow-action threshold. --- ## Video Recording SHAFT can record a video of every test session and automatically attach it to the Allure report. Because `evidenceLevel=FAILURE_ONLY` is the default, set `evidenceLevel=CUSTOM` before enabling video directly, or set `evidenceLevel=FULL` to enable the full rich-evidence profile. ```properties title="src/main/resources/properties/custom.properties" evidenceLevel=CUSTOM videoParams_recordVideo=true ``` ```bash mvn test -DevidenceLevel=CUSTOM -DvideoParams_recordVideo=true ``` ```java SHAFT.Properties.reporting.set().evidenceLevel("CUSTOM"); SHAFT.Properties.visuals.set().videoParamsRecordVideo(true); ``` ### Where to Put Your Properties File Create a `custom.properties` file under `src/main/resources/properties/` in your project. SHAFT automatically creates a `properties/` directory with some core files on first run — just add your file there: ``` your-project/ └── src/ └── main/ └── resources/ └── properties/ ├── default/ ← auto-generated by SHAFT └── custom.properties ← your file ``` ### Control Video Recording Scope You can control which part of the session is recorded: ```properties title="src/main/resources/properties/custom.properties" evidenceLevel=CUSTOM videoParams_recordVideo=true # Scope options: DriverSession | TestMethod videoParams_scope=DriverSession ``` ```bash mvn test -DevidenceLevel=CUSTOM -DvideoParams_recordVideo=true -DvideoParams_scope=DriverSession ``` ```java SHAFT.Properties.reporting.set().evidenceLevel("CUSTOM"); SHAFT.Properties.visuals.set() .videoParamsRecordVideo(true) .videoParamsScope("DriverSession"); ``` | Scope | Description | |---|---| | `DriverSession` | Records the entire browser/driver session (default) | | `TestMethod` | Records each test method as a separate video | --- ## Animated GIFs Animated GIFs are disabled by default. SHAFT creates them when you explicitly set `evidenceLevel=CUSTOM` and `createAnimatedGif=true`, and also enables them automatically for retry attempts when `forceCaptureSupportingEvidenceOnRetry=true`. GIF frames are captured in the background and do not force every action screenshot to be attached to the report. Generated GIF files are written under `video.folder` before they are attached to Allure: ```properties title="src/main/resources/properties/custom.properties" evidenceLevel=CUSTOM createAnimatedGif=true # Delay between frames in milliseconds (default: 500) animatedGif_frameDelay=500 ``` ```bash mvn test -DevidenceLevel=CUSTOM -DcreateAnimatedGif=true -DanimatedGif_frameDelay=500 ``` ```java SHAFT.Properties.reporting.set().evidenceLevel("CUSTOM"); SHAFT.Properties.visuals.set() .createAnimatedGif(true) .animatedGifFrameDelay(500); ``` --- ## OpenTelemetry spans SHAFT emits an OpenTelemetry span for every test and every reported step, with steps nested under their test and failures marked as errors. It uses the global OpenTelemetry instance, so attach your own SDK or the OpenTelemetry Java agent to export the spans (for example to Jaeger or an OTLP collector). With no SDK configured, the spans do nothing and cost almost nothing. ## Recommended Configurations by Use Case ### CI/CD Pipeline (archive report as artifact) ```properties title="src/main/resources/properties/custom.properties" # Do not open the browser during CI allure.automaticallyOpen=false # Generate a ZIP artifact to publish from the pipeline allure.generateArchive=true ``` ### Active Debugging (rich visuals) ```properties title="src/main/resources/properties/custom.properties" # Allure opens automatically by default; add rich evidence evidenceLevel=FULL ``` ### Quick Pass/Fail Summary ```properties title="src/main/resources/properties/custom.properties" # Enable fast portable summary alongside the Allure report openExecutionSummaryReportAfterExecution=true ``` --- ## All Reporting Properties For the full list of reporting-related properties and their default values, see the [Properties List](/docs/reference/properties/PropertiesList). Key properties quick reference: allure.automaticallyOpen Default true . Open the Allure report in the browser automatically after the run allure.generateReport Default true . Generate the Allure HTML report, helper script, archive, and live watch process allure.generateArchive Default false . Generate a portable ZIP archive of the Allure report allure.accumulateHistory Default true . Accumulate history across runs for trend graphs allure.accumulateReports Default true . Keep previous Allure HTML report files allure.cleanResultsDirectory Default true . Clean Allure results directory before each run allure.customTitle Default Test run report . Custom title in the Allure report header allure.customLogo Default is the SHAFT logo. Custom logo URL for the Allure report allure.realtimeMonitoring Default false . Start native Allure 3 allure watch while tests run allure.singleFile Default true . Generate an Allure 3 Awesome report as a self-contained HTML file allure.theme Default auto . Default Allure 3 Awesome color mode: auto , light , or dark allure.reportLanguage Default en . Language code used by the Allure 3 Awesome report UI allure.open Default false . Pass the browser-open flag to Allure 3 report generation allure.groupBy Default package,testClass . Comma-separated label hierarchy for the Allure 3 report tree openExecutionSummaryReportAfterExecution Default false . Open the Execution Summary report after the run evidenceLevel Default FAILURE_ONLY . Evidence profile: FAILURE_ONLY , BALANCED , FAST , FULL , or CUSTOM whenToAttachSourceCode Default Never . Per-step source-code attachment policy: Never , FailuresOnly , ValidationPointsOnly , or Always . Independent of evidence profiles and trace code context locatorHealthReportEnabled Default false . Generate end-of-run HTML/JSON locator health reports and Allure attachments shaft.locatorHealth.enabled Default false . Generate end-of-run locator health reports shaft.locatorHealth.warnBelowScore Default 70 . Mark locators below this health score as risky shaft.locatorHealth.attachDashboard Default true . Attach the HTML locator health dashboard to Allure shaft.locatorHealth.failBelowScore Default -1 . Fail the run when any locator score is below this threshold; -1 disables score-based failure slowLocatorThresholdMillis Default 750 . Lookup duration that marks a locator as slow failOnLocatorHealthWarnings Default false . Fail the run when locator health warnings are present shaft.flakeProfiler.enabled Default false . Attach opt-in flake and auto-wait timing profiles to Allure shaft.flakeProfiler.attachPerTest Default true . Attach each test's JSON/HTML profile when profiler signals exist shaft.flakeProfiler.failOnSevereFlakeRisk Default false . Fail the run when severe flake-risk actions are found shaft.flakeProfiler.slowActionThresholdMs Default 2000 . Duration threshold used to flag slow and severe-risk actions shaft.trace.enabled Default true . Attach the SHAFT failure trace viewer artifacts shaft.trace.mode Default auto . Trace mode: auto (retry-aware; resolves to retry when retryMaximumNumberOfAttempts > 0, otherwise failure ), failure , retry , or always shaft.trace.retainFailedAttempts Default true . Retain failed-attempt trace archives ( shaft-trace-attempt-<n>.zip ) when retries are enabled shaft.trace.includeCodeContext Default true . Include the best matching source-code frame and snippet shaft.trace.includeFullPageSnapshots Default true . Include web snapshots when available shaft.trace.includeNativePageSource Default true . Include Appium/native page source when available shaft.trace.includeNetwork Default true . Capture redacted Selenium browser network evidence in the trace metadata and HAR-like export shaft.trace.includeConsole Default true . Capture browser console evidence in the trace metadata when the driver exposes browser logs shaft.trace.maxArtifactMb Default 50 . Maximum size for a single trace bundle entry videoParams_recordVideo Default false . Enable video recording of test sessions videoParams_scope Default DriverSession . Scope of video recording createAnimatedGif Default false . Create an animated GIF; retries can enable it automatically animatedGif_frameDelay Default 500 . Delay between GIF frames (milliseconds) screenshotParams_whenToTakeAScreenshot Default ValidationPointsOnly . Granular screenshot policy; profile levels except CUSTOM override it whenToTakePageSourceSnapshot Default failuresOnly . Granular page-source policy; profile levels except CUSTOM override it --- ## Custom report messages Every SHAFT validation chain supports an optional `withCustomReportMessage()` call that replaces the default auto-generated step name in the Allure report with a meaningful, business-readable description. Add it after the validation condition: ```java title="CustomMessageExample.java" driver.assertThat() .browser().title() .contains("Dashboard") .withCustomReportMessage("User should be redirected to the Dashboard after a successful login"); ``` The same call works on element, object, number, file, and API response validations: ```java title="ElementAndApiValidationMessages.java" // Element validation driver.assertThat() .element(By.id("account-balance")).text() .contains("$1,000") .withCustomReportMessage("Account balance should display correctly after the deposit"); // API response validation api.assertThatResponse() .extractedJsonValue("$.name") .isEqualTo("John Doe") .withCustomReportMessage("GET /users/1 must return the correct user name"); ``` ## SHAFT.Report programmatic API `SHAFT.Report` inserts custom steps, log messages, and file/image attachments directly into the Allure report timeline — independent of any validation or action chain. ```java title="ReportApi.java" // Mark the beginning of a logical group of steps SHAFT.Report.log("Starting the checkout flow for a premium user"); // Add a discrete, named step entry — useful for manual verifications SHAFT.Report.report("Verified shopping cart contains exactly 3 items"); // Render an explicit Allure Status — the step text never influences its color SHAFT.Report.report("Custom business rule verified", Status.PASSED); SHAFT.Report.report("Custom business rule violated", Status.FAILED); ``` The same explicit-status overload exists on the lower-level facade as `ReportManager.log(String, Status)`. :::note Behavior change Older SHAFT versions inferred a step's status from its text (any message containing "failed" rendered as a red step). This inference has been removed: benign messages that merely contain the word "failed" now render normally, and failure messages without that literal word no longer render green. Use the explicit-status overload whenever you need a specific step color. ::: Attachments appear as expandable entries and can hold any content type: ```java title="ReportAttach.java" // Attach plain text (e.g., an API response body) SHAFT.Report.attach("text/plain", "API Response Body", api.getResponse().body().asString()); // Attach a screenshot captured as bytes byte[] screenshotBytes = ((TakesScreenshot) driver.getDriver()).getScreenshotAs(OutputType.BYTES); SHAFT.Report.attach("image/png", "Checkout Page Screenshot", new ByteArrayInputStream(screenshotBytes)); ``` ### Best practices - **Write messages from a business perspective** — describe *what* should be true, not *how* the assertion works. - **Keep messages concise** — one sentence is ideal; the Allure UI truncates long strings. - **Use `log()` as section headers** — call it at the start of each logical step group to create a readable narrative. - **Attach evidence on failures** — capture page source or API responses before a known fragile assertion to provide instant context. - **Avoid duplicating the assertion value** — SHAFT already shows the expected vs. actual values; the message should explain the business rule. ## Related - [Reporting and evidence](/docs/features/reporting) - [Validations](/docs/reference/actions/Validations) - [Architecture](/docs/features/architecture) --- # Request Builder Canonical HTML: https://shafthq.github.io/docs/reference/actions/API/Request_Builder Markdown: https://shafthq.github.io/md/reference/actions/API/Request_Builder.md ## SHAFT API In order to interact with APIs, you need an instance of SHAFT.API class and give it the base serviceURI ```java SHAFT.API api = new SHAFT.API("https://jsonplaceholder.typicode.com"); ``` Now you have api object with the base serviceURI to start working with it with the Request Builder ## Request Builder Build the request with the methods below, then send it with your test's request execution step. The resulting `SHAFT.API` session exposes [REST-Assured response](https://www.javadoc.io/doc/io.rest-assured/rest-assured/3.0.1/io/restassured/response/Response.html) access and assertions. **Note:** A request usually has only one of the following: urlArguments, parameters+type, or body ### Request Method Add the request method and give it the serviceName #### Get ```java SHAFT.API api = new SHAFT.API("https://jsonplaceholder.typicode.com"); api.get("/posts"); ``` #### Post ```java SHAFT.API api = new SHAFT.API("https://jsonplaceholder.typicode.com"); api.post("/posts"); ``` #### Put ```java SHAFT.API api = new SHAFT.API("https://jsonplaceholder.typicode.com"); api.put("/posts/1"); ``` #### Patch ```java SHAFT.API api = new SHAFT.API("https://jsonplaceholder.typicode.com"); api.patch("/posts/1"); ``` #### Delete ```java SHAFT.API api = new SHAFT.API("https://jsonplaceholder.typicode.com"); api.delete("/posts/1"); ``` #### Head `HEAD` returns the same status and headers as `GET` without a response body, so it is handy for existence, cache, and header checks. ```java SHAFT.API api = new SHAFT.API("https://jsonplaceholder.typicode.com"); api.head("/posts/1"); ``` #### Options `OPTIONS` is typically used to inspect the methods an endpoint allows (CORS/preflight), commonly asserted through the `Allow` response header. ```java SHAFT.API api = new SHAFT.API("https://jsonplaceholder.typicode.com"); api.options("/posts"); ``` ### Set Authentication Set BASIC or FORM authentication on the request you are building. The default is `AuthenticationType.NONE`. Use `addHeader()` when a bearer token must be sent with later requests. #### Authentication Type BASIC ```java SHAFT.API api = new SHAFT.API("https://postman-echo.com"); api.get("/basic-auth").setAuthentication("postman", "password", AuthenticationType.BASIC); ``` #### Authentication Type FORM ```java SHAFT.API api = new SHAFT.API("serviceURI"); api.get("serviceName").setAuthentication("username", "password", AuthenticationType.FORM); ``` ### Add Cookie Append a cookie to the current session to be used in the current and all the following requests. This feature is commonly used for authentication cookies. ```java SHAFT.API api = new SHAFT.API("serviceURI"); api.post("serviceName").addCookie("session_id", "1234"); ``` You can also use it directly without a request method to be used in all the following requests. ```java SHAFT.API api = new SHAFT.API("serviceURI"); api.addCookie("session_id", "1234"); api.post("serviceName"); ``` You can also copy browser cookies into an API session when a test starts in the UI and continues through API calls: ```java SHAFT.GUI.WebDriver driver = new SHAFT.GUI.WebDriver(); driver.browser().navigateToURL("https://app.example.com"); SHAFT.API api = new SHAFT.API("https://api.example.com"); api.importCookiesFrom(driver.browser()); api.get("/account"); ``` Use `api.importCookiesFrom(driver.browser(), "example.com", "/app")` when you only want cookies from one domain and path. When moving API cookies into a browser, host-only response cookies stay scoped to the API host; cross-subdomain browser reuse requires cookies issued with a shared domain. ### Set Target Status Code Sets the expected target status code for the API request that you're currently building. By default, this value is set to 200, but you can change it by calling the **setTargetStatusCode** method. ```java SHAFT.API api = new SHAFT.API("https://jsonplaceholder.typicode.com"); api.get("/users").setTargetStatusCode(200); ``` ### Follow Redirects By default the request automatically follows HTTP redirects (3xx), so a redirecting endpoint returns the final response. Call **setFollowRedirects(false)** to receive the redirect response itself — required when asserting a 3xx status code or inspecting the `Location` header of the redirect. ```java SHAFT.API api = new SHAFT.API("https://httpbin.org"); api.get("/redirect-to?url=/get") .setFollowRedirects(false) .setTargetStatusCode(302) ; ``` ### Retry Policy The request execution step sends a request once unless you add an opt-in retry policy. Retries are useful for transient network failures, timeouts, rate limits, and temporary upstream errors without rerunning the whole test method. ```java SHAFT.API api = new SHAFT.API("https://api.example.com"); api.get("/users") .withRetry(RetryPolicy.transientFailures() .maxAttempts(3) .exponentialBackoff(Duration.ofMillis(200), Duration.ofSeconds(2))) ; ``` `RetryPolicy.transientFailures()` retries network, connection, and read-timeout failures plus HTTP `408`, `429`, `500`, `502`, `503`, and `504`. Use `RetryPolicy.statusCodes(503, 504)` when you only want specific statuses. By default, SHAFT retries only idempotent requests: `GET`, `PUT`, and `DELETE`. For `POST` or `PATCH`, opt in only when the endpoint is safe to repeat: ```java api.post("/orders") .withRetry(RetryPolicy.statusCodes(503) .maxAttempts(2) .fixedBackoff(Duration.ofMillis(500)) .allowNonIdempotentRequests()) ; ``` Backoff can be fixed, exponential, or jittered. SHAFT reports include the retry attempt count and final outcome for requests that use a retry policy. ### Set Content Type Sets the content type for the API request that you're currently building. By default, this value is set to **ContentType.ANY** but you can change it by calling the **setContentType** method and giving it the enum value you want. contentType Enumeration of common [IANA](http://www.iana.org/assignments/media-types/media-types.xhtml) content-types. This may be used to specify a request or response content-type more easily than specifying the full string each time. Example: **ContentType.JSON** ```java SHAFT.API api = new SHAFT.API("https://jsonplaceholder.typicode.com"); api.get("/users").setContentType("application/json"); ``` ```java SHAFT.API api = new SHAFT.API("https://jsonplaceholder.typicode.com"); api.get("/users").setContentType(ContentType.JSON); ``` ### Add Header Append a header to the current session **to be used in the current and all the following requests**. This feature is commonly used for authentication tokens and other global headers as you need ```java SHAFT.API api = new SHAFT.API("serviceURI"); String token = "@1234z"; api.post("serviceName").addHeader("Authorization", "Bearer " + token); ``` You can add more than one header in the same request. ```java SHAFT.API api = new SHAFT.API("serviceURI"); String token = "@1234z"; api.post("serviceName").addHeader("Authorization", "Bearer " + token).addHeader("Accept-Charset", "utf-8"); ``` You can also use it directly without a request method to set the header for all the following requests. ```java SHAFT.API api = new SHAFT.API("serviceURI"); api.addHeader("Accept-Language", "en"); api.post("serviceName"); ``` For UI flows that keep a bearer token in browser storage, copy only the selected header: ```java api.addHeader("Authorization", "Bearer " + token); driver.browser() .navigateToURL("https://app.example.com") .importHeaderToLocalStorage(api, "Authorization", "authToken"); ``` ### Set Request Body Sets the body (if any) for the API request that you're currently building. ```java SHAFT.API api = new SHAFT.API("serviceURI"); api.post("serviceName").setRequestBody(body); ``` #### Body as String ```java SHAFT.API api = new SHAFT.API("https://reqres.in/"); String body = """ { "name": "adam", "job": "engineer" }"""; api.post("api/users").setRequestBody(body).setContentType(ContentType.JSON).setTargetStatusCode(201); ``` #### Body as Hash Map ```java SHAFT.API api = new SHAFT.API("https://reqres.in/"); HashMap body = new HashMap<>(); body.put("name", "adam"); body.put("job", "engineer"); api.post("api/users").setRequestBody(body).setContentType(ContentType.JSON).setTargetStatusCode(201); ``` #### Body as JSONObject ```java SHAFT.API api = new SHAFT.API("https://reqres.in/"); JSONObject body = new JSONObject(); body.put("name", "adam"); body.put("job", "engineer"); api.post("api/users").setRequestBody(body).setContentType(ContentType.JSON).setTargetStatusCode(201); ``` ### Set Request Body From File ```java SHAFT.API api = new SHAFT.API("serviceURI"); api.post("serviceName").setRequestBodyFromFile("relativeFilePath"); ``` Having a request body as json file in this path "src/test/resources/testDataFiles/requestBody.json" like this: ```json { "name": "morpheus", "job": "leader" } ``` ```java SHAFT.API api = new SHAFT.API("https://reqres.in/"); api.post("api/users").setRequestBodyFromFile("src/test/resources/testDataFiles/requestBody.json").setTargetStatusCode(201).setContentType(ContentType.JSON); ``` ### Set Parameters Sets the parameters (if any) for the API request that you're currently building. #### Parameters Type FORM Note that: Form parameter works with multipart/form-data requests. ```java SHAFT.API api = new SHAFT.API("serviceURI"); Map parameters = new LinkedHashMap<>(); parameters.put("username", "john"); parameters.put("password", "1234"); api.post("serviceName").setParameters(parameters, RestActions.ParametersType.FORM); ``` #### Parameters Type QUERY Query parameters are appended to the request URL. They can also be combined with a request body when the endpoint expects URL filters and payload data in the same request. ```java SHAFT.API api = new SHAFT.API("serviceURI"); Map parameters = new LinkedHashMap<>(); parameters.put("search", "john"); parameters.put("orderBy", "desc"); api.get("serviceName").setParameters(parameters, RestActions.ParametersType.QUERY); ``` ```java SHAFT.API api = new SHAFT.API("serviceURI"); Map parameters = new LinkedHashMap<>(); parameters.put("include", "profile"); String body = "{\"status\":\"active\"}"; api.post("serviceName") .setRequestBody(body) .setParameters(parameters, RestActions.ParametersType.QUERY) ; ``` #### Parameters Type MULTIPART Note that: Multipart parameters are used for file uploads and text fields. All text parts are sent as **UTF-8**, and the correct boundary is set automatically (no need to set `Content-Type` manually). **Using `Map `** ```java SHAFT.API api = new SHAFT.API("serviceURI"); Map parametersMap = new LinkedHashMap<>(); parametersMap.put("image", new java.io.File("src/test/resources/11_02.png")); parametersMap.put("arabicText", "تست أوتوميشن"); api.post("serviceName") .setParameters(parametersMap, RestActions.ParametersType.MULTIPART) ; ``` ### Set Path Parameters Sets the path parameters dynamically by replacing placeholders in the `serviceName`. This method supports two modes: 1. **Key-Value Replacement**: Use a `Map ` to specify placeholders and their corresponding values. 2. **Ordered Value Replacement**: Provide values in the exact order the placeholders appear in the `serviceName`. #### Key-Value Replacement Pass a `Map` of key-value pairs to replace placeholders by their names: ```java Map pathParams = Map.of("PostID", 1, "CommentID", 1); SHAFT.API api = new SHAFT.API("https://jsonplaceholder.typicode.com"); api.get("/posts/{PostID}/comments/{CommentID}") .setPathParameters(pathParams) ; ``` #### Ordered Value Replacement Pass value directly to replace placeholder in the `serviceName`: ```java SHAFT.API api = new SHAFT.API("https://jsonplaceholder.typicode.com"); api.get("/posts/{PostID}/comments/{CommentID}") .setPathParameters("1", "1") ; ``` ### Set URL Arguments Sets the url arguments (if any) for the API request that you're currently building. ```java SHAFT.API api = new SHAFT.API("serviceURI"); api.post("serviceName").setUrlArguments("username=john&password=1234"); ``` ```java SHAFT.API api = new SHAFT.API("https://jsonplaceholder.typicode.com"); api.get("/comments").setUrlArguments("postId=1").setTargetStatusCode(201); ``` ### Enable URL Encoding Tells whether REST Assured should automatically encode the URI if not defined explicitly. Note that this does not affect multipart form data. Default is true. ```java SHAFT.API api = new SHAFT.API("serviceURI"); api.post("serviceName").enableUrlEncoding(false); ``` ### Use Relaxed HTTPS Validation set useRelaxedHTTPSValidation configuration to trust all hosts regardless if the SSL certificate is invalid in the request builder 'SSL' is the protocol name by default ```java SHAFT.API api = new SHAFT.API("serviceURI"); api.get("serviceName").useRelaxedHTTPSValidation(); ``` ```java SHAFT.API api = new SHAFT.API("serviceURI"); api.get("serviceName").useRelaxedHTTPSValidation("SSL"); ``` ### Append Default Content Charset To Content Type If Undefined Tells whether REST Assured should automatically append the content charset to the content-type header if not defined explicitly. Note that this does not affect multipart form data. Default is true. ```java SHAFT.API api = new SHAFT.API("serviceURI"); api.post("serviceName").appendDefaultContentCharsetToContentTypeIfUndefined(false); ``` See [Response Validations](/docs/reference/actions/API/Response_Validations) for response assertions and the [JavaDocs](https://shafthq.github.io/SHAFT_ENGINE/) for API details. ## GraphQL API Testing SHAFT supports GraphQL requests through `SHAFT.API.sendGraphQlRequest()`. It builds a normal POST request with `Content-Type: application/json`, so headers, status codes, and response assertions use the same fluent chain. ### Simple GraphQL Query ```java title="GraphQLSimpleQuery.java" SHAFT.API api = new SHAFT.API("https://api.example.com"); api.sendGraphQlRequest("/graphql", "{ users { id name email } }"); api.assertThatResponse() .extractedJsonValue("$.data.users[0].name") .isEqualTo("Alice"); ``` ### GraphQL Query with Variables ```java title="GraphQLWithVariables.java" SHAFT.API api = new SHAFT.API("https://api.example.com"); String query = "query GetUser($id: ID!) { user(id: $id) { name email role } }"; String variables = "{\"id\": \"123\"}"; api.sendGraphQlRequest("/graphql", query, variables); api.assertThatResponse() .extractedJsonValue("$.data.user.email") .contains("@example.com"); ``` ### GraphQL with Authentication Header ```java title="GraphQLWithAuth.java" SHAFT.API api = new SHAFT.API("https://api.example.com"); api.sendGraphQlRequest("/graphql", "{ me { name } }") .addHeader("Authorization", "Bearer mytoken123") ; api.assertThatResponse().body().contains("\"name\""); ``` ### GraphQL Mutation ```java title="GraphQLMutation.java" SHAFT.API api = new SHAFT.API("https://api.example.com"); String mutation = "mutation CreateUser($input: CreateUserInput!) { createUser(input: $input) { id name } }"; String variables = "{\"input\": {\"name\": \"Bob\", \"email\": \"bob@example.com\"}}"; api.sendGraphQlRequest("/graphql", mutation, variables) .addHeader("Authorization", "Bearer admintoken") .setTargetStatusCode(200) ; api.assertThatResponse() .extractedJsonValue("$.data.createUser.name") .isEqualTo("Bob"); ``` :::tip GraphQL always uses HTTP `POST` under the hood. SHAFT automatically sets the `Content-Type: application/json` header and wraps your query and variables in the correct payload format. ::: --- ## Sample Code Snippet ```java public class Test_Api { SHAFT.API api; @Test public void test_get() { api = new SHAFT.API("https://jsonplaceholder.typicode.com"); api.get("/users"); api.assertThatResponse().extractedJsonValue("$[?(@.name=='Chelsey Dietrich')].id").isEqualTo("5"); } @Test public void test_post() { api = new SHAFT.API("https://reqres.in/"); String body = """ { "name": "morpheus", "job": "leader" }"""; api.post("api/users").setRequestBody(body).setTargetStatusCode(201).setContentType(ContentType.JSON); api.assertThatResponse().extractedJsonValue("$.name").isEqualTo("morpheus"); } } ``` ## Session headers, cookies and perform() | Method | What it does | | --- | --- | | `addHeaders(Map headers)` | Appends headers to the session, used by this and every following request. | | `addCookies(Map cookies)` | Appends cookies to the session, used by this and every following request. | | `perform()` | Sends the built request and returns the response. | `performRequest()` is deprecated; use `perform()` instead. ## Related - [Response Validations](/docs/reference/actions/API/Response_Validations) - [API Authentication](/docs/reference/actions/API/API_Authentication) - [API](/docs/testing/api) --- # Response Getters Canonical HTML: https://shafthq.github.io/docs/reference/actions/API/Response_Getters Markdown: https://shafthq.github.io/md/reference/actions/API/Response_Getters.md ## SHAFT API Getters After the request execution step sends a request, use these getters on the `SHAFT.API` session to read its latest response. ### Get Response Body Extracts the response body and returns it as a plain string ```java String body = api.getResponseBody(); ``` #### Usage ```java SHAFT.API api = new SHAFT.API("http://api.zippopotam.us/"); api.get("us/90210"); String body = api.getResponseBody(); SHAFT.Validations.assertThat().object(body).contains("Beverly Hills"); ``` ### Map JSON Response To Types Maps the latest JSON response body to a Java class, record, list, or generic `TypeReference`. ```java User user = api.getResponseAs(User.class); List users = api.getResponseAsList(User.class); List usersByType = api.getResponseAs(new TypeReference >() {}); ``` #### Usage ```java @JsonIgnoreProperties(ignoreUnknown = true) record User(int id, String name) {} SHAFT.API api = new SHAFT.API("https://jsonplaceholder.typicode.com"); api.get("/users/1").setTargetStatusCode(200); User user = api.getResponseAs(User.class); api.get("/users").setTargetStatusCode(200); List users = api.getResponseAsList(User.class); List usersByType = api.getResponseAs(new TypeReference >() {}); ``` Typed mapping expects a JSON response content type and a non-empty body. SHAFT throws clear exceptions for empty bodies, non-JSON responses, and mapping failures. ### Get Response Status Code Extracts the response status code as integer ```java int statusCode = api.getResponseStatusCode(); ``` #### Usage ```java SHAFT.API api = new SHAFT.API("http://api.zippopotam.us/"); api.get("us/90210"); int statusCode = api.getResponseStatusCode(); SHAFT.Validations.assertThat().number(statusCode).isEqualTo(200); ``` ### Get Response Time Extracts the response time as long ```java long responseTime = api.getResponseTime(); ``` #### Usage ```java SHAFT.API api = new SHAFT.API("http://api.zippopotam.us/"); api.get("us/90210"); long responseTime = api.getResponseTime(); SHAFT.Validations.verifyThat().number(responseTime).isGreaterThanOrEquals(1); SHAFT.Validations.verifyThat().number(responseTime).isLessThanOrEquals(10000); ``` ### Get Response JSON Value Extracts a string value from the response body by parsing the target **JSONPath.** _* To extract the desired value, please refer to these urls for examples: You can learn the JSONPath Syntax from [the JSONPath syntax reference](https://support.smartbear.com/alertsite/docs/monitors/api/endpoint/jsonpath.html) And test your JSONPath [on JSONPath.com](http://jsonpath.com/) *_ ```java String value = api.getResponseJSONValue("$.data.name"); ``` #### Usage ```java SHAFT.API api = new SHAFT.API("https://jsonplaceholder.typicode.com"); api.get("/users"); String value = api.getResponseJSONValue("$[?(@.name=='Ervin Howell')].address.street"); SHAFT.Validations.assertThat().object(value).isEqualTo("Victor Plains"); ``` ### Get Response JSON Value As List Extracts the response as list by parsing the target **JSONPath.** ```java String value = api.getResponseJSONValueAsList("$.data"); ``` #### Usage ```java SHAFT.API api = new SHAFT.API("https://jsonplaceholder.typicode.com"); api.get("/todos"); List completedList = api.getResponseJSONValueAsList("$[?(@.completed==true)].completed"); for (Object completed : completedList) { SHAFT.Validations.verifyThat().object(completed.toString()).isEqualTo("true"); } ``` ### Get Response XML Value ```java String value = api.getResponseXMLValue("xmlPath"); ``` ### Get Response XML Value As List ```java List value = api.getResponseXMLValueAsList("xmlPath"); ``` ## Session headers and cookies `api.getHeaders()` and `api.getCookies()` return immutable snapshots of the headers and cookies attached to the `SHAFT.API` session. ## Related - [Request Builder](/docs/reference/actions/API/Request_Builder) - [Response Validations](/docs/reference/actions/API/Response_Validations) - [API Authentication](/docs/reference/actions/API/API_Authentication) - [API](/docs/testing/api) --- # Response Validations Canonical HTML: https://shafthq.github.io/docs/reference/actions/API/Response_Validations Markdown: https://shafthq.github.io/md/reference/actions/API/Response_Validations.md ## SHAFT API Response Validations Call `assertThatResponse()` for a hard assertion or `verifyThatResponse()` for a soft verification. Each response condition runs immediately. ### Body Validate on the response body. Chains to JSON/body validation methods, including structural JSON equality that ignores array/object ordering. ```java api.assertThatResponse().body().contains("data"); api.assertThatResponse().body() .equalsIgnoringOrder("{\"roles\":[\"admin\",\"tester\"]}"); ``` #### Usage ```java SHAFT.API api = new SHAFT.API("http://api.zippopotam.us/"); api.get("us/90210"); api.assertThatResponse().body().contains("Beverly Hills"); ``` ### Extracted Json Value Validate an extracted value from the response body by parsing the target **JSONPath**. :::info You can learn the JSONPath syntax from the [JSONPath documentation](https://github.com/json-path/JsonPath) and test your expressions at [jsonpath.com](http://jsonpath.com/). ::: Chains to [Object validation methods](../Validations#object-validations) to continue building your validation. ```java api.assertThatResponse().extractedJsonValue("$.data").isEqualTo("data"); ``` #### Usage ```java SHAFT.API api = new SHAFT.API("https://jsonplaceholder.typicode.com"); api.get("/users"); api.assertThatResponse().extractedJsonValue("$[?(@.name=='Chelsey Dietrich')].id").isEqualTo("5"); ``` ### Extracted Json Value As List Validate an extracted value from the response body by parsing the target **JSONPath** as a list and check every item against it. Chains to [Object validation methods](../Validations#object-validations) to continue building your validation. ```java api.assertThatResponse().extractedJsonValueAsList("jsonPath").isEqualTo("data"); ``` #### Usage ```java SHAFT.API api = new SHAFT.API("https://jsonplaceholder.typicode.com"); api.get("/todos"); api.verifyThatResponse().extractedJsonValueAsList("$[?(@.completed==true)].completed").contains(true); ``` ### Time Validate on the response time. Chains to [Number validation methods](../Validations#number-validations) to continue building your validation. ```java api.assertThatResponse().time().isEqualTo(expectedNumberValue); ``` #### Usage ```java SHAFT.API api = new SHAFT.API("http://api.zippopotam.us/"); api.get("us/90210"); api.verifyThatResponse().time().isGreaterThanOrEquals(100); api.verifyThatResponse().time().isLessThanOrEquals(100000); ``` ### Is Equal To File Content Validate if the content of the provided actual response object is equal to the expected file content. ```java api.assertThatResponse().isEqualToFileContent("fileRelativePath"); ``` ### Does Not Equal File Content Validate if the content of the provided actual response object is not equal to the expected file content. ```java api.assertThatResponse().doesNotEqualFileContent("fileRelativePath"); ``` ### Is Equal To File Content Ignoring Order Validate if the content of the provided actual response object is equal to the expected file content while ignoring Order of the json objects. ```java api.assertThatResponse().isEqualToFileContentIgnoringOrder("fileRelativePath"); ``` ### Does Not Equal File Content Ignoring Order Validate if the content of the provided actual response object is not equal to the expected file content while ignoring Order of the json objects. ```java api.assertThatResponse().doesNotEqualFileContentIgnoringOrder("fileRelativePath"); ``` ### Contains File Content Validate if the content of the provided actual response object contains the expected file content. ```java api.assertThatResponse().containsFileContent("fileRelativePath"); ``` ### Does Not Contain File Content Validate if the content of the provided actual response object does not contain the expected file content. ```java api.assertThatResponse().doesNotContainFileContent("fileRelativePath"); ``` ### Matches Schema Validate if the content of the provided actual response object matches the schema for the expected file content. ```java api.assertThatResponse().matchesSchema("fileRelativePath"); ``` ### Does Not Match Schema Validate if the content of the provided actual response object does not match the schema for the expected file content. ```java api.assertThatResponse().doesNotMatchSchema("fileRelativePath"); ``` ## Response value builders These `api.assertThatResponse()` / `api.verifyThatResponse()` methods read a value from the completed response and return a validation builder: | Method | Value | | --- | --- | | `statusCodeValue()` | Response status code as a number. | | `bodyValue()` | Whole response body. | | `headerValue(String name)` | One response header. | | `cookieValue(String name)` | One response cookie. | | `jsonValue(String path)` | One JSONPath result. | | `jsonValues(String path)` | A JSONPath list result. | | `responseTimeMillis()` | Response time in milliseconds. | | `matchesContract(String fileRelativePath)` | Validates the response against an order-insensitive JSON contract file. | ```java api.assertThatResponse().statusCodeValue().isEqualTo(200); api.assertThatResponse().jsonValue("$.name").isEqualTo("SHAFT"); ``` ## Related - [Request Builder](/docs/reference/actions/API/Request_Builder) - [API Authentication](/docs/reference/actions/API/API_Authentication) - [API](/docs/testing/api) --- # Running Tests in CI/CD Pipelines Canonical HTML: https://shafthq.github.io/docs/reference/guides/CI_CD_Integration Markdown: https://shafthq.github.io/md/reference/guides/CI_CD_Integration.md Running your SHAFT tests in a CI/CD pipeline is straightforward. SHAFT's [property system](/docs/reference/properties/PropertyTypes) lets you override configuration from the command line. --- ## Basic Pipeline Command The simplest way to run SHAFT tests in any CI/CD system is with Maven: ```bash title="Pipeline command" mvn -e test \ -DheadlessExecution=true \ -Dallure.automaticallyOpen=false \ -Dallure.generateArchive=true ``` | Property | Why | |---|---| | `headlessExecution=true` | No display available in CI — run the browser without a GUI | | `allure.automaticallyOpen=false` | Do not try to open a browser to show the report | --- ## Recommended CI/CD Properties Create a `custom.properties` file with sensible defaults for CI, then override individual values from the CLI as needed: ```properties title="src/main/resources/properties/custom.properties" # CI/CD defaults headlessExecution=true allure.automaticallyOpen=false retryMaximumNumberOfAttempts=2 createAnimatedGif=false videoParams_recordVideo=false ``` :::info Remember the [property priority hierarchy](/docs/reference/properties/PropertyTypes): **Code-based > CLI-based > File-based > Defaults**. ::: --- ## GitHub Actions Example ```yaml title=".github/workflows/tests.yml" name: Run Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up JDK uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: '21' - name: Run tests run: | mvn -e test \ -DheadlessExecution=true \ -Dallure.automaticallyOpen=false - name: Upload Allure report if: always() uses: actions/upload-artifact@v7 with: name: allure-report path: allure-report/*.html archive: false if-no-files-found: ignore ``` --- ## Jenkins Pipeline Example ```groovy title="Jenkinsfile" pipeline { agent any tools { maven 'Maven-3.9' jdk 'JDK-21' } stages { stage('Test') { steps { sh ''' mvn -e test \ -DheadlessExecution=true \ -Dallure.automaticallyOpen=false \ -Dallure.generateArchive=true ''' } } } post { always { archiveArtifacts artifacts: 'allure-report-archive/**', allowEmptyArchive: true } } } ``` --- ## Running Specific Tests Use Maven Surefire parameters to control which tests run: ```bash title="Run a specific test class" mvn test -Dtest=LoginTest ``` ```bash title="Run a specific method" mvn test -Dtest=LoginTest#testValidLogin ``` ```bash title="Run all tests in a package" mvn test -Dtest="com.example.tests.**" ``` --- ## Parameterizing for Multiple Environments Use CI/CD pipeline variables to switch between environments: ```bash title="Parameterized execution" mvn test \ -DbaseURL=${TARGET_URL} \ -DtargetBrowserName=${BROWSER} \ -DexecutionAddress=${GRID_URL} \ -DtargetOperatingSystem=${OS} ``` This lets you run the same test suite against staging, production, or different browser/OS combinations by changing pipeline variables. --- ## Best Practices 1. **Always use headless mode** in pipelines — there is no display server. 2. **Disable auto-opening reports** — set `allure.automaticallyOpen=false`. 3. **Archive test artifacts** — always upload reports and logs, even on failure (use `if: always()` in GitHub Actions or `post { always { } }` in Jenkins). GitHub Actions v7+ with `archive: false` uploads HTML files directly without zipping. 4. **Set retry attempts** — `retryMaximumNumberOfAttempts=2` allows two retries after the first failure. Keep the budget small; JUnit retries still run setup and teardown for isolated attempts. 5. **Parameterize everything** — use CLI properties so the same test suite works across environments. ## Related - [Solution Design](/docs/reference/guides/Solution_Design) - [Test Structure](/docs/reference/guides/Test_Structure) - [Quick Start](/docs/start/quick-start) --- # Sharded Test Execution and Merged Reports Canonical HTML: https://shafthq.github.io/docs/reference/guides/Sharded_Execution Markdown: https://shafthq.github.io/md/reference/guides/Sharded_Execution.md Large suites take too long to run as one Maven invocation. SHAFT's `-Dshaft.shard=N/M` property splits a suite into `M` independent shards that you run in parallel — locally across terminals, or as a CI matrix — then merges every shard's Allure results back into a single report with `ShardMergeCli`. --- ## What Sharding Does Set `-Dshaft.shard= / ` on any `mvn test` invocation, where ` ` is the 1-based shard this run executes and ` ` is the shard count: ```bash title="Run shard 2 of 4" mvn test "-Dshaft.shard=2/4" -DheadlessExecution=true ``` SHAFT assigns each test method to exactly one shard by hashing `className#methodName` with SHA-256 and taking the hash modulo the shard count. The assignment is deterministic and content-based, not order-based: - The same method always lands in the same shard for a given ` `, across machines and runs. - The union of shards `1..M` covers the full suite with no overlap and no duplication. - Only `@Test` methods are partitioned. Configuration methods (`@BeforeSuite`, `@BeforeClass`, and similar) run normally for whatever test methods remain in a shard. :::info A blank, missing, or malformed `shaft.shard` value disables sharding entirely and runs the full suite, so the property is safe to leave unset in environments that do not shard. ::: ## When to Use Sharding Reach for sharding when a suite's wall-clock time is dominated by test count rather than per-test setup cost — many independent `@Test` methods that can run in separate JVMs without sharing state. Skip it for small suites, where the fixed cost of N Maven startups and a merge step outweighs the time saved, and for suites with cross-test dependencies that assume a single execution order. ## Running Shards Locally Run each shard as its own `mvn test` invocation with a matching `-Dtest=` filter if you want to scope which classes participate: ```bash title="Two shards, run sequentially" mvn test "-Dshaft.shard=1/2" -DheadlessExecution=true mvn test "-Dshaft.shard=2/2" -DheadlessExecution=true ``` :::info `run_sharded_and_merge.py` and `assemble_shard_blob.py` ship in the **SHAFT_ENGINE** repository, not in your project — copy [`run_sharded_and_merge.py`](https://github.com/ShaftHQ/SHAFT_ENGINE/blob/main/scripts/ci/run_sharded_and_merge.py) and [`assemble_shard_blob.py`](https://github.com/ShaftHQ/SHAFT_ENGINE/blob/main/scripts/ci/assemble_shard_blob.py) into your own `scripts/ci/` before using either command below. Both are standalone and use only the Python standard library, so no extra install step is required beyond a Python 3 interpreter. ::: `scripts/ci/run_sharded_and_merge.py` automates that loop end to end: it runs every shard, stages each shard's `allure-results` and trace directory into a shard blob, then merges all blobs into one report with `ShardMergeCli`. ```bash title="One command: shard, run, and merge" scripts/ci/run_sharded_and_merge.py --shards 3 --test "%regex[.*SmokeTest.*]" ``` Useful flags: | Flag | Purpose | |---|---| | `--shards ` | Total shard count — the `M` in `-Dshaft.shard= /M` | | `--test ` | `-Dtest=` filter forwarded to every shard's Maven run | | `--no-headless` | Do not force `-DheadlessExecution=true` | | `--output ` | Merged report output directory (default `target/merged-report`) | | `--skip-tests` | Skip the Maven runs and just re-assemble and merge existing results | ## Merging Shard Blobs With ShardMergeCli Each shard produces a blob directory (`allure-results/`, an optional `shaft-traces/` directory, and an optional `execution-intelligence.json`). `ShardMergeCli` combines any number of shard blobs into one Allure result set plus a cross-shard speedboard HTML. Run it through Maven's `exec:java` goal, without packaging a jar first: ```bash title="Merge via exec:java" mvn -q -pl report-aggregate exec:java \ "-Dexec.mainClass=com.shaft.reportaggregate.ShardMergeCli" \ "-Dexec.args=--output target/merged-report target/shard-blobs/1 target/shard-blobs/2 target/shard-blobs/3" ``` Or run the compiled class directly once `report-aggregate.jar` is built: ```bash title="Merge from a built jar" java -cp report-aggregate.jar com.shaft.reportaggregate.ShardMergeCli \ --output target/merged-report \ target/shard-blobs/1 target/shard-blobs/2 target/shard-blobs/3 ``` Both forms accept `--output ` followed by one or more shard blob directories. The merged Allure results directory, the speedboard HTML path, and any flaky-clustering warnings print to stdout when the merge finishes. MCP clients and `shaft-cli call` can run the same merge through `report_merge_shards` (arguments: `shardBlobPaths` list and optional `outputDirectory`, default `target/shaft-merged-report`): ```bash shaft-cli call report_merge_shards \ --args '{"shardBlobPaths":["target/shard-blobs/1","target/shard-blobs/2"],"outputDirectory":"target/shaft-merged-report"}' ``` ## GitHub Actions Matrix Example A generic CI shape: one matrix job per shard uploads its blob as an artifact, then a `merge` job downloads every blob and runs `ShardMergeCli`. This assumes `assemble_shard_blob.py` has already been copied into `scripts/ci/` in this repository, per the note above. ```yaml title=".github/workflows/sharded-tests.yml" jobs: shard: strategy: matrix: shard: [1, 2, 3] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Run shard ${{ matrix.shard }}/3 run: mvn test "-Dshaft.shard=${{ matrix.shard }}/3" -DheadlessExecution=true # assemble_shard_blob.py is copied from SHAFT_ENGINE's scripts/ci/ (see note above) - name: Assemble shard blob (script copied from SHAFT_ENGINE scripts/ci/) run: python3 scripts/ci/assemble_shard_blob.py --shard ${{ matrix.shard }} --output "target/shard-blobs/shard-${{ matrix.shard }}" --zip - uses: actions/upload-artifact@v4 with: name: shard-blob-${{ matrix.shard }} path: target/shard-blobs/shard-${{ matrix.shard }}.zip merge: needs: shard runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 with: pattern: shard-blob-* path: downloaded-blobs - name: Unzip shard blobs run: | mkdir -p target/shard-blobs for zip in downloaded-blobs/*/*.zip; do name="$(basename "$zip" .zip)" mkdir -p "target/shard-blobs/$name" unzip -oq "$zip" -d "target/shard-blobs/$name" done - name: Merge shards run: | mvn -q -pl report-aggregate -am -DskipTests install blobs=(target/shard-blobs/*/) mvn -q -pl report-aggregate exec:java \ "-Dexec.mainClass=com.shaft.reportaggregate.ShardMergeCli" \ "-Dexec.args=--output target/merged-report ${blobs[*]}" - uses: actions/upload-artifact@v4 with: name: merged-report path: target/merged-report ``` :::warning Every shard writes `allure-results` into the same relative path, so each matrix job needs its own runner (or workspace) — never run two shards against a shared `allure-results` directory, or their results collide before the merge step ever runs. ::: ## Related - [CI/CD Integration](/docs/reference/guides/CI_CD_Integration) - [Test Artifacts and Report Paths](/docs/reference/guides/Test_Artifacts) - [How SHAFT reduces flakiness](/docs/testing/flakiness) - [Properties Reference](/docs/reference/properties/PropertiesList) --- # SSH Remote Terminal Canonical HTML: https://shafthq.github.io/docs/reference/actions/CLI/SSH_Terminal Markdown: https://shafthq.github.io/md/reference/actions/CLI/SSH_Terminal.md SHAFT's `TerminalActions` class supports **SSH remote execution** natively. Use the `SHAFT.CLI.remoteTerminal(...)` facade for reusable SSH sessions, command execution, port forwarding, and SFTP transfers. --- ## Prerequisites - The remote machine must be reachable from the test execution machine (check firewall rules and SSH port access). - An SSH key pair must be available; **key-based authentication is the current shipped path** for the string-based `remoteTerminal(...)` overloads. - The SSH private key file must be accessible to the test execution process. - Keep **one JSch artifact** on the classpath. SHAFT ships [`com.github.mwiede:jsch`](https://github.com/mwiede/jsch); do not add the legacy `com.jcraft:jsch` coordinate alongside it. For strict host-key validation, password auth, and keyboard-interactive flows, see [#3130](https://github.com/ShaftHQ/SHAFT_ENGINE/issues/3130) and the upcoming `SshConnectionOptions` guide update. --- ## Create an SSH Terminal ```java title="SshTerminalSetup.java" TerminalActions remote = SHAFT.CLI.remoteTerminal( "192.168.1.100", // SSH host (IP or hostname) 22, // SSH port (default: 22) "deploy-user", // SSH username "~/.ssh/", // Directory containing the private key "id_rsa" // SSH private key file name ); ``` You can verify the terminal is connected as a remote SSH session: ```java title="VerifySshContext.java" assertTrue(remote.isRemoteTerminal(), "Terminal should be connected to the remote server via SSH"); ``` --- ## Reusable Session Lifecycle Create **one** `TerminalActions` instance, run multiple commands or transfers on it, then call `quit()` in teardown. `quit()` disconnects the reusable JSch session, cancels the idle-timeout task, and removes active port forwards. ```java title="ReusableRemoteSession.java" TerminalActions remote = SHAFT.CLI.remoteTerminal( System.getenv("TEST_SSH_HOST"), Integer.parseInt(System.getenv("TEST_SSH_PORT")), System.getenv("TEST_SSH_USERNAME"), System.getenv("TEST_SSH_KEY_FOLDER"), System.getenv("TEST_SSH_KEY_NAME"), true // optional verbose streaming of command output lines ); try { String whoami = remote.performTerminalCommand("whoami"); String withEnv = remote.performTerminalCommand( "sh -c 'printf %s $SHAFT_REMOTE_MARKER'", Map.of("SHAFT_REMOTE_MARKER", "docs-demo") ); String remoteFile = remote.uploadFile("target/demo.txt", "/tmp/shaft-demo.txt"); String localFile = remote.downloadFile(remoteFile, "target/ssh-demo/shaft-demo.txt"); String localPort = remote.forwardLocalPort(0, "127.0.0.1", 22); SHAFT.Validations.assertThat().object(whoami).doesNotEqual(""); SHAFT.Validations.assertThat().object(withEnv).contains("docs-demo"); SHAFT.Validations.assertThat().object(localFile).contains("shaft-demo.txt"); SHAFT.Validations.assertThat().object(localPort).doesNotEqual(""); } finally { remote.quit(); } ``` `TerminalActions` also implements `AutoCloseable`; `close()` delegates to `quit()` when you prefer try-with-resources. --- ## Execute Commands ### Single Command ```java title="SshSingleCommand.java" // Check disk usage on the remote server String diskUsage = remote.performTerminalCommand("df -h"); // Check available memory String memory = remote.performTerminalCommand("free -m"); // View a remote configuration file String config = remote.performTerminalCommand("cat /etc/app/config.yaml"); ``` Each call returns the combined command output log for assertions and reporting. ### Multiple Commands in Sequence ```java title="SshMultipleCommands.java" String result = remote.performTerminalCommands( List.of( "cd /opt/app", "./health-check.sh", "tail -n 20 /var/log/app.log" ) ); ``` ### Environment Variables Pass remote environment variables through the map overload: ```java title="SshCommandWithEnv.java" String output = remote.performTerminalCommand( "sh -c 'printf %s $APP_ENV'", Map.of("APP_ENV", "staging") ); ``` The SSH server must allow the variable names you send. OpenSSH servers typically require `AcceptEnv` entries in `sshd_config` (for example `AcceptEnv APP_ENV`). If the server rejects env requests, the command may run without the variable set. ### Verbose Streaming Use the six-argument facade overload to log each output line while the remote command is still running: ```java title="VerboseRemoteTerminal.java" TerminalActions remote = SHAFT.CLI.remoteTerminal( "192.168.1.100", 22, "deploy-user", "~/.ssh/", "id_rsa", true ); ``` --- ## File Transfer `TerminalActions` includes SFTP helpers for transferring files between the remote server and the local machine. Both methods return the destination path for assertions. ### Upload a Local File ```java title="SshUploadFile.java" String remotePath = remote.uploadFile( "target/build/app.jar", "/tmp/app.jar" ); // remotePath == "/tmp/app.jar" ``` Upload resolves the local source path through SHAFT file helpers. The remote destination must be writable by the SSH user. ### Download a Remote File ```java title="SshDownloadFile.java" String localPath = remote.downloadFile( "/var/log/app/application.log", "target/logs/application.log" ); // localPath is the absolute local destination path ``` When the local parent directory does not exist, SHAFT creates it before the download starts. ### Compute a Remote File Checksum Use checksums to verify file integrity after deployments or transfers: ```java title="SshFileChecksum.java" String checksum = SHAFT.CLI.file().getFileChecksum( remote, "/opt/app/", "build.jar" ); SHAFT.Validations.assertThat() .object(checksum) .isEqualTo(expectedChecksum) .withCustomReportMessage("Deployed JAR checksum must match the expected build artifact"); ``` --- ## Port Forwarding Port forwards are tied to the reusable SSH session and are removed automatically when `quit()` runs. ### Local Port Forward Forward a local port through the SSH session to a remote host/port as seen from the SSH server: ```java title="ForwardLocalPort.java" // Use 0 to let JSch assign an available local port String localPort = remote.forwardLocalPort(0, "127.0.0.1", 8080); ``` The return value is the bound local port as a string. ### Remote Port Forward Forward a remote port on the SSH server to a local host/port as seen from the SSH client machine: ```java title="ForwardRemotePort.java" String remotePort = remote.forwardRemotePort(9000, "127.0.0.1", 3000); ``` Remote port forwarding may require server-side SSH configuration (`GatewayPorts`, `AllowTcpForwarding`, etc.). --- ## Timeouts and Keep-Alive Two timeout properties affect reusable remote SSH sessions: | Property | Default | Unit | Effect | |---|---:|---|---| | `shellSessionTimeout` | `30` | minutes | JSch channel timeout for SFTP/exec channels and local shell commands | | `sshServerAliveInterval` | `60` | seconds | JSch `ServerAliveInterval`; values ` environmentVariables)` | Same, with environment variables. | | `performSshCommands(List commands, Map environmentVariables)` | Runs several commands in order on the same session and returns one result per command. | | `executeTerminalCommand(String command)` | Runs a terminal command and returns the terminal for fluent chaining. | | `getJschSession()` | Returns the underlying JSch `Session` for advanced use. | ```java TerminalActions remote = TerminalActions.getRemoteInstance("10.0.0.5", 22, "qa", "keys", "id_rsa"); SshCommandResult result = remote.performSshCommand("uname -a"); ``` ## Related - [Terminal Actions](./Terminal_Actions.md) — Local terminal execution overview and common patterns. - [Docker Terminal](./Docker_Terminal.md) — Execute commands inside running Docker containers. - [File Actions](./File_Actions.md) — Copy files between local and remote/container filesystems. --- # Terminal Actions Canonical HTML: https://shafthq.github.io/docs/reference/actions/CLI/Terminal_Actions Markdown: https://shafthq.github.io/md/reference/actions/CLI/Terminal_Actions.md ## Getting a Terminal Instance Use `SHAFT.CLI.terminal()` to get a `TerminalActions` instance: ```java title="TerminalSetup.java" TerminalActions terminal = SHAFT.CLI.terminal(); ``` You can also instantiate directly for legacy projects: `new TerminalActions()`. ## Execute a Single Command ### performTerminalCommand() Executes a terminal command and returns the standard output as a string. ```java title="SingleCommand.java" TerminalActions terminal = SHAFT.CLI.terminal(); String output = terminal.performTerminalCommand("echo Hello SHAFT"); SHAFT.Validations.assertThat().object(output).contains("Hello SHAFT"); ``` **Linux / macOS:** ```java title="LinuxCommands.java" String files = terminal.performTerminalCommand("ls -la /tmp"); String diskUse = terminal.performTerminalCommand("df -h /"); String uptime = terminal.performTerminalCommand("uptime"); ``` **Windows:** ```java title="WindowsCommands.java" String dir = terminal.performTerminalCommand("dir C:\\"); String processes = terminal.performTerminalCommand("tasklist"); ``` ## Execute Multiple Commands ### performTerminalCommands() Executes a list of commands in sequence and returns the combined output. ```java title="MultipleCommands.java" TerminalActions terminal = SHAFT.CLI.terminal(); List commands = Arrays.asList( "mkdir -p /tmp/shaft_test", "echo 'test data' > /tmp/shaft_test/data.txt", "cat /tmp/shaft_test/data.txt" ); String output = terminal.performTerminalCommands(commands); SHAFT.Validations.assertThat().object(output).contains("test data"); ``` ## Common Use Cases ### Test Environment Setup and Teardown ```java title="EnvironmentLifecycle.java" public class DatabaseSeedTest { private TerminalActions terminal; @BeforeClass public void setUp() { terminal = SHAFT.CLI.terminal(); // Seed the database before the test class terminal.performTerminalCommand("psql -U testuser -d testdb -f src/test/resources/seed.sql"); } @Test public void verifyRecordsWereSeeded() { String result = terminal.performTerminalCommand("psql -U testuser -d testdb -c \"SELECT COUNT(*) FROM users;\""); SHAFT.Validations.assertThat().object(result).contains("10"); } @AfterClass public void tearDown() { // Clean up test data terminal.performTerminalCommand("psql -U testuser -d testdb -c \"DELETE FROM users WHERE email LIKE '%test%';\""); } } ``` ### Build and Deployment Validation ```java title="DeploymentTest.java" public class DeploymentTest { @Test public void applicationDeployedSuccessfully() { TerminalActions terminal = SHAFT.CLI.terminal(); // Trigger deployment String deployOutput = terminal.performTerminalCommand("./scripts/deploy.sh staging"); SHAFT.Validations.assertThat() .object(deployOutput) .contains("Deployment successful") .withCustomReportMessage("Deploy script should report success"); // Verify the service is running String healthCheck = terminal.performTerminalCommand("curl -s -o /dev/null -w '%{http_code}' http://localhost:8080/health"); SHAFT.Validations.assertThat() .object(healthCheck) .isEqualTo("200") .withCustomReportMessage("Application health endpoint should return HTTP 200"); } } ``` ### File System Validation ```java title="FileSystemValidation.java" public class FileSystemTest { @Test public void exportGeneratesCorrectFile() { TerminalActions terminal = SHAFT.CLI.terminal(); // Trigger the export via your application's API or UI … // Then validate the generated file String exists = terminal.performTerminalCommand("test -f /tmp/export.csv && echo 'found' || echo 'missing'"); SHAFT.Validations.assertThat().object(exists).contains("found"); String lineCount = terminal.performTerminalCommand("wc -l < /tmp/export.csv"); SHAFT.Validations.assertThat() .object(lineCount.trim()).isNotNull(); } } ``` ## Cross-Platform Compatibility | OS | Shell | Example command | |----|-------|----------------| | **Linux / macOS** | bash | `ls -la`, `chmod 755 script.sh`, `./deploy.sh` | | **Windows** | PowerShell | `dir`, `tasklist`, `Get-Process` | When writing cross-platform tests, guard with a platform check: ```java title="CrossPlatformCommand.java" String os = System.getProperty("os.name").toLowerCase(); String listCmd = os.contains("win") ? "dir" : "ls -la"; String output = terminal.performTerminalCommand(listCmd); ``` ## Timeouts and Reporting Local commands use `shellSessionTimeout`; timed-out processes are destroyed and reported as failures. Returned output stays unchanged for assertions, while command/report logs redact common secrets such as tokens, passwords, bearer headers, and URI credentials. Use the environment-variable overload when a command needs secret input: ```java title="CommandEnvironment.java" String output = SHAFT.CLI.terminal().performTerminalCommand( "echo $env:DEPLOY_TOKEN", Map.of("DEPLOY_TOKEN", System.getenv("DEPLOY_TOKEN")) ); ``` ## Best Practices - **Always capture and validate output** — don't fire-and-forget; check the command succeeded. - **Use absolute paths** — relative paths depend on the working directory and are fragile. - **Avoid secrets in commands** — use environment variables rather than inline passwords. - **Clean up after yourself** — delete temporary files and directories in `@AfterMethod` / `@AfterClass`. - **Check for error keywords** — scan output for `error`, `failed`, or non-zero exit signals. ## Related - [File Actions](/docs/reference/actions/CLI/File_Actions) - [Docker Terminal](/docs/reference/actions/CLI/Docker_Terminal) - [CLI](/docs/testing/cli) --- # Test Artifacts and Report Paths Canonical HTML: https://shafthq.github.io/docs/reference/guides/Test_Artifacts Markdown: https://shafthq.github.io/md/reference/guides/Test_Artifacts.md After a test run, SHAFT generates several artifacts that you can use for debugging, reporting, and auditing. This page explains where to find them and how to publish them from your CI/CD pipeline. --- ## Artifact Locations | Artifact | Path | Description | |---|---|---| | **Allure results** | `allure-results/` | Raw Allure result files (JSON, attachments) | | **Allure report** | `allure-report/` | Generated HTML report (after `generate_allure_report.sh`) | | **Allure archive** | `allure-report-archive/` | Portable ZIP when `allure.generateArchive=true` | | **Execution summary** | `execution-summary/` | Lightweight HTML summary report | | **Screenshots** | Attached inside `allure-results/` | Captured on failures by default; validation and every-action screenshots depend on `evidenceLevel` or granular screenshot policy | | **Source code** | HTML attached to an action or validation inside `allure-results/` | Opt in with [`whenToAttachSourceCode`](/docs/reference/reporting#source-code-attachments) to see the Java call site and two surrounding lines on each side | | **Videos** | Generated under `video.folder`, attached inside `allure-results/` | When `evidenceLevel=CUSTOM` and `videoParams_recordVideo=true`, or when `evidenceLevel=FULL` | | **Animated GIFs** | Generated under `video.folder`, attached inside `allure-results/` | When `evidenceLevel=CUSTOM` and `createAnimatedGif=true`, when `evidenceLevel=FULL`, or on retry evidence capture | | **Failure trace viewer** | Attached inside `allure-results/` | `shaft-trace.zip` on failed tests by default; the archive contains `SHAFT Trace Report.html` and `shaft-trace.json` | | **Failure brief** | Attached inside `allure-results/` | `SHAFT Failure Brief.html`, `shaft-failure-brief.json`, and `shaft-attachments-manifest.json` on failed or broken tests | :::info All paths are relative to your project root directory. ::: --- ## Allure Report The Allure report is the **primary artifact** from every SHAFT test run. It contains: - Step-by-step action logs - Screenshots according to the selected evidence profile - Video recordings and animated GIFs (when enabled) - Failure trace viewer attachments for failed tests - Failure brief and attachment manifest for first-pass triage - Test history and trend graphs across multiple runs - Environment metadata ### Generating the Report Locally SHAFT writes convenience scripts to your project root on first run: ```bash title="macOS / Linux" ./generate_allure_report.sh ``` ```bash title="Windows" generate_allure_report.bat ``` ### Publishing to CI/CD With GitHub Actions v7+, you can upload Allure HTML files directly without archiving. See the [Publishing Artifacts in CI/CD](#publishing-artifacts-in-cicd) section below for examples. --- ## Execution Summary The execution summary is a lightweight, fast HTML report. Enable it with: ```properties title="src/main/resources/properties/custom.properties" openExecutionSummaryReportAfterExecution=true ``` The generated file is written to the `execution-summary/` directory. --- ## Publishing Artifacts in CI/CD ### GitHub Actions ```yaml title=".github/workflows/tests.yml" - name: Upload Allure report if: always() uses: actions/upload-artifact@v7 with: name: allure-report path: allure-report/*.html archive: false if-no-files-found: ignore - name: Upload execution summary if: always() uses: actions/upload-artifact@v7 with: name: execution-summary path: execution-summary/ ``` ### Jenkins ```groovy title="Jenkinsfile" post { always { archiveArtifacts artifacts: 'allure-report-archive/**', allowEmptyArchive: true archiveArtifacts artifacts: 'execution-summary/**', allowEmptyArchive: true } } ``` :::tip Always use `if: always()` (GitHub Actions) or `post { always { } }` (Jenkins) so that artifacts are uploaded even when tests fail — that is when you need them the most. ::: --- ## Controlling What Gets Captured | Property | Default | Description | |---|---|---| | `evidenceLevel` | `FAILURE_ONLY` | Profile-level evidence control. Profiles except `CUSTOM` override the granular controls below. | | `screenshotParams_whenToTakeAScreenshot` | `ValidationPointsOnly` | Granular screenshot policy: `Always`, `ValidationPointsOnly`, `FailuresOnly`, or `Never` | | `videoParams_recordVideo` | `false` | Granular video policy | | `createAnimatedGif` | `false` | Granular GIF policy; retry attempts can enable GIFs automatically | | `allure.generateArchive` | `false` | Generate a portable ZIP archive of the report | | `shaft.trace.mode` | `auto` | Trace mode: `auto` (retry-aware—resolves to `retry` when retries are configured, otherwise `failure`), `failure`, `retry`, or `always` | | `shaft.trace.retainFailedAttempts` | `true` | Keep failed-attempt trace bundles (`shaft-trace-attempt- .zip`) when retries are enabled so a passing retry never deletes flake evidence | For the full list of reporting properties, see the [Properties List](/docs/reference/properties/PropertiesList). ## Related - [Solution Design](/docs/reference/guides/Solution_Design) - [Test Structure](/docs/reference/guides/Test_Structure) - [Ci Cd Integration](/docs/reference/guides/CI_CD_Integration) - [Quick Start](/docs/start/quick-start) --- # Test Automation Solution Design Patterns Canonical HTML: https://shafthq.github.io/docs/reference/guides/Solution_Design Markdown: https://shafthq.github.io/md/reference/guides/Solution_Design.md How you design your test automation solution determines its readability, maintainability, and scalability. This page compares the most common patterns and shows how each works with SHAFT Engine. --- ## Page Object Model (POM) The **Page Object Model** encapsulates each page (or component) of your application in a dedicated class. Locators and actions live together, and test classes call page object methods. ```java title="LoginPage.java" public class LoginPage { private final SHAFT.GUI.WebDriver driver; private final By usernameInput = SHAFT.GUI.Locator.hasAnyTagName().hasId("username").build(); private final By passwordInput = SHAFT.GUI.Locator.hasAnyTagName().hasId("password").build(); private final By loginButton = SHAFT.GUI.Locator.hasAnyTagName().hasId("login-btn").build(); public LoginPage(SHAFT.GUI.WebDriver driver) { this.driver = driver; } public void navigateTo() { driver.browser().navigateToURL("https://example.com/login"); } public void login(String username, String password) { driver.element() .type(usernameInput, username) .and().type(passwordInput, password) .and().click(loginButton); } } ``` ```java title="LoginTest.java" @Test(description = "Verify login with valid credentials") public void testValidLogin() { LoginPage loginPage = new LoginPage(driver); loginPage.navigateTo(); loginPage.login("admin", "password123"); } ``` :::tip **POM is the most widely used pattern** and is recommended as the default approach. It provides a clean separation between test logic and page structure. ::: --- ## Fluent Page Object (Method Chaining) A variation of POM where page object methods return `this`, enabling method chaining: ```java title="FluentLoginPage.java" public class FluentLoginPage { private final SHAFT.GUI.WebDriver driver; private final By usernameInput = SHAFT.GUI.Locator.hasAnyTagName().hasId("username").build(); private final By passwordInput = SHAFT.GUI.Locator.hasAnyTagName().hasId("password").build(); private final By loginButton = SHAFT.GUI.Locator.hasAnyTagName().hasId("login-btn").build(); public FluentLoginPage(SHAFT.GUI.WebDriver driver) { this.driver = driver; } public FluentLoginPage navigateTo() { driver.browser().navigateToURL("https://example.com/login"); return this; } public FluentLoginPage enterUsername(String username) { driver.element().type(usernameInput, username); return this; } public FluentLoginPage enterPassword(String password) { driver.element().type(passwordInput, password); return this; } public FluentLoginPage clickLogin() { driver.element().click(loginButton); return this; } } ``` ```java title="FluentLoginTest.java" @Test(description = "Verify login with fluent page objects") public void testValidLogin() { new FluentLoginPage(driver) .navigateTo() .enterUsername("admin") .enterPassword("password123") .clickLogin(); } ``` --- ## Anonymous / Inline Style For simple tests or quick prototypes, you can skip page objects entirely and call SHAFT actions directly in the test: ```java title="InlineTest.java" @Test(description = "Quick login verification") public void testLogin() { driver.browser().navigateToURL("https://example.com/login"); driver.element() .type(By.id("username"), "admin") .and().type(By.id("password"), "password123") .and().click(By.id("login-btn")); driver.assertThat(By.id("welcome")) .text().contains("Welcome, admin"); } ``` :::warning The anonymous style is convenient for small tests and prototypes, but it does not scale well. Locators are duplicated, and changes to the UI require updating every test that references the affected elements. ::: --- ## Base Class with Inheritance A common pattern for sharing setup, teardown, and utility methods across test classes: ```java title="BaseTest.java" public abstract class BaseTest { protected SHAFT.GUI.WebDriver driver; @BeforeMethod public void baseSetup() { driver = new SHAFT.GUI.WebDriver(); } @AfterMethod public void baseTeardown() { driver.quit(); } } ``` ```java title="LoginTest.java" public class LoginTest extends BaseTest { @Test(description = "Verify login with valid credentials") public void testValidLogin() { LoginPage loginPage = new LoginPage(driver); loginPage.navigateTo(); loginPage.login("admin", "password123"); } } ``` :::tip Keep your base class **thin** — only shared setup/teardown and truly universal utilities. Avoid putting test-specific logic in the base class. ::: --- ## Pattern Comparison | Pattern | Readability | Reusability | Maintenance | Best For | |---|---|---|---|---| | **Page Object Model** | High | High | Low cost | Most projects (recommended default) | | **Fluent Page Object** | Very high | High | Low cost | Teams that value readable test code | | **Anonymous / Inline** | Moderate | None | High cost | Prototypes, one-off tests | | **Base Class + Inheritance** | High | Moderate | Moderate | Shared setup across test classes | --- ## Combining Patterns In practice, most teams combine several patterns: 1. **Base class** for shared driver setup/teardown. 2. **Page Object Model** for page-level abstractions. 3. **Fluent chaining** within page object methods (using SHAFT's built-in fluent API). 4. **Anonymous style** for quick, isolated tests that do not warrant a full page object. ```java title="CombinedExample.java" // Base class provides driver lifecycle public class CheckoutTest extends BaseTest { @Test(description = "Complete checkout flow") public void testCheckout() { // POM for structured page interactions new LoginPage(driver).navigateTo().login("user", "pass"); new ProductPage(driver).addToCart("Laptop"); // Inline for simple assertions driver.assertThat(By.id("cart-count")).text().isEqualTo("1"); } } ``` --- ## Best Practices 1. **Start with POM** — it is the industry standard and scales well. 2. **Use a base class** for driver setup/teardown, but keep it thin. 3. **Use SHAFT's fluent API** inside your page object methods for readability. 4. **Avoid deep inheritance hierarchies** — prefer composition over inheritance when sharing behavior between page objects. 5. **Choose one primary pattern and be consistent** across your project — mixing too many patterns creates confusion. ## Related - [Test Structure](/docs/reference/guides/Test_Structure) - [Ci Cd Integration](/docs/reference/guides/CI_CD_Integration) - [Quick Start](/docs/start/quick-start) --- # Test Automation Tool Selection Criteria Canonical HTML: https://shafthq.github.io/docs/reference/guides/Tool_Selection Markdown: https://shafthq.github.io/md/reference/guides/Tool_Selection.md Choosing the right test automation tool is a strategic decision that affects your team's productivity, test reliability, and long-term maintenance costs. This page outlines the key criteria to evaluate and how SHAFT Engine addresses each one. --- ## Key Selection Criteria ### 1. Technology Coverage Does the tool support all the types of testing your application requires? | Requirement | What to Look For | |---|---| | Web UI | Browser automation (Selenium-based) | | Mobile | Native and hybrid app testing (Appium-based) | | API | REST and SOAP API testing | | CLI | Terminal command execution and file operations | | Database | Query execution and data validation | :::tip SHAFT Engine covers **all five areas** with a unified API — `SHAFT.GUI.WebDriver`, `SHAFT.API`, `SHAFT.CLI`, and `SHAFT.DB`. You do not need to learn multiple frameworks or manage separate dependencies. ::: ### 2. Language and Ecosystem - Does the tool use a language your team already knows? - Is it compatible with your build tools (Maven, Gradle)? - Does it integrate with your test runner (TestNG, JUnit)? SHAFT is built on **Java** and integrates natively with **Maven**, **TestNG**, and **JUnit**. ### 3. Reporting - Does the tool produce rich, actionable reports? - Can reports be shared with non-technical stakeholders? - Is report generation automatic or does it require additional setup? SHAFT includes **Allure reporting** out of the box — with screenshots, videos, step-by-step logs, and BDD-style annotations. See [BDD-Style Reports](BDD_Style_Reports) for details. ### 4. CI/CD Integration - Can tests run headlessly in a pipeline? - Can configuration be overridden from the command line? - Is there built-in support for generating portable artifacts? SHAFT supports [headless execution and CLI-based property overrides](CI_CD_Integration) for direct pipeline integration. ### 5. Maintenance Cost - How much boilerplate code does the framework require? - Does it handle common issues automatically (waits, retries, scrolling)? - How easy is it to update when the application changes? SHAFT handles **automatic waits, smart scrolling, element retry, and screenshot capture** — reducing boilerplate and maintenance overhead. ### 6. Learning Curve - How long does it take a new team member to write their first test? - Is the documentation clear and complete? - Are there working examples and templates? SHAFT provides a [one-command project setup](/docs/start/installation), [quick-start guide](/docs/start/quick-start), and a fluent API designed for readability. ### 7. Community and Support - Is the tool actively maintained? - Is there a community for questions and support? - Is the source code open for inspection and contribution? SHAFT is [open-source on GitHub](https://github.com/ShaftHQ/SHAFT_ENGINE) with active development and [community support](/docs/start/overview). ### 8. Scalability - Can tests run in parallel? - Does the tool support remote execution (Selenium Grid, cloud providers)? - Can it handle large test suites efficiently? SHAFT supports [parallel execution](/docs/reference/configuration/parallelExecution), remote Grid execution, and cloud providers such as BrowserStack. ```java title="ProofOfConceptTest.java" SHAFT.GUI.WebDriver driver = new SHAFT.GUI.WebDriver(); driver.browser().navigateToURL("https://duckduckgo.com"); driver.element().type(By.name("q"), "SHAFT Engine"); driver.assertThat().browser().title().contains("DuckDuckGo"); driver.quit(); ``` --- ## Comparison at a Glance | Criteria | SHAFT Engine | Raw Selenium + REST Assured | Commercial Tools | |---|---|---|---| | Web UI | ✅ Built-in | ✅ Manual setup | ✅ Built-in | | Mobile | ✅ Built-in | ⚠️ Separate Appium setup | Varies | | API | ✅ Built-in | ✅ Separate library | ✅ Built-in | | CLI / DB | ✅ Built-in | ❌ Custom code | Varies | | Reporting | ✅ Allure (automatic) | ⚠️ Manual Allure setup | ✅ Built-in | | Smart waits/retries | ✅ Automatic | ❌ Manual implementation | ✅ Built-in | | CI/CD support | ✅ CLI properties | ✅ Manual config | ✅ Built-in | | Cost | Free (open-source) | Free (open-source) | License fees | --- ## Best Practices 1. **Evaluate against your actual requirements** — do not choose a tool because it is popular; choose it because it fits your technology stack and testing needs. 2. **Prioritize maintenance cost** — the tool you use on day one is less important than the tool that keeps your team productive on day 1,000. 3. **Run a proof of concept** — try the top two candidates against a real test scenario from your application before committing. 4. **Consider the full stack** — a tool that covers web, mobile, API, and CLI with one API reduces context switching and training time. ## Related - [Quick start](/docs/start/quick-start) - [Features and modules](/docs/features/modules) - [Solution design](/docs/reference/guides/Solution_Design) --- # Test Data Management Canonical HTML: https://shafthq.github.io/docs/reference/actions/TestData_Management Markdown: https://shafthq.github.io/md/reference/actions/TestData_Management.md ## Overview Test data management is a critical aspect of test automation. SHAFT Engine supports managing test data across different file formats, making it easy to maintain, scale, and share test data across your automation projects. SHAFT Engine supports the following test data formats: - **JSON** - Ideal for structured data, API payloads, and complex nested data - **CSV** - Perfect for simple tabular data and bulk test inputs - **Excel (XLS/XLSX)** - Great for organizing test scenarios with multiple sheets - **Properties** - Suitable for configuration data and key-value pairs ## Why Use External Test Data? Using external test data files offers several advantages: - **Separation of Concerns**: Keep test data separate from test logic - **Maintainability**: Update test data without modifying code - **Reusability**: Share test data across multiple test cases - **Collaboration**: Non-technical team members can manage test data - **Version Control**: Track changes to test data over time - **Data-Driven Testing**: Run the same test with different data sets --- ## JSON Test Data JSON (JavaScript Object Notation) is ideal for structured data with hierarchical relationships and is commonly used for API testing and complex test scenarios. ### Creating a JSON Test Data File Create a JSON file in your test resources directory (e.g., `src/test/resources/testData/`): ```json title="TestData.json" { "login": { "email": "user@example.com", "password": "SecurePass123!", "welcomeText": "Welcome back!" }, "search": { "query": "SHAFT Engine Test Automation", "expectedResults": "SHAFT Engine" }, "user": { "firstName": "John", "lastName": "Doe", "age": 30, "email": "john.doe@example.com" } } ``` ### Using JSON Test Data ```java public class JSONTestDataExample { private SHAFT.TestData.JSON testData; @BeforeClass public void setupTestData() { // Load the JSON file testData = new SHAFT.TestData.JSON("testData/TestData.json"); } @Test public void loginTest() { SHAFT.GUI.WebDriver driver = new SHAFT.GUI.WebDriver(); // Access test data using dot notation for nested keys String email = testData.getTestData("login.email"); String password = testData.getTestData("login.password"); String welcomeText = testData.getTestData("login.welcomeText"); driver.browser().navigateToURL("https://example.com/login") .and().element().type(By.id("email"), email) .and().element().type(By.id("password"), password) .and().element().click(By.id("loginButton")) .and().assertThat(By.className("welcome")).text().contains(welcomeText); driver.quit(); } @Test public void searchTest() { SHAFT.GUI.WebDriver driver = new SHAFT.GUI.WebDriver(); String searchQuery = testData.getTestData("search.query"); String expectedResults = testData.getTestData("search.expectedResults"); driver.browser().navigateToURL("https://duckduckgo.com/") .and().element().type(By.name("q"), searchQuery) .and().element().click(By.cssSelector("button[type='submit']")) .and().assertThat(By.id("links")).text().contains(expectedResults); driver.quit(); } } ``` ### JSON with Arrays You can also work with arrays in JSON: ```json title="Users.json" { "users": [ { "username": "user1", "password": "pass1" }, { "username": "user2", "password": "pass2" } ] } ``` ```java // Access array elements using index String firstUser = testData.getTestData("users[0].username"); String firstPassword = testData.getTestData("users[0].password"); ``` ### Advantages of JSON - ✅ Supports nested/hierarchical data structures - ✅ Easy to read and understand - ✅ Perfect for API request/response data - ✅ Widely supported and standard format - ✅ Can represent complex data types (arrays, objects) ### Best Practices for JSON - Use meaningful keys that describe the data - Group related data together using nested objects - Keep the structure flat when possible for easier access - Add comments in the test code to explain complex data structures - Validate JSON syntax using online validators or IDE plugins --- ## CSV Test Data CSV (Comma-Separated Values) is perfect for simple tabular data and is easy to create and edit using spreadsheet applications like Excel or Google Sheets. ### Creating a CSV Test Data File Create a CSV file in your test resources directory: ```csv title="LoginData.csv" username,password,expectedMessage user1@test.com,Password123!,Login successful user2@test.com,SecurePass456,Welcome back admin@test.com,AdminPass789,Admin dashboard ``` ### Using CSV Test Data ```java public class CSVTestDataExample { private SHAFT.TestData.CSV testData; @BeforeClass public void setupTestData() { // Load the CSV file testData = new SHAFT.TestData.CSV("testData/LoginData.csv"); } @Test public void loginWithFirstUser() { SHAFT.GUI.WebDriver driver = new SHAFT.GUI.WebDriver(); // Get data from specific row and column (0-indexed) // Row 0 is the header, so data starts from row 1 String username = testData.getCellData(1, 0); // Row 1, Column 0 String password = testData.getCellData(1, 1); // Row 1, Column 1 String expectedMessage = testData.getCellData(1, 2); // Row 1, Column 2 driver.browser().navigateToURL("https://example.com/login") .and().element().type(By.id("username"), username) .and().element().type(By.id("password"), password) .and().element().click(By.id("loginButton")) .and().assertThat(By.className("message")).text().isEqualTo(expectedMessage); driver.quit(); } } ``` ### Data-Driven Testing with CSV Use CSV files for data-driven testing with TestNG DataProvider: ```java public class CSVDataDrivenTest { private SHAFT.TestData.CSV testData; @BeforeClass public void setupTestData() { testData = new SHAFT.TestData.CSV("testData/LoginData.csv"); } @DataProvider(name = "loginData") public Object[][] getLoginData() { int rowCount = testData.getRowCount(); Object[][] data = new Object[rowCount - 1][3]; // Exclude header row for (int i = 1; i items = testData.getListAs("items", String.class); ``` ### Advantages of YAML - Human-readable with minimal syntax noise - Supports comments (`#`) — helpful for documenting test data - Handles nested structures and lists natively - Ideal replacement for properties files when you need hierarchy - Works with SHAFT's type-safe API ### Best Practices for YAML - Use `.yaml` (not `.yml`) extension for consistency with SHAFT conventions - Store YAML files under `src/test/resources/testData/` - Keep sensitive values (passwords, tokens) in environment variables or encrypted files - Use comments in YAML to explain the purpose of each section --- ## Comparison of Test Data Formats | Feature | JSON | CSV | Excel | Properties | YAML | |:--------|:-----|:----|:------|:-----------|:-----| | **Structure** | Hierarchical | Tabular | Tabular | Key-Value | Hierarchical | | **Complexity** | High | Low | Medium | Low | Medium | | **Best For** | APIs, Complex data | Bulk data | Test scenarios | Configuration | Config & nested data | | **Readability** | Good | Excellent | Excellent | Excellent | Excellent | | **Non-tech Friendly** | Medium | High | High | High | High | | **Multi-sheet Support** | N/A | No | Yes | No | No | | **Nested Data** | Yes | No | No | No | Yes | | **File Size** | Small | Small | Large | Small | Small | | **Supports Comments** | No | No | No | No | Yes | | **Edit Tools** | Text editor, IDE | Excel, Text editor | Excel | Text editor | Text editor, IDE | --- ## Best Practices for Test Data Management ### 1. Organize Test Data Files ``` src/test/resources/testData/ ├── json/ │ ├── users.json │ ├── products.json │ └── orders.json ├── csv/ │ ├── login-credentials.csv │ └── test-users.csv ├── excel/ │ └── test-scenarios.xlsx └── properties/ ├── test-dev.properties ├── test-staging.properties └── test-prod.properties ``` ### 2. Separate Test Data from Test Logic ```java // Good Practice @BeforeClass public void setupTestData() { testData = new SHAFT.TestData.JSON("testData/users.json"); } @Test public void loginTest() { String email = testData.getTestData("user.email"); // Use email in test } // Avoid hardcoding data in tests @Test public void loginTest() { String email = "hardcoded@test.com"; // Bad practice } ``` ### 3. Use Meaningful Names ```java // Good testData.getTestData("login.validUser.email"); testData.getTestData("checkout.invalidCard.number"); // Not ideal testData.getTestData("data1"); testData.getTestData("test"); ``` ### 4. Version Control Your Test Data - Commit test data files to version control - Track changes to test data - Use meaningful commit messages for data changes - Review test data changes in pull requests ### 5. Handle Sensitive Data Securely ```java // Don't store passwords in plain text // Use environment variables or secure vaults @Test public void loginTest() { String username = testData.getTestData("user.username"); String password = System.getenv("TEST_USER_PASSWORD"); // From env variable } ``` ### 6. Keep Test Data Clean - Remove obsolete test data regularly - Update test data when application changes - Use consistent data formats - Document complex test data structures ### 7. Load Test Data Efficiently ```java // Good - Load once per test class private SHAFT.TestData.JSON testData; @BeforeClass public void setupTestData() { testData = new SHAFT.TestData.JSON("testData.json"); } // Avoid - Loading in every test method @Test public void test1() { SHAFT.TestData.JSON data = new SHAFT.TestData.JSON("testData.json"); // Inefficient } ``` --- ## Common Use Cases ### Use Case 1: Multi-Language Testing ```json title="messages.json" { "en": { "welcome": "Welcome", "logout": "Logout" }, "es": { "welcome": "Bienvenido", "logout": "Cerrar sesión" }, "fr": { "welcome": "Bienvenue", "logout": "Déconnexion" } } ``` ```java @Test(dataProvider = "languages") public void testInMultipleLanguages(String language) { String welcomeMessage = testData.getTestData(language + ".welcome"); // Verify UI displays correct message } ``` ### Use Case 2: Cross-Browser Testing with Test Data ```java public class CrossBrowserTestWithData { private ThreadLocal driver = new ThreadLocal<>(); private SHAFT.TestData.JSON testData; @BeforeClass public void setupTestData() { testData = new SHAFT.TestData.JSON("testData/users.json"); } @Test public void loginTest() { driver.get().browser().navigateToURL(testData.getTestData("app.url")) .and().element().type(By.id("email"), testData.getTestData("user.email")) .and().element().type(By.id("password"), testData.getTestData("user.password")) .and().element().click(By.id("loginButton")) .and().assertThat(By.className("welcome")).text() .contains(testData.getTestData("user.welcomeMessage")); } @BeforeMethod public void setup() { driver.set(new SHAFT.GUI.WebDriver()); } @AfterMethod public void teardown() { driver.get().quit(); } } ``` ### Use Case 3: API Testing with JSON Test Data ```json title="api-test-data.json" { "createUser": { "name": "John Doe", "email": "john@example.com", "age": 30 }, "updateUser": { "name": "Jane Doe", "email": "jane@example.com" } } ``` ```java @Test public void createUserTest() { SHAFT.API api = new SHAFT.API("https://api.example.com"); String requestBody = String.format( "{\"name\":\"%s\",\"email\":\"%s\",\"age\":%s}", testData.getTestData("createUser.name"), testData.getTestData("createUser.email"), testData.getTestData("createUser.age") ); api.post("/users") .setRequestBody(requestBody) .setTargetStatusCode(201); } ``` ### Use Case 4: Database Testing with Properties ```properties title="db-config.properties" db.url=jdbc:mysql://localhost:3306/testdb db.username=testuser db.password=testpass db.driver=com.mysql.cj.jdbc.Driver ``` ```java @Test public void databaseTest() throws Exception { String dbUrl = testData.getProperty("db.url"); String username = testData.getProperty("db.username"); String password = testData.getProperty("db.password"); SHAFT.DB database = new SHAFT.DB(dbUrl, username, password); // Perform database operations } ``` --- ## Troubleshooting ### File Not Found **Problem**: `FileNotFoundException` when loading test data. **Solution**: - Verify file path is correct relative to `src/test/resources/` - Check file name spelling and extension - Ensure file is in the correct directory - Use absolute path for debugging: `new File("src/test/resources/testData.json").getAbsolutePath()` ### Invalid JSON Format **Problem**: JSON parsing errors. **Solution**: - Validate JSON syntax using online validators (jsonlint.com) - Check for missing commas, brackets, or quotes - Use IDE plugins for JSON validation - Ensure proper escaping of special characters ### CSV Encoding Issues **Problem**: Special characters not displayed correctly. **Solution**: - Save CSV files with UTF-8 encoding - Avoid special characters in data when possible - Use Excel's "CSV UTF-8" format when saving ### Excel File Locked **Problem**: Cannot read Excel file. **Solution**: - Close the Excel file before running tests - Don't open Excel files while tests are running - Use copies of Excel files for test runs --- ## Summary SHAFT Engine provides these test data management capabilities: 1. **JSON**: Best for structured, hierarchical data and API testing 2. **CSV**: Ideal for simple tabular data and bulk test inputs 3. **Excel**: Perfect for organized test scenarios with multiple sheets 4. **Properties**: Great for configuration and environment-specific data **Key Takeaways**: - Choose the right format for your use case - Keep test data separate from test logic - Use meaningful names and organize files well - Version control your test data - Handle sensitive data securely - Load test data efficiently By following these guidelines and examples, you can build a maintainable test data management strategy for your SHAFT Engine automation projects. --- ## Full test data reader reference | Reader | Methods | | --- | --- | | `SHAFT.TestData.CSV` | `getRows()`, `getColumns()`, `getColumnsWithData()` (column name to row values), `getFirstColumn()`, `getLastColumn()`, `getSpecificColumnName(int)`, `getSpecificColumnData(String or int)`, `getCellCount(String or int)`, `getMaxCellValue(String or int)` and `getMinCellValue(String or int)`. | | `SHAFT.TestData.EXCEL` | `getColumnNameUsingRowNameAndCellData([sheetName,] rowName, cellData)` finds the column holding a value in a row, and `getLastColumnNumber([sheetName])` returns the last header column (zero based). | | `SHAFT.TestData.JSON` | `getTestDataAsJson(String jsonPath)`, `getTestDataAsList(String jsonPath)` and `getTestDataAsMap(String jsonPath)` read an object, list or map at a JSONPath. | | `SHAFT.TestData.YAML` | `getDate(key)`, `getDouble(key)`, `getLong(key)` and `getMapAs(key, Class )`; nested keys use dots, for example `"user.address.city"`. | ## Related - [File Actions](/docs/reference/actions/CLI/File_Actions) - Reading and writing files programmatically - [File validations](/docs/reference/actions/Validations#file-validations) - Validating file content and existence - [API Request Builder](/docs/reference/actions/API/Request_Builder) - Building API requests with test data - [Parallel Execution](/docs/reference/configuration/parallelExecution) - Using test data in parallel tests - [Properties List](/docs/reference/properties/PropertiesList) - SHAFT configuration properties --- # Test Structure: Cases vs. Scenarios Canonical HTML: https://shafthq.github.io/docs/reference/guides/Test_Structure Markdown: https://shafthq.github.io/md/reference/guides/Test_Structure.md How you structure your tests has a major impact on reliability, maintainability, and debugging speed. This page covers the two main approaches — **isolated test cases** and **dependent test scenarios** — and explains why you should avoid using `priority` to control execution order. --- ## Test Cases: Isolated and Independent An **isolated test case** is self-contained. It sets up its own data, performs its action, validates the result, and cleans up — without depending on any other test. ```java title="IsolatedTestCase.java" public class LoginTest { private SHAFT.GUI.WebDriver driver; @BeforeMethod public void setup() { driver = new SHAFT.GUI.WebDriver(); driver.browser().navigateToURL("https://example.com/login"); } @Test(description = "Verify login with valid credentials") public void testValidLogin() { driver.element() .type(By.id("username"), "validUser") .and().type(By.id("password"), "validPass") .and().click(By.id("login-btn")); driver.assertThat(By.id("welcome")).text().contains("Welcome"); } @Test(description = "Verify login with invalid password") public void testInvalidPassword() { driver.element() .type(By.id("username"), "validUser") .and().type(By.id("password"), "wrongPass") .and().click(By.id("login-btn")); driver.assertThat(By.id("error")).text().contains("Invalid credentials"); } @AfterMethod public void teardown() { driver.quit(); } } ``` :::tip **Isolated tests are the default recommendation.** They can run in any order, in parallel, and a failure in one test never cascades to others. ::: --- ## Test Scenarios: Dependent Steps A **test scenario** models a multi-step business flow where each step depends on the previous one. Use `dependsOnMethods` to express this dependency explicitly: ```java title="DependentTestScenario.java" public class CheckoutScenario { private SHAFT.GUI.WebDriver driver; @BeforeClass public void setup() { driver = new SHAFT.GUI.WebDriver(); } @Test(description = "Step 1: Login to the store") public void login() { driver.browser().navigateToURL("https://example.com/login"); driver.element() .type(By.id("username"), "shopper@example.com") .and().type(By.id("password"), "password123") .and().click(By.id("login-btn")); } @Test(description = "Step 2: Add item to cart", dependsOnMethods = "login") public void addToCart() { driver.element().click(By.cssSelector("[data-product='laptop'] .add-to-cart")); driver.assertThat(By.id("cart-count")).text().isEqualTo("1"); } @Test(description = "Step 3: Complete checkout", dependsOnMethods = "addToCart") public void checkout() { driver.element().click(By.id("checkout-btn")); driver.element() .type(By.id("card-number"), "4111111111111111") .and().click(By.id("pay-btn")); driver.assertThat(By.id("confirmation")).text().contains("Order confirmed"); } @AfterClass public void teardown() { driver.quit(); } } ``` When `login` fails, TestNG automatically **skips** `addToCart` and `checkout`, giving you a clear signal that the root cause is in the login step. --- ## Why Not Use `priority`? TestNG's `@Test(priority = N)` attribute controls execution order, but it has important pitfalls: | `priority` | `dependsOnMethods` | |---|---| | Controls order but does **not** skip dependents on failure | Explicitly skips dependent tests when a prerequisite fails | | All tests still run even if early ones fail | Failing early stops the chain — saves time and avoids noise | | Implicit relationship — hard to understand at a glance | Explicit dependency — self-documenting | | Fragile when tests are added or reordered | Stable — dependencies are declared by name | :::warning Using `priority` to order tests creates a hidden dependency. If a test with `priority=1` fails, the test with `priority=2` still runs — often producing confusing, cascading failures that waste debugging time. ::: ### What `priority` Is Good For The `priority` attribute is appropriate **only** for controlling the order of truly independent tests — for example, running smoke tests before regression tests. It should never be used as a substitute for `dependsOnMethods`. --- ## When to Use Each Approach | Approach | Use When | |---|---| | **Isolated test cases** | Testing a single feature, input validation, edge cases, API endpoints | | **Dependent test scenarios** | End-to-end business flows where steps build on each other (e.g., login → add to cart → checkout) | --- ## Best Practices 1. **Default to isolated tests.** They are more reliable, parallelizable, and easier to debug. 2. **Use `dependsOnMethods` for multi-step scenarios** — never `priority`. 3. **Keep scenarios short** — 3 to 5 steps maximum. Longer scenarios are harder to debug. 4. **Use `@BeforeClass` / `@AfterClass` for scenario-level setup/teardown** and `@BeforeMethod` / `@AfterMethod` for isolated test setup/teardown. 5. **Name test methods clearly** so that `dependsOnMethods` references are self-explanatory. ## Related - [Solution Design](/docs/reference/guides/Solution_Design) - [Ci Cd Integration](/docs/reference/guides/CI_CD_Integration) - [Quick Start](/docs/start/quick-start) --- # The Testing Pyramid and Automation Strategy Canonical HTML: https://shafthq.github.io/docs/reference/guides/Testing_Pyramid Markdown: https://shafthq.github.io/md/reference/guides/Testing_Pyramid.md The **testing pyramid** is a foundational concept in test automation strategy. It guides how many tests you should have at each level and where your automation investment delivers the most value. --- ## The Pyramid ``` / UI / E2E \ ← Fewest, slowest, most expensive /───────────────\ / Integration/API \ ← Balanced count, moderate speed /───────────────────\ / Unit Tests \ ← Most numerous, fastest, cheapest /───────────────────────\ ``` | Level | Speed | Cost | Scope | Typical Count | |---|---|---|---|---| | **Unit tests** | Very fast | Low | Single function or class | Thousands | | **Integration / API tests** | Fast | Moderate | Service interactions, API contracts | Hundreds | | **UI / E2E tests** | Slow | High | Full user flows through the UI | Tens to low hundreds | --- ## Why the Pyramid Matters Each level serves a different purpose: ### Unit Tests (Base) - Test individual functions, methods, and classes in isolation. - Run in milliseconds — fast feedback for developers. - Catch logic errors early, before they propagate. - **Owned by developers**, run on every commit. ### Integration / API Tests (Middle) - Test how components interact — API endpoints, database queries, service contracts. - Faster and more stable than UI tests. - Catch issues in data flow, serialization, authentication, and business rules. - **Where SHAFT's API testing shines** — use `SHAFT.API` to test REST endpoints efficiently. ### UI / E2E Tests (Top) - Test the complete user journey through the real application interface. - Catch visual regressions, workflow issues, and integration gaps. - Slowest and most brittle — use sparingly for critical paths. - **Where SHAFT's GUI testing shines** — use `SHAFT.GUI.WebDriver` for web and mobile. :::warning An **inverted pyramid** (more UI tests than unit tests) is a common anti-pattern. It leads to slow test suites, flaky results, and high maintenance costs. Aim for the right balance. ::: --- ## Applying the Pyramid to Your Strategy ### Step 1: Identify What to Automate at Each Level | Question | Level | |---|---| | Does this test validate a single calculation or transformation? | **Unit** | | Does this test verify an API contract, request/response, or service interaction? | **API / Integration** | | Does this test need a real browser or mobile device to verify a user workflow? | **UI / E2E** | ### Step 2: Maximize API Coverage API tests provide the best **cost-to-coverage ratio**. They are fast, stable, and cover the business logic that powers both the web and mobile UIs: ```java title="ApiTest.java" public class ApiTest { private SHAFT.API api; @Test(description = "Verify user creation API") public void testCreateUser() { api = new SHAFT.API("https://api.example.com"); api.post("/users") .setRequestBody("{\"name\": \"John\", \"email\": \"john@example.com\"}") .setContentType("application/json") ; api.assertThatResponse().extractedJsonValue("name").isEqualTo("John"); api.assertThatResponse().extractedJsonValue("email").isEqualTo("john@example.com"); api.verifyThatResponse().statusCode().isEqualTo(201); } } ``` ### Step 3: Reserve UI Tests for Critical Paths Use UI tests only for the most important user journeys — login, checkout, core workflows: ```java title="CriticalPathTest.java" public class CriticalPathTest { private SHAFT.GUI.WebDriver driver; @Test(description = "Verify complete checkout flow") public void testCheckoutFlow() { driver = new SHAFT.GUI.WebDriver(); driver.browser().navigateToURL("https://example.com"); // Login → Browse → Add to cart → Checkout → Confirm // Only the most critical path needs a full UI test } } ``` --- ## Where SHAFT Fits in the Pyramid SHAFT Engine supports **every level** of the pyramid above unit tests: | Level | SHAFT Module | |---|---| | API / Integration | `SHAFT.API` — REST API testing | | CLI / Infrastructure | `SHAFT.CLI` — Terminal and file operations | | Database | `SHAFT.DB` — Database queries and validations | | UI / E2E (Web) | `SHAFT.GUI.WebDriver` — Browser automation | | UI / E2E (Mobile) | `SHAFT.GUI.WebDriver` — Appium-based mobile automation | :::info By using one engine across all levels, you get a **consistent API, unified reporting, and shared configuration** — regardless of what type of test you are writing. ::: --- ## Best Practices 1. **Follow the pyramid shape** — more unit/API tests, fewer UI tests. 2. **Automate API tests first** when building a new automation suite — highest ROI. 3. **Use UI tests for critical paths only** — login, checkout, core business flows. 4. **Push testing down** — if you can verify something with an API test, do not use a UI test. 5. **Review your test distribution regularly** — measure how many tests exist at each level and rebalance. ## Related - [Solution Design](/docs/reference/guides/Solution_Design) - [Test Structure](/docs/reference/guides/Test_Structure) - [Ci Cd Integration](/docs/reference/guides/CI_CD_Integration) - [Quick Start](/docs/start/quick-start) --- # Tips: Infrastructure, Network, and Visual Canonical HTML: https://shafthq.github.io/docs/reference/actions/GUI/Infrastructure_Network_And_Visual Markdown: https://shafthq.github.io/md/reference/actions/GUI/Infrastructure_Network_And_Visual.md Deeper infrastructure, network, and cross-cutting quality tips for running SHAFT against real execution environments. ## Native Selenium WebDriver access Access the native Selenium `WebDriver` instance whenever SHAFT's fluent API doesn't cover a case: ```java title="NativeDriverAccess.java" SHAFT.GUI.WebDriver driver = new SHAFT.GUI.WebDriver(); WebDriver nativeDriver = driver.getDriver(); nativeDriver.findElement(By.id("lname")).sendKeys("Smith"); ``` You can also wrap any existing `org.openqa.selenium.WebDriver` instance with SHAFT capabilities — useful when migrating from plain Selenium incrementally, or when a third-party library hands you a raw driver: ```java title="WrapExistingWebDriver.java" WebDriver rawDriver = new ChromeDriver(); rawDriver.get("https://example.com"); // Wrap it — all SHAFT fluent actions, smart waits, and reporting now apply SHAFT.GUI.WebDriver driver = new SHAFT.GUI.WebDriver(rawDriver); driver.assertThat().browser().title().contains("Example"); ``` :::tip After wrapping, call `driver.quit()` (not `rawDriver.quit()`) so SHAFT's lifecycle hooks run and the Allure report is finalized. ::: ## Custom browser capabilities Pass a `MutableCapabilities` or browser-specific options object directly to the `SHAFT.GUI.WebDriver` constructor to configure anything Selenium supports — arguments, experimental options, proxies, extensions: ```java title="CustomCapabilities.java" ChromeOptions options = new ChromeOptions(); options.addArguments("--disable-extensions", "--disable-notifications", "--no-sandbox"); SHAFT.GUI.WebDriver driver = new SHAFT.GUI.WebDriver(DriverType.CHROME, options); ``` The same pattern applies to `FirefoxOptions` and `EdgeOptions`. Common use cases: a custom download directory (`prefs.put("download.default_directory", ...)`), an HTTP proxy (`options.setProxy(proxy)`), or loading a browser extension (`options.addExtensions(new File(...))`). :::tip Prefer [SHAFT Properties](/docs/reference/properties/PropertyTypes) for ordinary browser configuration; reach for custom capabilities only for browser-specific settings that aren't exposed as SHAFT properties. On Selenium Grid or cloud platforms, custom capabilities are merged with the platform-level capabilities from your properties file. ::: ## Mobile emulation SHAFT supports **Chrome DevTools mobile emulation** to render a page as it would appear on a real device, without Appium or physical hardware — ideal for responsive-layout checks and quick mobile smoke tests: ```java title="MobileEmulation.java" SHAFT.Properties.web.set() .isMobileEmulation(true) .mobileEmulationIsCustomDevice(false) .mobileEmulationDeviceName("iPhone 12 Pro"); SHAFT.GUI.WebDriver driver = new SHAFT.GUI.WebDriver(); ``` For a device outside Chrome's preset list, or to pin an exact viewport, use custom dimensions instead: ```java title="CustomDeviceEmulation.java" SHAFT.Properties.web.set() .isMobileEmulation(true) .mobileEmulationIsCustomDevice(true) .mobileEmulationWidth(390) .mobileEmulationHeight(844) .mobileEmulationPixelRatio(3.0) .mobileEmulationUserAgent("Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) ..."); ``` :::warning Device-list drift The browser's built-in device list changes across releases (Chrome 143 removed `"Pixel 5"`, for example), and an unknown device name fails session creation with the opaque `entry 0 of 'firstMatch' is invalid`. SHAFT pins `"Pixel 5"` — its long-standing default — to explicit device metrics so it keeps working on every Chromium browser. If a previously working device name starts failing, switch to custom device emulation with the device's width, height, pixel ratio, and user agent. ::: ## Local Selenium Grid execution Running a local Grid is useful for distributed execution without cloud services, cross-browser/OS testing on one network, and debugging remote-execution issues before deploying to a managed Grid. The fastest path is Docker Compose: ```yaml title="docker-compose.yml" services: selenium-hub: image: selenium/hub:latest ports: ["4444:4444", "4442:4442", "4443:4443"] chrome: image: selenium/node-chrome:latest shm_size: 2gb depends_on: [selenium-hub] environment: - SE_EVENT_BUS_HOST=selenium-hub - SE_EVENT_BUS_PUBLISH_PORT=4442 - SE_EVENT_BUS_SUBSCRIBE_PORT=4443 ``` ```bash docker-compose up -d # Console: http://localhost:4444 docker-compose up -d --scale chrome=3 # scale a node docker-compose down ``` Without Docker, run the standalone Selenium Server JAR: `java -jar selenium-server- .jar hub`, then `java -jar selenium-server- .jar node --hub http://localhost:4444` (or `standalone` for both in one process). Point SHAFT at the Grid: ```properties title="src/main/resources/properties/custom.properties" executionAddress=localhost:4444 targetOperatingSystem=Linux targetBrowserName=chrome headlessExecution=true # Inspect Grid capacity before creating remote sessions (Selenium Grid 4) remotePreflightEnabled=true remoteAdaptiveSessionThrottling=true ``` For Selenium Grid 4, SHAFT can query `/status` and `/graphql` before remote session creation; the preflight summary is attached to the report. Use `remoteAdaptiveSessionThrottling=true` when local TestNG/JUnit parallelism can exceed Grid capacity, and `remotePreflightFailFast=true` when a missing browser slot should fail before WebDriver retries. ## Kubernetes Selenium Grid For a cloud-native, auto-scaling Grid, deploy on **Kubernetes** with **KEDA** (Kubernetes Event-Driven Autoscaler) — browser nodes scale from zero to capacity on demand and back down when tests finish: ```bash helm repo add docker-selenium https://www.selenium.dev/docker-selenium helm repo add kedacore https://kedacore.github.io/charts helm repo update helm install keda kedacore/keda --namespace keda --create-namespace helm install selenium-grid docker-selenium/selenium-grid \ --set autoscaling.enabled=true \ --set autoscaling.scaledOptions.minReplicaCount=0 \ --set autoscaling.scaledOptions.maxReplicaCount=8 ``` Point SHAFT at the in-cluster Service DNS name (or port-forward for access from outside the cluster): ```properties title="src/main/resources/properties/custom.properties" executionAddress=selenium-grid-selenium-hub.default.svc.cluster.local:4444 targetOperatingSystem=Linux targetBrowserName=chrome ``` ```bash # From outside the cluster kubectl port-forward svc/selenium-grid-selenium-hub 4444:4444 ``` :::tip Best practices Keep `minReplicaCount=0` to avoid paying for idle browser nodes; set Kubernetes resource requests/limits (browser containers are memory-intensive); use a dedicated namespace; store `values.yaml` in version control; use Kubernetes Secrets for Grid credentials. ::: ## Network mocking and interception `driver.browser().interceptRequest()` matches outgoing browser requests to replace responses with controlled test data, or lets real responses continue and validates them with SHAFT API validations. It relies on Selenium DevTools, so use a DevTools-capable browser (Chrome or Edge). ```java title="NetworkMocking.java" driver.browser() .interceptRequest() .get() .urlContains("/api/users") .queryParam("role", "admin") .respond() .statusCode(200) .jsonBody("{\"users\":[{\"name\":\"Mock User\"}]}"); driver.browser().navigateToURL("https://example.com/dashboard"); ``` Use `assertResponse(...)` / `verifyResponse(...)` to validate a **real** response instead of mocking it: ```java title="NetworkValidation.java" driver.browser() .interceptRequest() .get() .pathEquals("/api/users") .assertResponse(response -> response.extractedJsonValue("users.size()").isEqualTo("1")); ``` `routeFromHar()` replays a previously captured HAR 1.2 file as mocked responses (works on both `SHAFT.GUI.WebDriver` and `SHAFT.GUI.Playwright`); a compatible HAR can come from a [Capture](/docs/agentic/capture) session or any HAR 1.2 export. Contract recording/replay (`startContractRecording`, `replayContract`, `assertContract`) shares the same contract mode as `SHAFT.API` — see [UI and API contract replay](/docs/testing/contracts). Network profiles simulate offline/slow/blocked conditions: ```java title="NetworkProfiles.java" driver.browser().throttleNetwork(300, 128, 64).and().navigateToURL("https://example.com/reports"); driver.browser().restoreNetwork(); driver.browser().blockNetworkResources("*.png", "*.webp").and().goOffline(); ``` :::note Interception rules are scoped to the current browser session and cleared automatically on `quit()`, or explicitly with `driver.browser().clearNetworkInterceptors()`. If multiple rules match the same request, the latest registered rule wins. When `shaft.trace.includeNetwork=true`, SHAFT also passively records network exchanges into the failure trace bundle (`shaft-network.har`) with secrets masked. ::: ## Using cookies and cached authentication Move authentication state between an API session and a browser session: ```java title="CookieBridging.java" // API login -> browser session SHAFT.API api = new SHAFT.API("https://api.example.com"); api.post("/auth/login").setRequestBody(credentials).setContentType(ContentType.JSON); SHAFT.GUI.WebDriver driver = new SHAFT.GUI.WebDriver(); driver.browser().navigateToURL("https://app.example.com").importCookiesFrom(api); // Browser login -> API session (the inverse: exportCookiesTo(...)) api.importCookiesFrom(driver.browser()); ``` `SHAFT.API` also accepts a storage-state JSON path directly in its constructor — the same file produced by `driver.browser().saveStorageState(path)`: ```java SHAFT.API api = new SHAFT.API("https://api.example.com", "target/auth-state.json"); ``` `SHAFT.Auth.setup(name, flow)` is a Playwright-style cached-authentication helper: it runs a login `flow` once per `name` and caches the resulting browser storage state to disk, so later calls — including from other tests, classes, or threads — reuse the cached session instead of repeating the UI login: ```java title="AuthSetup.java" // Once per suite, e.g. in a @BeforeSuite: SHAFT.Auth.setup("standardUser", driver -> { driver.browser().navigateToURL("https://example.com/login"); driver.element().type(By.id("username"), "standard_user"); driver.element().type(By.id("password"), "secret_sauce"); driver.element().click(By.id("login-button")); }); // In every test, reuse the cached session: SHAFT.Properties.web.set().storageStatePath(SHAFT.Auth.stateFile("standardUser")); SHAFT.GUI.WebDriver driver = new SHAFT.GUI.WebDriver(); ``` The cache is valid as long as its file exists (no TTL) under `SHAFT.Properties.paths.authCache()` (default `target/auth-cache/`). Concurrent `setup()` calls for the *same* `name` are serialized on a per-name lock; calls for different names never block each other. See [Auto-load storage state on driver init](/docs/reference/actions/GUI/Browser_Actions#auto-load-storage-state-on-driver-init). ## Accessibility testing SHAFT integrates [axe-core](https://github.com/dequelabs/axe-core) for automated WCAG audits directly within existing WebDriver tests — chain `.accessibility()` onto browser actions, no separate tooling required (add the `com.deque.html.axe-core:selenium` dependency): ```java title="AccessibilityTesting.java" driver.browser().navigateToURL("https://example.com") .accessibility() .assertNoCriticalViolations("Home Page"); driver.browser().navigateToURL("https://example.com") .accessibility() .assertAccessibilityScoreAtLeast("Home Page", 90.0); driver.browser().navigateToURL("https://example.com") .accessibility() .analyzeWithIgnoredRules("Home Page", List.of("color-contrast", "image-alt")); ``` | Scenario | Method | |---|---| | Strict compliance gate | `assertNoCriticalViolations()` | | Progressive improvement | `assertAccessibilityScoreAtLeast()` | | Known accepted deviations | `analyzeWithIgnoredRules()` | :::tip Run accessibility audits in CI smoke tests to catch regressions early. Results (page name, score, violation details) are automatically included in the SHAFT Allure report. ::: ## Jira integration SHAFT can automatically create Jira bugs and link test results to Jira issues. Create an [Atlassian API token](https://id.atlassian.com/login) (Account Settings → Security → Create and manage API tokens), then configure `JiraXRay.properties`: ```properties title="src/main/resources/properties/JiraXRay.properties" jiraInteraction=true jiraUrl=https://your-jira-instance.atlassian.net projectKey=PROJ authorization=your-copied-token reportTestCasesExecution=true reportPath=target/surefire-reports/testng-results.xml ExecutionName=RegressionTestExecution ReportBugs=true assignee=jira-user-id ``` ## Visual testing Visual regression (`matchesReferenceImage()`, `matchesScreenshot()`, OpenCV/Applitools Eyes engines) has its own dedicated page — see [Visual testing](/docs/integrations/visual) for the full engine comparison, the `shaft-visual` dependency decision, and per-browser/OS baseline naming. ## Related - [Browser Actions](/docs/reference/actions/GUI/Browser_Actions) - [Element Actions](/docs/reference/actions/GUI/Element_Actions) - [Visual testing](/docs/integrations/visual) - [Properties List](/docs/reference/properties/PropertiesList) --- # Tips: Locators and Self-Healing Canonical HTML: https://shafthq.github.io/docs/reference/actions/GUI/Locators_And_Self_Healing Markdown: https://shafthq.github.io/md/reference/actions/GUI/Locators_And_Self_Healing.md Deeper locator tips beyond the [Element Identification](/docs/reference/actions/GUI/Element_Identification) reference — resilient locator strategies and recovery from broken locators. ## Generated and repository locator policy This is the official `SHAFT-GUIDE` locator policy for generated and repository web code. Stop at the first unique match: 1. A unique, author-written id via the SHAFT locator builder: `SHAFT.GUI.Locator.hasAnyTagName().hasId("checkout-submit").build()`. Never a framework-recycled id such as `:r1:`, `mat-input-3`, `cdk-overlay-0`, `ember1234`, `j_idt42`, `ctl00_...`, or `sc-bdVaJa`. 2. The same builder's ARIA role, chained with `hasNormalizedText` / `hasAttribute` / context until unique. 3. Native relative xpath only: `By.xpath(...)` when the element has neither. Never emit `SHAFT.GUI.Locator.xpath(...)`, the raw `SHAFT.GUI.Locator.id/name/cssSelector/className/tagName(...)` factories, or Smart Locators (`inputField` / `clickableField`) into generated or checked-in code. `test_code_guardrails_check` flags those as `SMART_LOCATOR` and `NON_ARIA_LOCATOR`. Smart Locators remain legitimate only for a human's throwaway exploration snippet. ### Human steps 1. Inspect the live DOM, ARIA snapshot, or mobile accessibility tree. 2. Prefer an existing verified locator owned by the current page object. 3. If you must add one, walk the three-tier ladder above. 4. Prove uniqueness, then run the nearest focused test. ### AI codegen details - Locator policy: unique author-written id via SHAFT locator builder, then ARIA role, then native relative xpath only. - Replay-proven snippets: record with `capture_start`, confirm the flow with `capture_generate_replay` (`replay=true`) or `verify_run_focused`, then generate with `capture_code_blocks` / `capture_record_at_target_code_blocks`. - Properties: no extra property is required for the locator ladder. Heal stays opt-in (`healing.strategy=shaft-heal`). Pilot AI stays default-off (`pilot.ai.enabled=false`). - Exact commands: ```bash shaft-cli call test_code_guardrails_check --args '{"source":" "}' shaft-cli call capture_generate_replay sessionPath=recordings/checkout.json replay=true shaft-cli call verify_run_focused ``` ## ARIA role-based locators `SHAFT.GUI.Locator.hasRole()` finds elements by their semantic ARIA role rather than fragile IDs or CSS classes, using the `Role` enum (`BUTTON`, `SEARCHBOX`, `NAVIGATION`, `DIALOG`, `ALERT`, `CHECKBOX`, `LINK`, `LISTBOX`, `TEXTBOX`, and more): ```java title="ARIALocators.java" By submitButton = SHAFT.GUI.Locator.hasRole(Role.BUTTON).hasNormalizedText("Submit").build(); By searchInput = SHAFT.GUI.Locator.hasRole(Role.SEARCHBOX).build(); By errorAlert = SHAFT.GUI.Locator.hasRole(Role.ALERT).containsText("error").build(); driver.element().click(submitButton); driver.element().type(searchInput, "test query"); ``` :::tip For generated or repository code, chain `hasRole(...)` with `hasNormalizedText`, `hasAttribute`, or context until the match is unique. Do not pair it with a Smart Locator. ::: ## Self-healing locators For current SHAFT projects, start with [SHAFT Heal](/docs/agentic/heal): add `shaft-heal` and opt in with `healing.strategy=shaft-heal`. It is deterministic, explainable, disabled by default, and writes reviewable locator recovery reports. Legacy **Healenium** integration remains opt-in through `healing.strategy=healenium` (or the legacy `heal-enabled=true` flag) for projects that already run a Healenium backend server. Install that backend with the [managed Healenium setup flow](/docs/start/local-infrastructure/services#install-managed-healenium): ```java title="SelfHealingLocators.java" SHAFT.Properties.healenium.set() .healEnabled(true) .recoveryTries(3) .scoreCap("0.7") .serverHost("localhost") .serverPort(7878); ``` Once enabled, no test-code changes are needed — locators automatically self-heal when the DOM changes. A healing report is generated so you can update your locators proactively; self-healing is a safety net, not a substitute for maintaining accurate locators. ## Shadow DOM locator builder Elements inside a Shadow Root are not reachable by regular `By.id()`, `By.cssSelector()`, or `By.xpath()` because they live in an encapsulated DOM tree. SHAFT's Locator Builder resolves this with `.insideShadowDom()` — no JavaScript execution required: ```java title="ShadowDomBasic.java" // 1. Locate the shadow host (the custom element that owns the shadow root) By shadowHost = SHAFT.GUI.Locator.hasTagName("my-component").build(); // 2. Build a locator that targets an element INSIDE that shadow root By shadowElement = SHAFT.GUI.Locator .hasTagName("button") .hasText("Submit") .insideShadowDom(shadowHost) .build(); driver.element().click(shadowElement); ``` For nested shadow roots, chain `.insideShadowDom()` calls from the outermost host inward: ```java title="ShadowDomNested.java" By outerHost = SHAFT.GUI.Locator.hasTagName("app-shell").build(); By innerHost = SHAFT.GUI.Locator.hasTagName("user-card") .insideShadowDom(outerHost) .build(); By editButton = SHAFT.GUI.Locator.hasTagName("button") .hasText("Edit Profile") .insideShadowDom(innerHost) .build(); ``` All regular Locator Builder methods (`hasAttribute()`, `hasText()`, `containsText()`, `containsClass()`, `containsId()`, and more) work the same way inside `.insideShadowDom()`. :::tip Use Chrome DevTools (Elements panel → expand `#shadow-root`) to inspect shadow root structure and identify host tag names before writing your locators. ::: ## SHAFT Locator Builder `SHAFT.GUI.Locator` describes elements in plain English instead of raw XPath or CSS. The builder composes a standard Selenium `By` locator under the hood, so it works everywhere a `By` is accepted. Call `.build()` at the end. | Method | Description | Example | |--------|-------------|---------| | `hasTagName(tag)` | Matches elements with this HTML tag | `hasTagName("button")` | | `hasAnyTagName()` | Matches any HTML tag | `hasAnyTagName()` | | `hasAttribute(name[, value])` | Attribute present, optionally with an exact value | `.hasAttribute("type", "submit")` | | `hasText(text)` / `containsText(text)` | Visible text equals / contains the string | `.hasText("Login")` | | `containsId(id)` / `containsClass(cls)` | `id` / `class` attribute contains the string | `.containsClass("btn-primary")` | | `hasImage(imagePath)` | Locate visually using a reference screenshot | `.hasImage("ref/login-btn.png")` | | `byAxis()` | Fluent XPath axis navigation — `parent()`, `ancestor(tag)`, `child(tag)`, `followingSibling(tag)`, `precedingSibling(tag)` | `.byAxis().followingSibling("input")` | Conditions are ANDed together — all must match: ```java title="ChainedConditions.java" // Submit By submitBtn = SHAFT.GUI.Locator .hasTagName("button") .containsClass("btn-primary") .hasAttribute("data-test", "checkout") .hasText("Submit") .build(); driver.element().click(submitBtn); ``` Visual locators match against a reference screenshot when no reliable DOM attribute exists, using OpenCV — save reference images under `src/test/resources/dynamicObjectRepository/`: ```java title="ImageLocator.java" By checkoutBtn = SHAFT.GUI.Locator .hasAnyTagName() .hasImage("dynamicObjectRepository/checkout-button.png") .build(); ``` XPath axis navigation lets you walk DOM relationships without writing raw XPath: ```java title="XPathAxisLabelToInput.java" // Find the input field that follows the "Email" label By emailInput = SHAFT.GUI.Locator.hasTagName("label") .hasText("Email") .byAxis().followingSibling("input") .build(); ``` For more locator strategies, see [LocatorBuilderTest examples on GitHub](https://github.com/ShaftHQ/SHAFT_ENGINE/blob/main/shaft-engine/src/test/java/testPackage/locator/LocatorBuilderTest.java). ## Smart locators `inputField()` and `clickableField()` find elements by user-facing labels, placeholders, and button text. That is a human-exploration helper only. Generated and repository code follow the [generated locator policy](#generated-locator-policy), not this API. ```java title="ThrowawayExploration.java" // Human exploration only. Do not generate or check this in. By email = SHAFT.GUI.Locator.inputField("Email"); By login = SHAFT.GUI.Locator.clickableField("Log In"); ``` | Approach | Example | Generated or repository rank | |---|---|---| | Author-written id | `SHAFT.GUI.Locator.hasAnyTagName().hasId("login-submit").build()` | First, when unique and not recycled | | ARIA role | `SHAFT.GUI.Locator.hasRole(Role.BUTTON).hasNormalizedText("Log In").build()` | Second | | Native relative xpath | `By.xpath(".//form//button[@type='submit']")` | Third, only when the element has neither | | Smart Locator | `clickableField("Log In")` | Human exploration only; never generated or repository code | :::note When multiple elements match the same label or text, Smart Locators return the first match in DOM order. That is another reason they stay out of generated and repository code. ::: ## iFrame handling `driver.element().switchToIframe(locator)` switches WebDriver's context into an ` ` so subsequent interactions target elements inside it; `driver.element().switchToDefaultContent()` returns to the main page: ```java title="iFrameHandling.java" driver.element().switchToIframe(By.id("payment-iframe")); driver.element() .type(By.id("cardNumber"), "4111111111111111") .type(By.id("cvv"), "123") .click(By.id("payBtn")); driver.element().switchToDefaultContent(); ``` Nested iframes require switching into each level in order. See [Element Identification → Interacting with IFrames](/docs/reference/actions/GUI/Element_Identification) for the full walkthrough, including nested-frame and index-based switching examples. :::warning Always call `switchToDefaultContent()` after finishing work inside an iframe — forgetting to switch back is a common cause of `NoSuchElementException` on main-page elements. ::: ## Portable locators (ShaftLocator) `ShaftLocator` describes an element once and resolves it on both Selenium and Playwright. | Method | What it does | | --- | --- | | `css(String selector)` | Creates a CSS locator. | | `role(String role, String accessibleName)` | Creates a role plus accessible-name locator. | | `accessibleName(String accessibleName)` | Creates an accessible-name locator (aria-label / getByLabel). | | `from(By locator)` / `from(SemanticLocatorResolution)` | Converts a Selenium `By` or a semantic resolution into a portable locator. | | `value()` / `secondaryValue()` | Returns the locator's main value and, for role locators, the accessible name. | | `toBy()` | Converts to a Selenium `By`. | | `toPlaywrightSelector()` | Returns a Playwright string selector for CSS, XPath or text strategies. | | `toPlaywrightLocator(Page page)` / `toPlaywrightLocator(Locator parent)` | Resolves to a Playwright `Locator` on a page or under a parent locator. | ## Related - [Element Identification](/docs/reference/actions/GUI/Element_Identification) - [Element Actions](/docs/reference/actions/GUI/Element_Actions) - [SHAFT Heal](/docs/agentic/heal) - [Set up local infrastructure](/docs/start/local-infrastructure/services#install-managed-healenium) - [Web](/docs/testing/web) ## Flutter locator factories For Flutter Integration Driver sessions, prefer `SHAFT.GUI.Locator.flutter*` thin wrappers over Appium java-client `AppiumBy.flutter*`: | Factory | Purpose | | --- | --- | | `flutterKey` | Flutter Key / ValueKey | | `flutterText` / `flutterTextContaining` | Exact / partial widget text | | `flutterType` | Widget type name (e.g. TextField) | | `flutterSemanticsLabel` | Semantics label (includes Tooltip text) | | `flutterDescendant` / `flutterAncestor` | Hierarchy finders | The `Locators` enum (`XPATH` / `CSS`) is only the relation-builder strategy enum used by `LocatorBuilder` — not a full By factory catalog. Full Flutter guide: [Flutter testing](/docs/testing/flutter). --- # Tips: Waits and Synchronization Canonical HTML: https://shafthq.github.io/docs/reference/actions/GUI/Waits_And_Synchronization Markdown: https://shafthq.github.io/md/reference/actions/GUI/Waits_And_Synchronization.md Use these patterns when an application state needs more than SHAFT's automatic element waits. ## Explicit waits SHAFT retries normal element lookups with `defaultElementIdentificationTimeout`. For a specific condition, use the generic `driver.element().waitUntil(...)`. On Selenium WebDriver, `waitForLazyLoading()` and post-`navigateToURL` (including new tab/window) use the navigation-grade DOM quiet window (default `300`ms). Playwright `waitForLazyLoading()` is still load-state. `scrollToLoadAll()` is explicit only: ```java title="ExplicitWaits.java" driver.element().waitUntil(ExpectedConditions.textToBePresentInElementLocated( By.id("status"), "Complete")); driver.element().waitUntil(ExpectedConditions.attributeContains( By.id("progress"), "style", "width: 100%")); driver.element().waitUntil(ExpectedConditions.numberOfElementsToBeMoreThan( By.cssSelector(".result"), 0)); driver.element().waitUntil(ExpectedConditions.elementToBeSelected(By.id("checkbox"))); driver.element().waitUntil(ExpectedConditions.presenceOfAllElementsLocatedBy( By.cssSelector(".row"))); driver.browser().waitForLazyLoading(); ``` :::info Configure the default custom UI condition wait via `SHAFT.Properties.timeouts.set().waitForUiStateTimeout(600)`. See [Programmatic Configuration](/docs/reference/properties/Programmatic_Config). ::: ### Custom condition waits (lambda) `driver.element().waitUntil()` accepts a Selenium condition or lambda. Without an explicit `Duration`, it uses `waitForUiStateTimeout` (default 600 seconds): ```java title="CustomConditionWait.java" // Lambda: wait until a specific element has a non-empty text value driver.element().waitUntil(webDriver -> { String text = webDriver.findElement(By.id("priceLabel")).getText(); return text != null && !text.isEmpty(); }); // Built-in ExpectedCondition driver.element().waitUntil(ExpectedConditions.invisibilityOfElementLocated(By.id("spinner"))); // Wait until a JavaScript variable is set to true driver.element().waitUntil(webDriver -> (Boolean) ((JavascriptExecutor) webDriver) .executeScript("return window.appReady === true;")); ``` :::tip Prefer a built-in `ExpectedConditions` predicate when one expresses the state clearly. Use a lambda when the condition depends on application-specific state. ::: :::warning Avoid combining excessive explicit waits with a high implicit timeout — this multiplies the worst-case wait time and slows down failure detection. Set the implicit timeout conservatively and rely on explicit waits for specific conditions. ::: ## Clipboard actions `driver.element().clipboardActions(locator, ClipboardAction)` performs copy/cut/paste/select-all operations on an element via the `ClipboardAction` enum (`COPY`, `CUT`, `PASTE`, `SELECT_ALL`) — no raw keyboard shortcuts required. Getting the order right matters: some browsers require the field to be focused and its content selected *before* a `CUT`/`COPY` will capture the right data, so treat clipboard actions as a small synchronization sequence rather than a single call. ```java title="ClipboardActions.java" // Copy from a read-only source field, then paste into the target driver.element() .clipboardActions(By.id("generatedToken"), ClipboardAction.COPY) .clipboardActions(By.id("tokenInput"), ClipboardAction.PASTE); // Select all existing content before cutting/overwriting a field driver.element() .clipboardActions(By.id("commentBox"), ClipboardAction.SELECT_ALL) .clipboardActions(By.id("commentBox"), ClipboardAction.CUT); ``` :::tip `clipboardActions()` returns the element actions builder, so operations chain fluently. Prefer it over `typeAppend()` for copy-from-read-only scenarios. Clipboard operations use the OS-level clipboard — running parallel tests on the same machine can race; prefer isolated environments (Docker/Grid) for clipboard-intensive parallel tests. ::: ## Related - [Element Actions](/docs/reference/actions/GUI/Element_Actions) - [Async Element Actions](/docs/reference/actions/GUI/Async_Element_Actions) - [Flakiness](/docs/testing/flakiness) - [Web](/docs/testing/web) --- # Touch Actions Canonical HTML: https://shafthq.github.io/docs/reference/actions/GUI/Touch_Actions Markdown: https://shafthq.github.io/md/reference/actions/GUI/Touch_Actions.md SHAFT provides touch action methods for mobile app automation. Access them through `driver.touch()`. For trust-gated natural-language touch workflows such as `driver.act("tap Continue")`, see [Natural Language Actions](./Natural_Language_Actions). ## Tap Actions ### tap() Taps an element once on a touch-enabled screen. ```java title="TapExample.java" By loginButton = By.id("login_btn"); driver.touch().tap(loginButton); ``` Reference-image taps use `shaft-visual` to find the image in the current viewport, then execute a Selenium/Appium pointer action at the matched center: ```java title="ImageTapExample.java" driver.touch().tap("src/test/resources/dynamicObjectRepository/login-button.png"); ``` ### type() Taps a field found by reference image, then types into the focused field. ```java title="ImageTypeExample.java" driver.touch().type("src/test/resources/dynamicObjectRepository/search-field.png", "SHAFT Engine"); ``` ### doubleTap() Double-taps an element on a touch-enabled screen. ```java title="DoubleTapExample.java" By imageElement = By.id("photo"); driver.touch().doubleTap(imageElement); ``` ### longTap() Performs a long press on an element to trigger the context menu. ```java title="LongTapExample.java" By listItem = By.id("item_1"); driver.touch().longTap(listItem); ``` ## Swipe Actions ### swipeToElement() Swipes from a source element to a destination element. ```java title="SwipeToElementExample.java" By sourceElement = By.id("card_1"); By destinationElement = By.id("drop_zone"); driver.touch().swipeToElement(sourceElement, destinationElement); ``` ### swipeByOffset() Swipes an element by a specified horizontal and vertical offset. Positive x-offset swipes right, negative swipes left. Positive y-offset swipes down, negative swipes up. ```java title="SwipeByOffsetExample.java" By element = By.id("slider"); driver.touch().swipeByOffset(element, 200, 0); // swipe right by 200px ``` ### swipeElementIntoView() Scrolls until the target element is visible. Useful for finding elements in long scrollable lists. ```java title="SwipeIntoViewExample.java" By targetElement = By.id("item_at_bottom"); driver.touch().swipeElementIntoView(targetElement, TouchActions.SwipeDirection.DOWN); ``` You can also specify a scrollable container: ```java title="SwipeIntoViewContainerExample.java" By scrollableContainer = By.id("list_view"); By targetElement = By.id("item_50"); driver.touch().swipeElementIntoView(scrollableContainer, targetElement, TouchActions.SwipeDirection.DOWN); ``` Image-path overloads scroll until the reference image is visible in the viewport. Selenium sessions use headless-safe page or element scrolling; Appium native sessions use Appium scroll gestures. ```java title="ImageSwipeIntoViewExample.java" driver.touch().swipeElementIntoView( "src/test/resources/dynamicObjectRepository/pay-now.png", TouchActions.SwipeDirection.DOWN); ``` ### swipeToEndOfView() Swipes until the current Appium view can no longer scroll in the requested direction. ```java title="SwipeToEndExample.java" driver.touch().swipeToEndOfView(TouchActions.SwipeDirection.DOWN); ``` You can also limit the gesture to a scrollable container: ```java title="SwipeContainerToEndExample.java" By scrollableContainer = By.id("list_view"); driver.touch().swipeToEndOfView(scrollableContainer, TouchActions.SwipeDirection.UP); ``` ## Keyboard Actions ### nativeKeyboardKeyPress() Sends a key press via the device soft keyboard. ```java title="KeyPressExample.java" driver.touch().nativeKeyboardKeyPress(TouchActions.KeyboardKeys.ENTER); ``` ### hideNativeKeyboard() Hides the device native soft keyboard. ```java title="HideKeyboardExample.java" driver.touch().hideNativeKeyboard(); ``` ## Zoom Actions ### pinchToZoom() Zooms the current screen in or out. ```java title="PinchToZoomExample.java" driver.touch().pinchToZoom(TouchActions.ZoomDirection.IN); driver.touch().pinchToZoom(TouchActions.ZoomDirection.OUT); ``` ## App Management ### sendAppToBackground() Sends the currently active app to the background. ```java title="BackgroundAppExample.java" // Send to background and return after 5 seconds driver.touch().sendAppToBackground(5); // Send to background and leave deactivated driver.touch().sendAppToBackground(); ``` ### activateAppFromBackground() Activates an app that has been previously deactivated or sent to the background. ```java title="ActivateAppExample.java" driver.touch().activateAppFromBackground("com.example.myapp"); ``` ## Visual Element Detection Image-path touch actions support cropped reference screenshots captured at a different DPI or display scale than the current app screenshot when `shaft-visual` is on the runtime classpath. ### waitUntilElementIsVisible() Waits until a specific element is visible on the screen using image-based detection. ```java title="WaitForVisualElement.java" driver.touch().waitUntilElementIsVisible("path/to/reference-screenshot.png"); ``` ### waitUntilElementIsNotVisible() Waits until a reference image is no longer visible on the screen. ```java title="WaitForVisualElementToDisappear.java" driver.touch().waitUntilElementIsNotVisible("path/to/reference-screenshot.png"); ``` ## Complete Example ```java title="MobileTouchActionsDemo.java" public class MobileTouchActionsDemo { private SHAFT.GUI.WebDriver driver; @BeforeMethod public void setup() { SHAFT.Properties.platform.set().targetPlatform("ANDROID"); SHAFT.Properties.mobile.set().automationName("UiAutomator2"); driver = new SHAFT.GUI.WebDriver(); } @Test public void testTouchActions() { By usernameField = By.accessibilityId("username"); By passwordField = By.accessibilityId("password"); By loginButton = By.accessibilityId("login"); driver.touch().tap(usernameField); driver.element().type(usernameField, "admin"); driver.touch().tap(passwordField); driver.element().type(passwordField, "password"); driver.touch().tap(loginButton); } @AfterMethod public void teardown() { driver.quit(); } } ``` ## Coordinates, files, app state and Flutter helpers | Method | What it does | | --- | --- | | `tapByCoordinates(int x, int y)` | Taps viewport coordinates once. | | `swipeByCoordinates(int startX, int startY, int endX, int endY, int durationMillis)` | Swipes between two viewport coordinates over the given duration. | | `pushFile(String deviceFilePath, String localFilePath)` / `pushFile(String deviceFilePath, File localFile)` | Uploads a local file to the device, emulator or simulator. | | `pullFile(String deviceFilePath, String localFilePath)` | Downloads a file from the device to the local machine. | | `saveAppState(String filePath)` | Saves a JSON snapshot of the current app state (active app, context, orientation, window size). | | `loadAppState(String filePath)` | Restores what can be restored from a saved app-state snapshot on the live session. | | `saveSessionCapabilities(String filePath)` | Saves the active Appium session capabilities to JSON so a later run can reuse the same device setup. | | `loadSessionCapabilities(String filePath)` | Loads previously saved Appium session capabilities from JSON. | | `performDoubleClick(By)`, `performLongPress(By)`, `performDragAndDrop(By source, By target)` | Flutter-native gestures on widgets, using the Flutter driver. | | `waitForVisible(By)` / `waitForVisible(By, Duration)` | Waits for a Flutter widget to become visible. | | `waitForAbsent(By)` / `waitForAbsent(By, Duration)` | Waits for a Flutter widget to disappear. | | `injectMockImage(File image)` / `activateInjectedImage(String imageId)` | Injects a mock camera image into a Flutter app and activates it. | ```java driver.touch().tapByCoordinates(200, 640) .pushFile("/sdcard/Download/data.json", "src/test/resources/data.json") .saveAppState("target/app-state.json"); ``` ## Related - [Browser Actions](/docs/reference/actions/GUI/Browser_Actions) - [Element Actions](/docs/reference/actions/GUI/Element_Actions) - [Element Identification](/docs/reference/actions/GUI/Element_Identification) - [Web](/docs/testing/web) --- # Validations Canonical HTML: https://shafthq.github.io/docs/reference/actions/Validations Markdown: https://shafthq.github.io/md/reference/actions/Validations.md Use the `Validations` class or the fluent driver API to assert and verify values. `assertThat()` stops the test on failure; `verifyThat()` records the failure and lets the test continue. ## Overview Assertions and verifications are core parts of test automation. Each serves a distinct purpose: 1. **Assertions (Hard Assertions)** — If the assertion condition is not met, test execution is **aborted** immediately. The remaining steps in the test case are skipped and the result is marked as **failed**. Use assertions for business-critical checkpoints. 2. **Verifications (Soft Assertions)** — Even if the verification condition is not met, test execution **continues** until the last step completes. All failures are collected and reported at the end of the test run. Use verifications when you want to validate multiple conditions in a single test. You can perform assertions and verifications on: 1. **[Browser validations](#browser-validations)** — URL, title, and other browser attributes 2. **[Element validations](#element-validations)** — Existence, visibility, text, attributes, and visual matching 3. **[File validations](#file-validations)** — Existence, checksum, and content validation 4. **[Object validations](#object-validations)** — Equality, containment, regex matching, null checks, and boolean checks 5. **[Number validations](#number-validations)** — Equality and comparison operators 6. **[API response validations](#response-validations)** — JSON/XML extraction, file content comparison, and schema matching 7. **[Force fail](#force-fail)** — Intentionally fail a test with a custom report message SHAFT provides two ways to write validations: ```java title="StandaloneValidationExample.java" // Hard assertion — fails immediately if condition is not met Validations.assertThat().object(actualValue).isEqualTo(expectedValue); // Soft assertion — collects failures and reports at the end Validations.verifyThat().object(actualValue).isEqualTo(expectedValue); ``` ```java title="DriverValidationExample.java" // Assert using the driver instance driver.assertThat().element(locator).text().isEqualTo("Expected Text"); // Verify using the driver instance driver.verifyThat().element(locator).exists(); ``` :::tip Each validation runs when you call its condition method. The returned executor is retained for source compatibility; do not add another execution call. ::: ## Browser validations You can perform assertions and verifications on the browser itself using the `WebDriverBrowserValidationsBuilder`. Access it through the driver instance or the standalone `Validations` class. ### attribute() Use this method to validate a specific browser attribute. It returns a `NativeValidationsBuilder` to continue building your validation chain. ```java title="BrowserUrlValidation.java" // Using driver-based validation (recommended) driver.assertThat().browser().attribute("url").isEqualTo("https://example.com"); driver.verifyThat().browser().attribute("url").contains("example"); // Using standalone validation Validations.assertThat().browser(driver.getDriver()).attribute("url").isEqualTo("https://example.com"); Validations.verifyThat().browser(driver.getDriver()).attribute("url").contains("example"); ``` ```java title="BrowserTitleValidation.java" // Using driver-based validation (recommended) driver.assertThat().browser().attribute("title").isEqualTo("My Page Title"); driver.verifyThat().browser().attribute("title").contains("Page"); ``` :::tip After calling `.attribute()`, you can chain any of the following comparison methods: `.isEqualTo()`, `.contains()`, `.doesNotContain()`, `.matchesRegex()`, `.isNull()`, `.isNotNull()`, and more. ::: ## Element validations You can perform assertions and verifications on web elements using the `WebDriverElementValidationsBuilder`. Access it through the driver instance or the standalone `Validations` class. ### exists() / doesNotExist() ```java title="ElementExistsValidation.java" driver.assertThat().element(locator).exists(); driver.verifyThat().element(locator).exists(); driver.assertThat().element(locator).doesNotExist(); ``` ### matchesReferenceImage() / doesNotMatchReferenceImage() Checks that the target element matches (or does not match) a reference image using OpenCV. On the first run, a screenshot of the element is captured and saved as the reference. Subsequent runs compare the element against this reference. Reference images are stored under `src/test/resources/DynamicObjectRepository`. ```java title="VisualValidation.java" driver.assertThat().element(locator).matchesReferenceImage(); driver.verifyThat().element(locator).doesNotMatchReferenceImage(); ``` `SHAFT.GUI.Playwright` supports the same element visual assertions when `shaft-visual` is on the runtime classpath. Playwright captures `Locator.screenshot()` bytes; the no-argument overload uses OpenCV, while explicit OpenCV and Applitools Eyes engines use the selected visual provider. See [Visual testing](/docs/integrations/visual) for engine options. ### attribute() Checks a specific element attribute against an expected value. After calling `.attribute()`, chain a comparison method such as `.isEqualTo()`, `.contains()`, `.doesNotContain()`, `.matchesRegex()`, and more. ```java title="ElementAttributeValidation.java" driver.assertThat().element(locator).attribute("class").contains("active"); driver.verifyThat().element(locator).attribute("href").isEqualTo("https://example.com"); ``` ### text() / textTrimmed() ```java title="ElementTextValidation.java" driver.assertThat().element(locator).text().isEqualTo("Welcome"); driver.assertThat().element(locator).textTrimmed().isEqualTo("Hello World"); ``` ### cssProperty() ```java title="ElementCssValidation.java" driver.assertThat().element(locator).cssProperty("color").contains("rgb(0, 128, 0)"); ``` ### State validations `isVisible()`, `isHidden()`, `isEnabled()`, `isDisabled()`, `isSelected()`, `isNotSelected()`, `isChecked()`, and `isNotChecked()` check the target element's current state: ```java title="ElementStateValidation.java" driver.assertThat().element(locator).isVisible(); driver.assertThat().element(locator).isEnabled(); driver.assertThat().element(locator).isSelected(); ``` :::tip State validations run as soon as you call the condition method. ::: ### Complete example ```java title="src/test/java/tests/CheckoutValidationTest.java" public class CheckoutValidationTest { private SHAFT.GUI.WebDriver driver; private final By addToCartBtn = By.cssSelector("[data-test='add-to-cart']"); private final By cartBadge = By.className("shopping_cart_badge"); private final By checkoutBtn = By.id("checkout"); private final By orderSummary = By.id("order-summary"); private final By placeOrderBtn = By.id("place-order"); @Test public void checkoutFlow() { driver.browser().navigateToURL("https://example.com/products"); // Add item and check badge driver.element().click(addToCartBtn); driver.assertThat().element(cartBadge) .text().isEqualTo("1"); // Proceed to checkout driver.element().click(checkoutBtn); driver.assertThat().browser().url().contains("/checkout"); // Verify order summary is displayed driver.assertThat().element(orderSummary) .isVisible(); // Verify place order button is enabled driver.assertThat().element(placeOrderBtn) .isEnabled(); } @BeforeMethod public void setup() { driver = new SHAFT.GUI.WebDriver(); } @AfterMethod public void teardown() { driver.quit(); } } ``` ## File validations You can perform assertions and verifications on files using the `FileValidationsBuilder`. ### exists() / doesNotExist() ```java title="FileExistsValidation.java" Validations.assertThat().file("src/test/resources", "testData.json").exists(); Validations.assertThat().file("src/test/resources", "deleted.json").doesNotExist(); ``` ### checksum() Calculates and validates the file checksum to confirm whether it has the exact same content. Chain a comparison method such as `.isEqualTo()` after calling `.checksum()`. ```java title="FileChecksumValidation.java" Validations.assertThat().file("src/test/resources", "testData.json") .checksum() .isEqualTo("expectedChecksumValue"); ``` ### content() Reads and validates the file content. Works for PDF and text files. Chain a comparison method such as `.isEqualTo()`, `.contains()`, or `.matchesRegex()` after calling `.content()`. ```java title="FileContentValidation.java" Validations.assertThat().file("src/test/resources", "report.txt") .content() .contains("Test Passed"); ``` ## Object validations You can perform assertions and verifications on objects using the `NativeValidationsBuilder`: `isEqualTo()`, `doesNotEqual()`, `contains()`, `doesNotContain()`, `matchesRegex()`, `doesNotMatchRegex()`, `equalsIgnoringCaseSensitivity()`, `doesNotEqualIgnoringCaseSensitivity()`, `isNull()`, `isNotNull()`, `isTrue()`, and `isFalse()`. ```java title="ObjectEqualValidation.java" Validations.assertThat().object(actualValue).isEqualTo(expectedValue); Validations.verifyThat().object(actualValue).isEqualTo(expectedValue); ``` ```java title="ObjectContainsValidation.java" Validations.assertThat().object(actualText).contains("expected substring"); Validations.assertThat().object(actualValue).matchesRegex("\\d{3}-\\d{4}"); Validations.assertThat().object("Hello World").equalsIgnoringCaseSensitivity("hello world"); Validations.assertThat().object(actualValue).isNotNull(); ``` ## Number validations You can perform assertions and verifications on numbers using the `NumberValidationsBuilder`: `isEqualTo()`, `doesNotEqual()`, `isGreaterThan()`, `isGreaterThanOrEquals()`, `isLessThan()`, and `isLessThanOrEquals()`. ```java title="NumberValidation.java" Validations.assertThat().number(actualNumber).isEqualTo(expectedNumber); Validations.assertThat().number(actualNumber).isGreaterThan(expectedNumber); ``` :::tip Number validations are useful for checking response times, element counts, and other numeric test data: ```java // Validate API response time is under 2 seconds Validations.assertThat().number(api.getResponseTime()).isLessThan(2000); // Validate the number of search results Validations.assertThat().number(resultCount).isGreaterThan(0); ``` ::: ## Response validations You can perform assertions and verifications on API responses using the `RestValidationsBuilder`. Access it through the `SHAFT.API` instance or the standalone `Validations` class. ### isEqualToFileContent() / doesNotEqualFileContent() / containsFileContent() / doesNotContainFileContent() ```java title="ResponseFileValidation.java" // Using SHAFT.API instance (recommended) api.assertThatResponse().isEqualToFileContent("src/test/resources/expected-response.json"); api.assertThatResponse().containsFileContent("src/test/resources/partial-response.json"); // Using standalone validation Validations.assertThat().response(response).isEqualToFileContent("src/test/resources/expected-response.json"); ``` ### extractedJsonValue() / extractedJsonValueAsList() Extracts a value (or list of values) from the JSON response using a JSONPath expression and validates it. Chain a comparison method such as `.isEqualTo()`, `.contains()`, or `.matchesRegex()`. ```java title="ResponseJsonExtractValidation.java" api.assertThatResponse().extractedJsonValue("data.name").isEqualTo("John"); api.verifyThatResponse().extractedJsonValue("data.items[0].id").isEqualTo("123"); api.assertThatResponse().extractedJsonValueAsList("data.items").isNotNull(); ``` :::info Use standard JSONPath expressions such as `"$.data.name"`. SHAFT evaluates them with [Jayway JsonPath](https://github.com/json-path/JsonPath). ::: :::tip When using `SHAFT.API`, the response from the last request is automatically passed to the validation builder. You don't need to pass it explicitly. ::: ## JSON schema validation SHAFT Engine supports **JSON Schema validation** for API responses through `matchesSchema()` and `doesNotMatchSchema()` on the response assertion builder. This enables **contract testing** — ensuring your API responses conform to an agreed-upon structure as your system evolves. | Benefit | Description | |---|---| | Contract testing | Catch breaking API changes before they reach consumers | | Type safety | Verify field types (string, integer, boolean) are correct | | Required fields | Assert mandatory fields are always present in responses | | Format validation | Validate email, date-time, URI, and other formats | Place your schema files under `src/test/resources/schemas/`: ```json title="user-schema.json" { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "required": ["id", "name", "email"], "properties": { "id": { "type": "integer" }, "name": { "type": "string" }, "email": { "type": "string", "format": "email" } } } ``` ### matchesSchema() / doesNotMatchSchema() ```java title="JSONSchemaValidation.java" SHAFT.API api = new SHAFT.API("https://api.example.com"); api.get("/users/1") .setTargetStatusCode(200) ; // Assert the response body matches the schema api.assertThatResponse() .matchesSchema("src/test/resources/schemas/user-schema.json"); // Assert the response body does NOT match a given schema (negative testing) api.assertThatResponse() .doesNotMatchSchema("src/test/resources/schemas/admin-schema.json"); ``` :::tip Commit your schema files to source control alongside your tests. When a deliberate API contract change is made, update the schema and create a PR — making API contract changes explicit and reviewable. ::: :::note SHAFT uses the [JSON Schema Validator](https://github.com/java-json-tools/json-schema-validator) library under the hood, which supports JSON Schema Draft 4, 6, and 7. ::: ## Force fail Use `forceFail()` to intentionally fail a test with a custom message. This is useful for marking incomplete tests, flagging known issues, or creating conditional failures. ```java title="ForceFailExample.java" // Force fail with a report message Validations.assertThat().forceFail("This feature is not yet implemented"); // Soft force fail — collects the failure and continues execution Validations.verifyThat().forceFail("Known issue: JIRA-1234"); ``` ### Validation timing Validation chains run when you call the condition method. Use the condition method as the end of the chain: ```java title="ValidationTiming.java" Validations.assertThat().file("src/test/resources", "data.json").exists(); Validations.assertThat().number(count).isGreaterThan(0); driver.assertThat().element(locator).exists(); ``` ## Soft vs hard assertions SHAFT Engine provides two assertion modes: **hard assertions** that stop the test immediately on the first failure, and **soft assertions** that collect all failures and report them together at the end of the test. Choosing the right mode is key to writing effective, maintainable tests. ### Hard assertions — assertThat() `assertThat()` is a **hard assertion**. If the assertion condition is not met, test execution **stops immediately** and the test is marked as failed. Remaining steps are skipped. Use hard assertions for **business-critical checkpoints** where proceeding with a broken state would produce meaningless results. ```java title="SoftVsHardAssertions.java" // Hard assertion — test fails immediately on first failure driver.assertThat().browser().title().contains("Dashboard"); driver.assertThat().element(By.id("welcomeMsg")).text().contains("Welcome"); ``` ### Soft assertions — verifyThat() `verifyThat()` is a **soft assertion**. If the condition is not met, the failure is **recorded but execution continues**. All accumulated failures are reported together at the end of the test. Use soft assertions when you want to **validate multiple independent conditions** in a single test without early exit. ```java title="SoftVsHardAssertions.java" // Soft assertion — collects failures, reports all at end of test driver.verifyThat().element(By.id("name")).text().contains("John"); driver.verifyThat().element(By.id("email")).text().contains("@"); driver.verifyThat().element(By.id("role")).text().isEqualTo("Admin"); // All three are verified; all failures reported together at test end ``` Both modes are also available without a driver instance via `SHAFT.Validations`: ```java title="StandaloneSoftVsHardAssertions.java" SHAFT.Validations.assertThat().object("actual").isEqualTo("expected"); SHAFT.Validations.verifyThat().number(42).isGreaterThan(0); ``` | Aspect | `assertThat()` (Hard) | `verifyThat()` (Soft) | |---|---|---| | On failure | Stops test immediately | Records failure, continues | | Use case | Critical business checkpoints | Multiple independent validations | | Report | Shows first failure only | Shows all failures together | | Test speed | Faster on failure | Completes all steps before reporting | :::tip A good pattern is to use **one hard assertion** to confirm the test precondition (e.g., correct page, user is logged in), then use **soft assertions** for all subsequent field validations. ::: :::info Soft assertion failures are accumulated per-test and reported at the end of the test method. In TestNG, SHAFT triggers a test failure after the `@AfterMethod` completes, so all soft failures for a test are visible in the Allure report. ::: ## Localization assertions Validate the script used by element or browser text with `language()`. SHAFT supports Arabic, English, Spanish, French, and German presets. You can pass the matching `ValidationEnums.TextLanguage` value or its two-letter code. ```java title="LocalizationValidation.java" driver.assertThat().element(By.id("welcome")).text().language().is(TextLanguage.ARABIC); driver.verifyThat().element(By.id("welcome")).text().language().isNot("en"); driver.assertThat().element(By.id("welcome")).text().direction().is(TextDirection.RTL); driver.verifyThat().element(By.id("welcome")).text().direction().isRightToLeft(); ``` Call `language()` or `direction()` only after a text validation. Language detection checks whether the value contains characters from the selected language family. Direction checks the text-direction metadata collected for the element or browser text. Use `isArabic()` when you need the convenience check for Arabic characters and right-to-left direction: ```java title="ArabicTextValidation.java" driver.assertThat().element(By.id("welcome")).text().isArabic(); ``` ## Soft verification evidence Each `verifyThat()` condition records its checkpoint while execution continues. Open the test's validation evidence to review every collected checkpoint and the final aggregated result. Keep independent observations as separate chains so each failed condition has its own report entry. ```java title="VerificationEvidence.java" driver.verifyThat().element(By.id("name")).text().contains("John"); driver.verifyThat().element(By.id("email")).text().contains("@"); ``` ## Related - [Custom Report Messages](/docs/reference/reporting#custom-report-messages) — enrich Allure reports with business context - [Element Actions](/docs/reference/actions/GUI/Element_Actions) — available interactions before validating - [Element Identification](/docs/reference/actions/GUI/Element_Identification) — locate the elements you validate - [Testing: Web](/docs/testing/web) — locator strategy and web testing guide --- # Agentic testing Canonical HTML: https://shafthq.github.io/docs/agentic/overview Markdown: https://shafthq.github.io/md/agentic/overview.md # Agentic testing without hidden automation SHAFT separates deterministic automation from optional model-provider advice. MCP, Capture, Doctor, and Heal remain useful without provider credentials. ```mermaid flowchart TD IntelliJ["IntelliJ plugin"] --> MCP["SHAFT MCP"] Client["AI client"] --> MCP["SHAFT MCP"] MCP --> Engine["shaft-engine tools"] MCP --> Capture["Capture"] Capture --> Generated["Reviewable TestNG source"] Results["Allure results"] --> Doctor["Doctor"] Doctor --> Diagnosis["Portable diagnosis"] FailedLocator["Failed locator"] --> Heal["Deterministic Heal"] Heal --> Report["Explainable recovery report"] Provider["Optional provider"] -. consent + redaction .-> Doctor Provider -. consent + redaction .-> Heal ``` ## Install SHAFT agent tools Install the project-local [ChaosEngine harness](/docs/agentic/chaos-engine) when you want one canonical agent workflow plus verified host adapters and local Memory, MemPalace, and Graphify tooling. Install the first-party skill pack without configuring an MCP client. The [SHAFT agent skills guide](/docs/agentic/skills) owns the commands, supported routes, native directories, upgrade behavior, and packaging tradeoffs. Install `shaft-cli` without configuring an MCP client. This also installs its internal `shaft-mcp` runtime prerequisite. Change into the target project first. Public one-liners install SHAFT Agentic Tools (MCP, CLI, and skills): ```powershell irm "https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/scripts/mcp/install.ps1" | iex ``` ```bash curl -fsSL "https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/scripts/mcp/install.sh" | bash ``` CLI-only install still uses the inner Agentic Tools script: ```powershell powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/scripts/mcp/install-shaft-agentic-tools.ps1 | iex; Install-ShaftMcp -Arguments @('--install-shaft-cli')" ``` ```bash curl -fsSL https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/scripts/mcp/install-shaft-agentic-tools.sh | sh -s -- --install-shaft-cli ``` | Capability | Default behavior | Start here | |---|---|---| | IntelliJ plugin | IDE front door for Assistant, Coding Partner, Recorder, Doctor, Healer, Inspector, Projects, and Guide search | [IntelliJ IDEA plugin](/docs/agentic/intellij) | | MCP | Local stdio tools; no model credentials stored | [Connect MCP](/docs/agentic/mcp) | | Planning | Deterministic same-origin crawl drafts Markdown test plans into `specs/`; no AI calls | [Planning test coverage](/docs/agentic/mcp#planning-test-coverage) | | Capture | Record and generate deterministic test code | [Capture](/docs/agentic/capture) | | Doctor | Analyze allowlisted evidence offline | [Doctor](/docs/agentic/doctor) | | Heal | Recover eligible web locators with an explainable policy | [Heal](/docs/agentic/heal) | | Pilot providers | Disabled until explicitly configured and approved | [Provider controls](/docs/agentic/providers) | ## Why SHAFT? Compared with plain recorder/codegen tools and raw agent-written Selenium, SHAFT's agentic loop differs on four load-bearing points: - **Privacy-safe recording.** Typed values are externalized to test-data files, secrets are redacted at capture time, and a checked-in team policy (`.shaft/recorder-policy.json`) pins recording behavior for the whole repository — recordings are safe to commit and share. - **Compile-validated codegen.** Generated tests are compiled against SHAFT and optionally replayed end-to-end before you ever see them; a replay failure returns the code *plus* the failing step's diagnostics, never a silent broken file. - **Repo-aware generation.** The Coding Partner plans against your existing page objects, locator fields, and action methods before proposing new code, so generation extends your suite instead of forking it. - **A maintenance loop, not just a generator.** Doctor triages failed runs from Allure evidence and Heal recovers eligible locators with an explainable policy — the same tools that created the test keep it alive. The same workflows run on every agent — Codex, Claude Code, GitHub Copilot, and Gemini — because they are MCP tools, not agent-specific plugins; and the No-AI lane (Recorder, codegen, Doctor, Healer) works with no agent or model credentials at all. Use the [MCP command reference](/docs/agentic/mcp#mcp-command-reference) for Capture and Doctor CLI examples. ```text MCP commands live on /docs/agentic/mcp#mcp-command-reference ``` ## Related - [What's new: agentic tools](/docs/features/whats-new/agentic) - [ChaosEngine](/docs/agentic/chaos-engine) - [SHAFT agent skills](/docs/agentic/skills) - [MCP](/docs/agentic/mcp) - [Pilot](/docs/agentic/pilot) - [Doctor](/docs/agentic/doctor) - [Providers](/docs/agentic/providers) --- # Agentic tool updates Canonical HTML: https://shafthq.github.io/docs/features/whats-new/agentic Markdown: https://shafthq.github.io/md/features/whats-new/agentic.md # Agentic tool updates Choose the smallest local interface that fits your workflow. Provider-backed advice remains optional; deterministic tools continue to work without it. Install only the tool route your environment uses: ```text MCP, CLI, IntelliJ, or agent skills ``` ![SHAFT IntelliJ Assistant showing project guidance, local MCP tools, and a reviewed response.](/img/whats-new/intellij-assistant.png) ## Related - [Agentic testing overview](/docs/agentic/overview) - [What's new since modularization](/docs/features/whats-new) --- # Architecture Canonical HTML: https://shafthq.github.io/docs/features/architecture Markdown: https://shafthq.github.io/md/features/architecture.md # Architecture SHAFT has two kinds of modularity: 1. Maven artifacts control dependency weight. 2. The `SHAFT` facade exposes testing namespaces such as GUI, API, CLI, and DB. These are related, but they are not one artifact per facade namespace. Most functionality remains in the required `shaft-engine` JAR. ## Where SHAFT fits in a test automation stack A test automation architecture separates concerns into layers — your test scripts, page objects/models, the engine, core libraries, and drivers/protocols that talk to the application under test. SHAFT Engine sits in the **engine** layer: it wraps and enhances industry-standard libraries (Selenium WebDriver, Appium, REST Assured, TestNG/JUnit, Allure) with smart element handling, a unified API across web/mobile/API/CLI/database testing, built-in reporting, and property-based configuration. | Capability | Without SHAFT | With SHAFT | |---|---|---| | Wait strategy | Manual `WebDriverWait` configuration | Automatic smart waits | | Element not found handling | Custom retry logic | Built-in retry with configurable attempts | | Screenshot capture | Manual `TakesScreenshot` code | Automatic at validation points | | Reporting | Manual Allure setup and step annotations | Automatic step logging and report generation | | Cross-browser | Manual driver management | Automatic driver management | SHAFT is an **engine**, not a framework: it does not dictate how you structure your tests — Page Object Model, fluent design, or any pattern you prefer all work — it provides the building blocks and you decide how to assemble them. This means SHAFT works with your team's existing conventions and can be adopted gradually, one test at a time, without rewriting your suite. ## Published Maven artifacts ```mermaid flowchart TB Consumer["Consumer project"] --> BOM["shaft-bom dependency management"] Consumer --> Engine["shaft-engine required runtime"] Consumer -.->|optional| PilotCore["shaft-pilot-core contracts + security"] Consumer -.->|optional| Capture["shaft-capture managed recording + privacy"] Consumer -.->|optional| Doctor["shaft-doctor offline evidence + diagnosis"] Consumer -.->|optional| AI["shaft-ai direct providers"] Consumer -.->|optional| Heal["shaft-heal deterministic element recovery"] Consumer -.->|optional| BrowserStack["shaft-browserstack BrowserStack Java SDK"] Consumer -.->|optional| Video["shaft-video local desktop recording"] Consumer -.->|optional| Visual["shaft-visual OpenCV + Eyes + Shutterbug"] Consumer -.->|optional| SikuliX["shaft-sikulix SikuliX desktop image automation"] Agent -.-> MCP["shaft-mcp executable MCP server"] BrowserStack --> Engine Video --> Engine Visual --> Engine SikuliX --> Engine MCP --> Engine MCP --> Capture MCP --> Doctor MCP --> AI PilotCore --> Engine Capture --> PilotCore Doctor --> PilotCore AI --> PilotCore Heal --> Engine Heal --> PilotCore Legacy["SHAFT_ENGINE relocation POM only"] -.->|relocates| Engine Engine --> GUI["Web + Appium/Flutter"] Engine --> API["REST API"] Engine --> Data["DB + CLI + test data"] Engine --> Support["validations + reporting accessibility + properties"] ``` | Artifact | Packaging | Consumer purpose | |----------------------|----------------|--------------------------------------------------------------------------------------------------------------------------| | `shaft-engine` | JAR | Required facade and core web, mobile, API, database, CLI, reporting, and accessibility implementation. | | `shaft-pilot-core` | JAR | Provider-neutral Pilot contracts, consent, redaction, budgets, audit metadata, and deterministic fallback. | | `shaft-capture` | JAR | Managed Chrome/Edge recording, deterministic privacy classification, versioned schema, and atomic JSON persistence. | | `shaft-doctor` | JAR | Portable redacted evidence, deterministic diagnosis, optional advisory, and approval-gated isolated repair proposals. | | `shaft-ai` | JAR | Optional direct OpenAI, Anthropic, Gemini, GitHub Models, and Ollama HTTP adapters discovered through `ServiceLoader`. | | `shaft-heal` | JAR | Deterministic, explainable, action-scoped web element recovery with bounded local history and optional reranking. | | `shaft-browserstack` | JAR | BrowserStack SDK interception and `browserstack.yml` orchestration. Direct BrowserStack sessions stay in `shaft-engine`. | | `shaft-video` | JAR | Local non-headless desktop recording. Appium-native recording stays in `shaft-engine`. | | `shaft-visual` | JAR | Reference-image assertions and image-based lookup through OpenCV, Eyes, and Shutterbug. | | `shaft-sikulix` | JAR | SikuliX image matching for desktop workflows that cannot be reached through DOM or Appium locators. | | `shaft-mcp` | thin JAR | Optional MCP server and CLI exposing browser automation, managed capture, Doctor analysis, and packaged direct adapters. | | `shaft-bom` | POM | Aligns all SHAFT artifact versions; adds no runtime classes. | | `SHAFT_ENGINE` | relocation POM | Temporary legacy-coordinate bridge to `shaft-engine`; adds no optional providers. | See the [upgrade guide](/docs/start/upgrade) for method-level dependency boundaries. ```xml title="pom.xml" io.github.shafthq shaft-bom ${shaft.version} pom import io.github.shafthq shaft-engine ``` ## Facade architecture ```mermaid graph TB SHAFT["SHAFT facade"] subgraph "Testing namespaces in shaft-engine" GUI["GUI Web + Mobile"] API["API REST Assured"] CLI["CLI Terminal + Files"] DB["DB JDBC"] TestData["TestData JSON + Excel + CSV + YAML"] Validations["Validations Fluent assertions"] Properties["Properties Configuration"] Report["Report Logs + Attachments"] end subgraph "GUI components" WebDriver["WebDriver lifecycle"] Browser["Browser actions"] Element["Element actions"] Touch["Touch actions"] Alert["Alert actions"] Locator["Locator builder"] end subgraph "Test orchestration" TestNG["TestNG"] JUnit["JUnit"] Cucumber["Cucumber"] Allure["Allure"] end SHAFT --> GUI SHAFT --> API SHAFT --> CLI SHAFT --> DB SHAFT --> TestData SHAFT --> Validations SHAFT --> Properties SHAFT --> Report GUI --> WebDriver WebDriver --> Browser WebDriver --> Element WebDriver --> Touch WebDriver --> Alert WebDriver --> Locator TestNG -.->|uses| SHAFT JUnit -.->|uses| SHAFT Cucumber -.->|uses| SHAFT SHAFT -.->|reports to| Allure ``` ## Optional provider discovery `shaft-heal`, `shaft-visual`, and `shaft-video` implement SHAFT-owned provider interfaces and register them through Java `ServiceLoader`. The facade and orchestration remain in `shaft-engine`, which means users add a provider JAR without changing test method calls. `shaft-browserstack` is a runtime integration rather than a SHAFT provider. It adds `browserstack-java-sdk`; SHAFT's core BrowserStack driver path also generates `browserstack.yml`, which the SDK consumes when the optional module is present. `shaft-sikulix` is a runtime action module. It adds the SikuliX API and a small desktop-image action facade without shadowing `shaft-engine` classes. ## Module guides - [Upgrade and module selection](/docs/start/upgrade) - [SHAFT Heal](/docs/agentic/heal) - [BrowserStack SDK module](/docs/integrations/browserstack) - [Visual processing module](/docs/integrations/visual) - [Desktop and video](/docs/integrations/desktop-and-video) - [SHAFT Pilot](/docs/agentic/pilot) - [SHAFT Pilot release runbook](/docs/maintainers/pilot-release) - [SHAFT MCP](/docs/agentic/mcp) - [Optional AI providers](/docs/agentic/providers) - [SHAFT Capture](/docs/agentic/capture) - [Maven reactor layout](/docs/maintainers/reactor) ## Related - [Overview](/docs/start/overview) - [Quick start](/docs/start/quick-start) - [Modules](/docs/features/modules) - [Reporting](/docs/features/reporting) - [Underlying technology](/docs/features/modules#technology) - [BrowserStack](/docs/integrations/browserstack) --- # Capture and generation updates Canonical HTML: https://shafthq.github.io/docs/features/whats-new/capture Markdown: https://shafthq.github.io/md/features/whats-new/capture.md # Capture and generation updates Use Capture to record a working journey, correct its intent, and generate code that passes replay before you keep it. Open the local Capture workflow through its documented client: ```text /record ``` ![SHAFT Capture locator picker showing scored locator choices for the selected browser element.](/img/whats-new/capture-locator-picker.png) ![SHAFT Capture assertion mode with the assertion panel open and a browser target selected.](/img/whats-new/capture-assertion-mode.png) ## Related - [Capture guide](/docs/agentic/capture) - [What's new since modularization](/docs/features/whats-new) --- # Evidence and report updates Canonical HTML: https://shafthq.github.io/docs/features/whats-new/evidence Markdown: https://shafthq.github.io/md/features/whats-new/evidence.md # Evidence and report updates Use the lightest evidence profile that answers the team's diagnostic needs. Failed runs can retain portable reports, bundles, manifests, and structured checks. Keep the default automatic trace profile unless the project needs another level: ```properties shaft.trace.enabled=true shaft.trace.mode=auto ``` ![SHAFT Trace Viewer populated with a failed action timeline, filters, and evidence detail.](/img/whats-new/trace-viewer.png) ![Generated Allure report with the SHAFT Failure Brief attachment open.](/img/whats-new/failure-brief.png) ## Related - [Reporting and evidence](/docs/features/reporting) - [What's new since modularization](/docs/features/whats-new) --- # Features and modules Canonical HTML: https://shafthq.github.io/docs/features/modules Markdown: https://shafthq.github.io/md/features/modules.md # Features and modules The required `shaft-engine` artifact provides the public facade and core test automation capabilities. Add optional artifacts only when a project uses the capability they own. ## Published artifact map | Artifact | Add it when | |---|---| | `shaft-engine` | The project needs web, mobile, API, database, CLI actions, test data, accessibility, reporting, or screenshots. | | `shaft-pilot-core` | An integration needs provider-neutral Pilot contracts and local-agent orchestration. | | `shaft-capture` | The project records browser/API flows and generates reviewable replay code. | | `shaft-doctor` | The project analyzes Allure evidence and failure traces offline. | | `shaft-ai` | Pilot needs optional direct model-provider adapters. | | `shaft-heal` | Web tests need deterministic, explainable locator recovery. | | `shaft-mcp` | An MCP client needs SHAFT browser, Capture, Doctor, healer, or guide-search tools. | | `shaft-cli` | Scripts or CI need repeatable one-shot access to the SHAFT tool catalog. | | `shaft-browserstack` | Tests need BrowserStack SDK interception, YAML expansion, or SDK orchestration. | | `shaft-video` | Local non-headless desktop runs need FFmpeg video recording. | | `shaft-visual` | Tests need OpenCV, Applitools Eyes, Shutterbug, or reference-image behavior. | | `shaft-sikulix` | Desktop tests need SikuliX image-based automation. | | `shaft-bom` | A project uses more than one published SHAFT library and needs aligned versions. | `shaft-capture-proxy` supports Capture's isolated proxy runtime and is not the normal consumer entry point. `report-aggregate` builds reactor-wide reports but is not deployed as a consumer dependency. `legacy-shaft-engine` is a relocation POM for older coordinates, not a new-project dependency. `shaft-ocr` is present in `SHAFT_ENGINE` source as an unreleased preview. It is not part of the current published BOM; wait for a containing release before adding it as a project dependency. ## Feature-to-module map | Feature | Maven artifact | |--------------------------------------------------------------------------------------------------|----------------------| | Web, mobile/Appium/Flutter, API, database, CLI, test data, accessibility, reporting, screenshots | `shaft-engine` | | Direct BrowserStack WebDriver/Appium sessions and app upload | `shaft-engine` | | BrowserStack SDK interception, multi-platform YAML, and SDK orchestration | `shaft-browserstack` | | Appium Android/iOS driver-native recording | `shaft-engine` | | Local non-headless desktop recording | `shaft-video` | | Reference-image assertions and image-path touch actions | `shaft-visual` | | Visible-text actions and OCR assertions | `shaft-ocr` | | SikuliX image-based desktop automation | `shaft-sikulix` | | Deterministic explainable web element recovery | `shaft-heal` | | Screenshot highlighting, animated GIFs, and `compareImageFolders(...)` | `shaft-engine` | See the [upgrade guide](/docs/start/upgrade) for the exact method boundaries. Use the BOM to align the core engine with any optional modules you add: ```xml title="pom.xml" io.github.shafthq shaft-bom ${shaft.version} pom import io.github.shafthq shaft-engine io.github.shafthq shaft-visual io.github.shafthq shaft-sikulix ``` ## Smart Features SHAFT's smart features target the [Pillars of successful test automation](/docs/features/test-automation-pillars): Scalability, Reliability, and Maintainability. Use that guide for the feature-to-pillar map; use this page for artifact and platform selection. ## Supported Platforms ### Browsers | | Linux | macOS | Windows | Android | iOS | | :--- | :---: | :---: | :---: | :---: | :---: | | Google Chrome | :white_check_mark: | :white_check_mark: | :white_check_mark: |:white_check_mark: | :white_check_mark: | | Microsoft Edge | :white_check_mark: | :white_check_mark: | :white_check_mark: |_ | _ | | Mozilla Firefox | :white_check_mark: | :white_check_mark: | :white_check_mark: |_ | _ | | Apple Safari | _ | :white_check_mark: | _ | _ | :white_check_mark: | ### Apps | | Android | iOS | Windows | | :--- | :---: | :---: | :---: | | Native |:white_check_mark: | :white_check_mark: | N/A | | Hybrid | :white_check_mark: | :white_check_mark: | N/A | | Flutter | :white_check_mark: | :white_check_mark: | N/A | | WPF | N/A | N/A |:white_check_mark: | ### Other | API | Database | CLI | PDF | JSON | YAML | Excel | Property | | :---: | :---: | :---:|:---:|:---:|:---:|:---:|:---:| | :white_check_mark: |:white_check_mark: | :white_check_mark: |:white_check_mark: |:white_check_mark: |:white_check_mark: |:white_check_mark: |:white_check_mark: | ### Test Orchestration | TestNG | JUnit | Cucumber | | :---: |:---: |:---: | | :white_check_mark: |:white_check_mark: |:white_check_mark: | --- ## Underlying technology SHAFT provides one facade over established automation projects while keeping heavy providers optional: | Layer | Technology | |---|---| | Runtime and build | [Java 25](https://www.oracle.com/java/technologies/downloads/), [Maven](https://maven.apache.org/) | | Web | [Selenium](https://www.selenium.dev/) | | Mobile | [Appium](https://appium.io/) | | API | [REST Assured](https://rest-assured.io/) | | Test runners | [TestNG](https://testng.org/), [JUnit](https://junit.org/), [Cucumber](https://cucumber.io/) | | Evidence | [Allure Report](https://allurereport.org/) | | Optional visual providers | [OpenCV](https://opencv.org/), [Applitools](https://applitools.com/), Selenium Shutterbug | | Distribution | [Maven Central](https://central.sonatype.com/artifact/io.github.shafthq/shaft-engine), [GitHub Container Registry](https://github.com/ShaftHQ/SHAFT_ENGINE/pkgs/container/shaft-engine-mcp) | The facade keeps test code on one entry point while the underlying libraries do the specialized work: ```java SHAFT.GUI.WebDriver driver = new SHAFT.GUI.WebDriver(); driver.browser().navigateToURL("https://duckduckgo.com"); driver.element().type(By.name("q"), "SHAFT Engine"); driver.assertThat().browser().title().contains("DuckDuckGo"); driver.quit(); ``` See [Architecture](/docs/features/architecture) for exact dependency boundaries. ## Project support and adoption ### Tooling and open-source program support SHAFT has received tooling or open-source support from: - [BrowserStack](https://www.browserstack.com/) - [LambdaTest](https://www.lambdatest.com/) - [Applitools](https://applitools.com/) - [JetBrains Open Source Support](https://jb.gg/OpenSourceSupport) Engineers responding to anonymous community surveys have reported using SHAFT within organizations including Vodafone, DXC Technology, Euronet, Solutions by STC, IDEMIA, GET Group, EFG Holding, Jahez, Incorta, Paymob, GIZA Systems, and others. These names are community-reported rather than audited customer endorsements. No organization is represented as guaranteeing or officially endorsing SHAFT. ### Fund continued maintenance SHAFT remains free under the MIT License. Financial support helps fund release maintenance, documentation, and public infrastructure through [GitHub Sponsors](https://github.com/sponsors/MohabMohie/). Use your own provider credentials through properties rather than hardcoding them in tests: ```properties title="src/main/resources/properties/browserStack.properties" browserStack.userName=${BROWSERSTACK_USERNAME} browserStack.accessKey=${BROWSERSTACK_ACCESS_KEY} browserStack.browserstackAutomation=true ``` ## Related - [What's new since modularization](/docs/features/whats-new/modules) - [Architecture](/docs/features/architecture) - [Upgrade and module selection](/docs/start/upgrade) - [Visual processing module](/docs/integrations/visual) - [Desktop and video](/docs/integrations/desktop-and-video) - [BrowserStack integration](/docs/integrations/browserstack) --- # Features you might have missed Canonical HTML: https://shafthq.github.io/docs/features/whats-new/missed Markdown: https://shafthq.github.io/md/features/whats-new/missed.md # Features you might have missed Use these focused capabilities when exact text, stable locators, or report tooling need more than the basic path. Build an accessible role locator for durable page-object code: ```java By login = SHAFT.GUI.Locator.hasRole(Role.BUTTON).hasNormalizedText("Log in").build(); ``` ## Related - [Validations](/docs/reference/actions/Validations) - [What's new since modularization](/docs/features/whats-new) --- # How SHAFT reduces flakiness Canonical HTML: https://shafthq.github.io/docs/testing/flakiness Markdown: https://shafthq.github.io/md/testing/flakiness.md # How SHAFT reduces flakiness SHAFT reduces flakiness by moving timing, locator, retry, and evidence capture out of individual tests and into the engine. It does not make every failure pass: product defects, broken test data, and real environment outages should still fail. The difference is that common UI timing and locator churn have one configured path instead of many hand-written sleeps and custom retry loops. ```mermaid flowchart TD Intent["Test intent"] --> Locator["Semantic locator"] Locator --> Sync["Synchronized action"] Sync --> Evidence["Allure evidence"] Evidence --> Passed{"Passed?"} Passed -- Yes --> Result["Stable pass"] Passed -- No --> Retry{"Retry enabled?"} Retry -- No --> Failure["Original failure"] Retry -- Yes --> RetryEvidence["Retry with extra evidence"] RetryEvidence --> Final{"Still failing?"} Final -- No --> Flaky["Flaky signal preserved"] Final -- Yes --> Heal{"Heal enabled and locator missing?"} Heal -- No --> Failure Heal -- Yes --> Recovery["Validated recovery or original failure"] ``` ## Semantic Locators Raw Selenium locators often bind tests to implementation details: generated IDs, CSS classes, absolute XPath, or DOM depth. SHAFT gives tests a higher level locator vocabulary. Generated and repository code must still follow the [generated locator policy](/docs/reference/actions/GUI/Locators_And_Self_Healing#generated-locator-policy): unique author-written id, then ARIA role, then native relative xpath only. - `SHAFT.GUI.Locator.hasAnyTagName().hasId("email").build()` is the first generated-code rung when the product owns a stable author-written id. - `SHAFT.GUI.Locator.hasRole(Role.BUTTON).hasNormalizedText("Sign in").build()` targets semantic roles and text instead of brittle markup structure. - `SHAFT.GUI.Locator.inputField("Email")` and `SHAFT.GUI.Locator.clickableField("Sign in")` are Smart Locators for a human's throwaway exploration only. Do not emit them into generated or repository code. - `driver.element().type("Email", value)` and `driver.element().click("Sign in")` are intent-shaped overloads that route through those Smart Locators in both WebDriver and Playwright sessions. - `driver.act(...)` can express a guarded workflow intent, then SHAFT plans it into ordinary browser, element, and touch actions only when natural actions are enabled and the plan passes the configured trust threshold. ```java title="SemanticLocators.java" By email = SHAFT.GUI.Locator.hasAnyTagName().hasId("email").build(); By apply = SHAFT.GUI.Locator.hasRole(Role.BUTTON).hasNormalizedText("Apply filter").build(); driver.element() .type(email, "qa@example.com") .click(apply); ``` The practical effect is simple: when a team renames a CSS class or wraps a button in a new container, the test can keep describing what the user sees. When the user-facing label itself changes, the failing test points to a real product or requirements change instead of a hidden selector detail. ## Automatic Synchronization Most flaky UI tests fail because they click before the browser is ready. SHAFT element actions use a configured fluent wait, poll for retriable Selenium states, scroll the element into view, and then perform the action. Browser and element actions also call lazy-loading synchronization where applicable: SHAFT waits for document readiness, active `fetch`/XHR quiet time, resource timing changes, jQuery activity, and Angular readiness when those signals exist on the page. For native user actions, SHAFT re-locates the element after this synchronization and verifies that the final target is displayed and enabled before acting on it. The default element lookup budget comes from `defaultElementIdentificationTimeout`; condition-specific waits use `waitForUiStateTimeout` unless you pass a shorter `Duration`. Lazy-loading synchronization uses `waitForLazyLoadingTimeout`; its network quiet behavior can be tuned with `lazyLoadingNetworkIdleInitialObservationMillis` and `lazyLoadingNetworkIdleQuietWindowMillis`. Lazy-loading readiness itself is layered, so the default wait adapts to what a page actually does without any test-code changes: - **JS readiness engine** (default, always on) polls `document.readyState`, in-flight `fetch`/XHR/`sendBeacon` activity, resource-timing changes, jQuery and Angular activity, and a main-thread idle probe (`requestIdleCallback` → `requestAnimationFrame` → `setTimeout` fallback) every `lazyLoadingPollingIntervalMillis` (default `200`ms). Long-lived `EventSource`/`WebSocket` connections are observed but never block idle, so SSE and long-poll pages don't pin every action at the timeout ceiling. - **DOM-stability quiet window** is split: cheap per-action waits use `lazyLoadingDomStabilityQuietWindowMillis` (default `0`, off). After navigation — and when you call `driver.browser().waitForLazyLoading()` — SHAFT also applies `lazyLoadingDomStabilityOnNavigationQuietWindowMillis` (default `300`ms) so in-viewport hydration can settle without turning DOM folding on for every click. That navigation wait is Selenium WebDriver only (including new-tab/window `navigateToURL`); Playwright `waitForLazyLoading()` remains a load-state wait. `pageLoadStrategy=eager` / `readinessState=interactive` unblocks Selenium/BiDi at `DOMContentLoaded`; the JS readiness engine still requires `loaded`/`complete` plus XHR/fetch idle and does not treat `interactive` as document-ready. Long-lived `EventSource`/`WebSocket` and BiDi in-flight counts remain advisory; they never hard-gate the wait. - **Advisory BiDi network layer** (gated on `platform.enableBiDi()`) tracks in-flight requests via WebDriver BiDi network events when BiDi is enabled. This signal is advisory only: it can extend the quiet window while requests are genuinely in flight (aged out after 10 seconds; SSE/WebSocket upgrades excluded), but it never hard-gates readiness, preserving the same tolerance for long-poll and SSE-style pages as the JS-only path. ```properties title="src/main/resources/properties/custom.properties" lazyLoadingPollingIntervalMillis=200 lazyLoadingDomStabilityQuietWindowMillis=0 lazyLoadingDomStabilityOnNavigationQuietWindowMillis=300 waitForLazyLoadingTimeout=30 ``` For content that only loads on scroll — infinite lists, or sections behind an `IntersectionObserver` that hydrate only once visible — call `scrollToLoadAll()` right before a full-page assertion or screenshot. It sweeps the page in bounded, viewport-height steps (up to `lazyLoadingScrollSweepMaxSteps`, default `20`), waiting for lazy-loading readiness between each step, then restores the original scroll position. It is never invoked automatically by any readiness wait — sweeping an entire page is not a safe default before an arbitrary action — so call it explicitly: ```java title="ScrollToLoadAll.java" driver.browser().navigateToURL("https://example.test/catalog") .scrollToLoadAll() .captureScreenshot(); ``` ```java title="ExplicitStateWait.java" driver.browser().navigateToURL("https://example.test/orders") .and().element().click("Refresh orders") .waitUntil(webDriver -> webDriver.findElement(By.id("order-count")).getText().equals("25")); ``` Use explicit waits for business states the browser cannot infer: a queue finishing, a toast disappearing, a calculated value appearing, or a backend job reaching a terminal state. Do not add sleeps around SHAFT actions by default; that duplicates the engine wait and makes failures slower without making them more accurate. For strict WebDriver form-entry checks, enable `forceCheckTextWasTypedCorrectly=true`. SHAFT then verifies the final value after `type`, `typeSecure`, and `typeAppend`; special key sequences are skipped because they do not map to a stable literal field value. ## Retry With Evidence Retry is a diagnostic boundary, not a cure. By default, `retryMaximumNumberOfAttempts=0`, so SHAFT does not hide failures unless you opt in. When retries are enabled, SHAFT's TestNG retry analyzer and JUnit extension use the same retry budget. If `forceCaptureSupportingEvidenceOnRetry=true`, the retry attempt turns on richer evidence such as video, animated GIF, WebDriver logs, page source on failure, and Playwright tracing when retry-only tracing is enabled. `retryMaximumNumberOfAttempts` means additional attempts after the original failure: `1` allows one retry, for two executions total. TestNG delegates this to its retry analyzer. JUnit schedules the retry after the failed attempt's `@AfterEach` lifecycle has finished, then runs the retry through JUnit again so `@BeforeEach` setup is executed for the retry attempt. Keep browser/session creation in setup and cleanup in teardown so each retry starts from isolated state. ```properties title="src/main/resources/properties/custom.properties" retryMaximumNumberOfAttempts=1 forceCaptureSupportingEvidenceOnRetry=true playwright.tracing.onRetryOnly=true ``` Keep the retry budget small. A pass-after-retry is still a flaky signal that should be reviewed; the value is that the retry attempt carries enough evidence to tell timing, locator, environment, and product failures apart. ## Flake Profiler The flake profiler is disabled by default. Enable it when you need a run-level and per-test view of where time is going during element actions, assertions, wait polling, retries, and evidence capture. ```properties title="src/main/resources/properties/custom.properties" shaft.flakeProfiler.enabled=true shaft.flakeProfiler.attachPerTest=true shaft.flakeProfiler.failOnSevereFlakeRisk=false shaft.flakeProfiler.slowActionThresholdMs=2000 ``` The Allure attachments show slow actions, wait-heavy actions, locator lookup counts, match counts, stale element retries, healing attempts, retry history, and evidence costs. Element action duration excludes screenshot capture and report attachment time, while assertion and verification duration is measured around the validation step itself. Leave `shaft.flakeProfiler.failOnSevereFlakeRisk=false` during investigation. Use the JSON profile to set a realistic `shaft.flakeProfiler.slowActionThresholdMs` before making severe flake risk fail the run. ## Optional Self Healing For web locator churn that survives the locator strategy above, SHAFT Heal can recover eligible locator-not-found failures. It is optional, disabled by default, and provided by the separate `io.github.shafthq:shaft-heal` artifact. ```properties title="src/main/resources/properties/custom.properties" healing.strategy=shaft-heal healing.minimumTrustPercentage=85 healing.ambiguityMargin=0.10 ``` SHAFT first tries the original locator. Recovery runs only after a web locator-not-found result, and the action proceeds only when the provider returns exactly one validated element. The provider scores deterministic evidence such as accessibility names, labels, configured test IDs, stable IDs/names, semantic attributes, DOM fingerprints, native state, ancestor context, and bounded local history. Low trust, ties, changed frame locators, and changed shadow-host locators preserve the original failure. Use healing as a safety net while updating locators, not as a reason to ignore broken tests. Reports under `target/shaft-heal/reports` and Allure attachments show why a candidate was accepted or rejected. ## What To Use First | Flakiness source | First SHAFT feature to use | Why | | --- | --- | --- | | Generated IDs, wrapper markup, or CSS churn | Author-written ids via the locator builder, then ARIA roles | Tests follow stable product identity instead of DOM implementation. | | Clicks before render, XHR, or framework settling | Built-in synchronization and explicit waits | Timing policy lives in one engine path. | | Unknown action or assertion slowdown | Flake profiler | Allure shows action, wait, retry, and evidence timings separately. | | Intermittent CI browser or infrastructure blips | Small retry budget with retry evidence | The suite can classify a transient failure without losing the original signal. | | A known web locator changed after a release | SHAFT Heal | Recovery is bounded, trust-gated, and reported. | | Hard-to-triage failures | Allure evidence, Doctor, and retry diagnostics | The failure carries screenshots, logs, source snapshots, and retry context. | ## Related - [Web testing](/docs/testing/web) - [Generated locator policy](/docs/reference/actions/GUI/Locators_And_Self_Healing#generated-locator-policy) - [ARIA Locators](/docs/reference/actions/GUI/Locators_And_Self_Healing#aria-locators) - [Smart Locators](/docs/reference/actions/GUI/Locators_And_Self_Healing#smart-locators) - [Explicit Waits](/docs/reference/actions/GUI/Waits_And_Synchronization#explicit-waits) - [Natural Language Actions](/docs/reference/actions/GUI/Natural_Language_Actions) - [SHAFT Heal](/docs/agentic/heal) - [Reporting](/docs/reference/reporting/) - [Properties Reference](/docs/reference/properties/PropertiesList) --- # How the modular SHAFT upgrade works Canonical HTML: https://shafthq.github.io/docs/start/upgrade/how-it-works Markdown: https://shafthq.github.io/md/start/upgrade/how-it-works.md # How the modular SHAFT upgrade works This page explains what the upgrader decides and why. To run it, follow [Run the upgrade](/docs/start/upgrade/run). For command options and module boundaries, see the [upgrade reference](/docs/start/upgrade/reference). ## What the script guarantees 1. Finds supported Maven POMs without scanning generated `target`, `build`, `.git`, IDE, or report directories. 2. Resolves the latest published `shaft-engine` release from Maven Central, unless `--shaft-version` is supplied. 3. Compiles the unchanged project first. A broken baseline stops the migration before any file is changed. 4. Parses `pom.xml` as XML, imports `shaft-bom`, adds `shaft-engine`, and removes the legacy `SHAFT_ENGINE` dependency without copying BOM `pom`/`import` metadata onto runtime dependencies. 5. Adds only the optional modules supported by project evidence. 6. For Selenium/Appium source upgrades, applies conservative Java rewrites in the same transaction as the POM migration. 7. Runs Maven `dependency:go-offline test-compile` so dependencies resolve into the local Maven repository and both main and test source are compiled. 8. Commits the file transaction only after compilation passes. 9. Restores every touched file byte-for-byte when validation fails. 10. Optionally uses the OpenAI Responses API for exactly three repair attempts before rollback. ```mermaid flowchart TD Start["Run upgrade_to_modular_shaft.py"] --> Discover["Discover Maven POMs and project type"] Discover --> Supported{"Legacy/modular SHAFT or supported native stack?"} Supported -- No --> Stop["Stop without changing files"] Supported -- Yes --> Resolve["Resolve latest modular SHAFT version"] Resolve --> Baseline["Compile unchanged project"] Baseline --> BaselinePass{"Baseline passes?"} BaselinePass -- No --> Stop BaselinePass -- Yes --> Snapshot["Open file transaction"] Snapshot --> Upgrade["Update POM and optional source rewrites"] Upgrade --> Compile["Resolve dependencies and run Maven test-compile"] Compile --> Pass{"Compilation passes?"} Pass -- Yes --> Commit["Commit upgraded files"] Pass -- No --> Key{"OpenAI API key available?"} Key -- No --> Rollback["Restore original bytes"] Key -- Yes --> Repair["Request constrained repair"] Repair --> Retry["Apply allowed POM/Java replacements"] Retry --> AICompile["Recompile after repair"] AICompile --> AIPass{"Compilation passes?"} AIPass -- Yes --> Commit AIPass -- No --> Attempts{"Three repair attempts exhausted?"} Attempts -- No --> Repair Attempts -- Yes --> Rollback ``` ## Project detection The script supports projects when it detects one of these shapes: | Project shape | Detection | | --- | --- | | Legacy SHAFT | `io.github.shafthq:SHAFT_ENGINE` in a POM. | | Modular SHAFT | `shaft-engine`, `shaft-bom`, or an optional SHAFT module in a POM. | | Native Selenium | Selenium dependency/import plus TestNG or JUnit dependency/import. | | Native Appium | Appium dependency/import plus TestNG or JUnit dependency/import. | | Native REST Assured | Any `io.rest-assured:*` dependency/import plus TestNG or JUnit dependency/import. | | Cucumber | Cucumber dependency/import. | For multi-module builds, POMs that directly declare SHAFT or a supported native stack/runner pair are updated. A child module with a supported native stack is also updated when the test runner is declared in a parent POM or detected from source imports. Other reactor POMs are left unchanged. JUnit detection includes JUnit 4, JUnit Jupiter, and JUnit Platform coordinates or imports, including suite-style projects that depend on `org.junit.platform:junit-platform-suite-api`. ## Optional-module scan For legacy and existing modular SHAFT projects, optional modules are inferred from existing POMs, Java source, properties, JSON, XML, and YAML. File contents are not printed; the report records only paths and detection reasons. Native projects receive `shaft-engine` only because native third-party integrations do not prove that the corresponding SHAFT provider API is used. POM evidence is read as Maven dependency coordinates, so compact XML and formatted XML are treated the same. For example, a legacy project that already declares `com.browserstack:browserstack-java-sdk`, `com.automation-remarks:video-recorder-*`, `org.openpnp:opencv`, or `com.sikulix:sikulixapi` selects the matching SHAFT optional module during migration. ```mermaid flowchart LR Scan["Scan code, POMs, and configuration"] --> Visual{"Reference-image assertion, visual engine, OpenCV/Eyes/Shutterbug, or image-path touch API?"} Scan --> Video{"videoParamsRecordVideo=true, zero-argument desktop recording, JAVE, or Automation Remarks?"} Scan --> BrowserStack{"BrowserStack SDK dependency, platformsList/parallelsPerPlatform, automation switch, or SDK YAML?"} Scan --> SikuliX{"SikuliX dependency or SHAFT.GUI.SikuliX usage?"} Visual -- Yes --> AddVisual["Add shaft-visual"] Visual -- No --> CoreVisual["Keep visual module absent"] Video -- Yes --> AddVideo["Add shaft-video"] Video -- No --> CoreVideo["Keep video module absent"] BrowserStack -- Yes --> AddBrowserStack["Add shaft-browserstack"] BrowserStack -- No --> Direct["Direct BrowserStack remains in shaft-engine"] SikuliX -- Yes --> AddSikuliX["Add shaft-sikulix"] SikuliX -- No --> CoreDesktop["Appium Windows stays in shaft-engine"] ``` Existing modular optional dependencies are preserved even when no additional scan evidence is found. ### BrowserStack evidence The BrowserStack module is selected for BrowserStack Java SDK behavior, not for ordinary remote sessions. Evidence includes: - `com.browserstack:browserstack-java-sdk`. - `SHAFT.Properties.browserStack.set().platformsList(...)`. - `SHAFT.Properties.browserStack.set().parallelsPerPlatform(...)`. - `SHAFT.Properties.browserStack.set().browserstackAutomation(...)`. - `SHAFT.Properties.browserStack.set().customBrowserStackYmlPath(...)`. - `browserStack.platformsList`. - `browserStack.parallelsPerPlatform`. - `browserStack.browserstackAutomation`. - `browserStack.customBrowserStackYmlPath`. - A BrowserStack YAML file with SDK `platforms` or `parallelsPerPlatform`. Direct BrowserStack WebDriver/Appium execution still requires only `shaft-engine`. Direct settings such as `SHAFT.Properties.browserStack.set().deviceName(...)` do not select `shaft-browserstack` by themselves. ### Visual evidence The visual module is selected for reference-image or image-engine behavior, including: - `matchesReferenceImage(...)` and `doesNotMatchReferenceImage(...)`. - `VisualValidationEngine`. - `findImageWithinCurrentPage(...)`, `compareAgainstBaseline(...)`, or `loadOpenCV()`. - Image-path touch/wait/swipe APIs. - Explicit OpenCV, Applitools Eyes, Selenium Shutterbug, or `shaft-visual` dependencies. Ordinary screenshots, highlighting, GIF generation, and folder comparison do not select `shaft-visual`. ### Video evidence The video module is selected for local desktop recording: - `videoParamsRecordVideo=true`. - `videoParams_recordVideo=true`. - `SHAFT.Properties.visuals.set().videoParamsRecordVideo(true)`. - A zero-argument `startVideoRecording()` call. - Explicit Automation Remarks, JAVE/FFmpeg, or `shaft-video` dependencies. Appium driver-native recording and cloud-provider video do not select `shaft-video`. ### SikuliX evidence The SikuliX module is selected for image-based desktop automation: - `SHAFT.GUI.SikuliX`, `SikuliActions`, or `SikuliDriver`. - Explicit `com.sikulix:sikulixapi` or `shaft-sikulix` dependencies. Appium Windows desktop sessions do not select `shaft-sikulix`; they remain in `shaft-engine`. ## Compilation and rollback The default validation command is: ```bash mvn dependency:go-offline test-compile -DskipTests -Dgpg.skip ``` `dependency:go-offline` resolves project dependencies and build plugins into the local Maven repository. `test-compile` compiles both production and test source without executing tests. The same command runs before and after migration. Use `--compile-command` only when the project requires a profile, settings file, module selector, or another project-specific compile entry point. The transaction records original bytes and file permissions immediately before the first write to each file. A normal success discards that in-memory snapshot. A failure, invalid AI response, path-policy violation, interruption, or exhausted retry budget restores the originals. ```mermaid sequenceDiagram participant User participant Script participant Files participant Maven participant OpenAI User->>Script: Start upgrade Script->>Maven: Compile unchanged project Maven-->>Script: Success Script->>Files: Remember original bytes Script->>Files: Write modular POM and optional source rewrites Script->>Maven: Compile upgraded project alt Compilation succeeds Maven-->>Script: Success Script->>Files: Commit transaction Script-->>User: Upgraded project retained else Compilation fails without API key Maven-->>Script: Failure Script->>Files: Restore original bytes Script-->>User: Original project restored else Compilation fails with API key Maven-->>Script: Failure diagnostics loop Maximum three repair attempts Script->>OpenAI: Redacted diagnostics and allowed files OpenAI-->>Script: Structured replacements Script->>Files: Apply validated replacements Script->>Maven: Recompile end alt A repair compiles Script->>Files: Commit transaction else All attempts fail Script->>Files: Restore POM, source, and all AI-edited files end end ``` The rollback guarantee covers failures handled by the running process. An operating-system kill, power loss, or hardware failure can terminate any program before cleanup runs, which is another reason to use version control. ## Optional OpenAI compile repair OpenAI repair is disabled unless a key is supplied. The safest interactive setup avoids shell history: ```bash python upgrade_to_modular_shaft.py \ --project . \ --prompt-for-openai-key ``` For CI or an existing secret manager, set `OPENAI_API_KEY` and run the normal command: ```bash python upgrade_to_modular_shaft.py --project . --yes ``` Do not paste a real API key into the script, `pom.xml`, source code, or a checked-in properties file. When the upgraded compile fails, the script uses the [OpenAI Responses API](https://developers.openai.com/api/docs/guides/text) with [Structured Outputs](https://developers.openai.com/api/docs/guides/structured-outputs). The default model is `gpt-5.5`; override it with `--openai-model` when required. Each request is constrained as follows: - Compiler output is redacted for common tokens, passwords, bearer headers, and private keys. - Only candidate POMs and Java files named by compiler diagnostics are considered. - A file containing a detected secret is excluded from editable context. - The model can replace only existing, non-symlink `pom.xml` and `.java` files that were supplied in the request. - Absolute paths, parent traversal, new files, unrelated extensions, invalid XML, oversized output, and changes outside the supplied context are rejected. - The modular BOM/version contract is revalidated after every accepted repair. - The project is recompiled after each of the three repair attempts. - If no attempt passes, the POM and every AI-edited file are rolled back. Use `--no-ai` to force deterministic rollback even when the environment already contains an API key. ## Selenium source migration Source migration is opt-in and compile-validated. `basic` keeps source intact. `session` and `full` are rejected for REST Assured-only, Cucumber-only, and other non-Selenium/Appium projects. ### Session upgrade rewrites `--upgrade-type session` performs the POM upgrade, then rewrites supported driver session code: - `WebDriver` type declarations, simple return types, parameters, and `ThreadLocal ` holders become `SHAFT.GUI.WebDriver`. - `new ChromeDriver(...)`, `new FirefoxDriver(...)`, `new EdgeDriver(...)`, `new SafariDriver(...)`, `new RemoteWebDriver(...)`, `new AndroidDriver(...)`, `new IOSDriver(...)`, and `new AppiumDriver(...)` are wrapped as `new SHAFT.GUI.WebDriver(new ...Driver(...))`. - `driver.close()` becomes `driver.quit()` when the variable was migrated to `SHAFT.GUI.WebDriver`. Example: ```java // Selenium before private WebDriver driver; @BeforeMethod public void startDriver() { driver = new ChromeDriver(); } @AfterMethod public void stopDriver() { driver.close(); } // SHAFT after session upgrade private SHAFT.GUI.WebDriver driver; @BeforeMethod public void startDriver() { driver = new SHAFT.GUI.WebDriver(new ChromeDriver()); } @AfterMethod public void stopDriver() { driver.quit(); } ``` ### Full upgrade rewrites `--upgrade-type full` runs the session upgrade, then rewrites simple one-statement browser and element actions: ```java // Selenium driver.get("https://example.com"); driver.findElement(By.id("login")).click(); driver.findElement(By.id("username")).sendKeys("demo"); // SHAFT driver.browser().navigateToURL("https://example.com"); driver.element().click(By.id("login")); driver.element().type(By.id("username"), "demo"); ``` The automatic full upgrade currently targets: - `driver.get(url)` and `driver.navigate().to(url)`. - `driver.findElement(locator).click()`. - `driver.findElement(locator).sendKeys(text)`. - `driver.findElement(locator).getText()`. - `driver.findElement(locator).isDisplayed()`. - `driver.findElement(locator).getAttribute(name)`. The report lists files changed by source migration and records unsupported patterns that need manual review. Risky cases remain manual: chained Selenium `Actions`, JavaScript execution, custom waits, reused `WebElement` instances, `findElements` collection handling, exception-driven control flow, and assertions whose message or soft/hard behavior matters. After a full upgrade, migrate element and browser assertions manually so their intent remains clear: ```java // Selenium/TestNG assertion Assert.assertTrue(driver.findElement(By.id("banner")).isDisplayed()); // SHAFT assertion driver.element().assertThat(By.id("banner")).isVisible(); ``` ## Related - [Installation](/docs/start/installation) - [Quick Start](/docs/start/quick-start) - [Modules](/docs/features/modules) - [Upgrade overview](/docs/start/upgrade) --- # Integrations and modules Canonical HTML: https://shafthq.github.io/docs/features/whats-new/modules Markdown: https://shafthq.github.io/md/features/whats-new/modules.md # Integrations and modules Add only the artifact that owns the required capability. Let the SHAFT BOM manage published module versions. Add a selected artifact without repeating its BOM-managed version: ```xml shaft-visual ``` ## Related - [Features and modules](/docs/features/modules) - [What's new since modularization](/docs/features/whats-new) --- # Pillars of successful test automation Canonical HTML: https://shafthq.github.io/docs/features/test-automation-pillars Markdown: https://shafthq.github.io/md/features/test-automation-pillars.md # Pillars of successful test automation The **Pillars of successful test automation** are **Scalability**, **Reliability**, and **Maintainability**. SHAFT supports them by keeping test intent in the test code and moving browser, mobile, API, evidence, configuration, and recovery mechanics into one engine. ```mermaid flowchart LR Suite["SHAFT test automation framework"] subgraph Scalability["Scalability"] direction TB ScaleGoal["Run more useful coverage without duplicating test logic"] Parallel["Parallel execution"] Surfaces["Web, mobile, API, CLI, DB"] Remote["Grid, Docker, BrowserStack"] Config["Property-driven environments"] ScaleGoal --> Parallel --> Surfaces --> Remote --> Config end subgraph Reliability["Reliability"] direction TB ReliableGoal["Make failures deterministic, observable, and actionable"] Sync["Built-in synchronization"] Evidence["Allure logs, screenshots, video, traces"] Retry["Small retry budgets with retry evidence"] Checks["Fluent assertions and validations"] ReliableGoal --> Sync --> Evidence --> Retry --> Checks end subgraph Maintainability["Maintainability"] direction TB MaintainGoal["Keep tests readable as products and platforms change"] Facade["One SHAFT facade"] Locators["Smart and fluent locators"] Data["External test data"] Recovery["Optional Heal recovery"] MaintainGoal --> Facade --> Locators --> Data --> Recovery end Suite --> ScaleGoal Suite --> ReliableGoal Suite --> MaintainGoal ``` ## Scalability Scalability means the framework can add browsers, devices, environments, and test volume without forcing every test class to fork. SHAFT does this by putting execution choice behind configuration and keeping the test body on the same facade. | SHAFT support | What it enables | | --- | --- | | WebDriver and Playwright backends | Choose Selenium/Appium WebDriver or Playwright per test setup while keeping SHAFT GUI actions. | | Web, mobile, API, CLI, database, and test data namespaces | Keep different test surfaces in one framework instead of separate helper stacks. | | TestNG, JUnit, and Cucumber integration | Use the runner that fits the project and CI pipeline. | | TestNG parallel settings and ThreadLocal driver pattern | Run independent test methods, classes, suites, or data providers in parallel without sharing a driver instance. | | `SHAFT.CrossBrowserMode` | Run the same browser test class against Chrome, Firefox, and Safari sequentially or in parallel when Docker is available. | | `executionAddress` | Move the same test code from local execution to Docker, Selenium Grid, BrowserStack, Appium, or another remote WebDriver endpoint. | | `headlessExecution` and `maximumPerformanceMode` | Reduce desktop browser overhead when the suite needs faster CI feedback. | | `shaft-bom` and optional modules | Add heavy capabilities such as Visual, Video, SikuliX, Heal, BrowserStack SDK, Capture, Doctor, MCP, or AI providers only when the project needs them. | ```properties title="src/main/resources/properties/custom.properties" executionAddress=localhost:4444 targetOperatingSystem=Linux targetBrowserName=chrome headlessExecution=true SHAFT.CrossBrowserMode=parallelized ``` ## Reliability Reliability means a passing test represents a real pass and a failing test carries enough evidence to explain the failure. SHAFT reduces accidental flakiness by centralizing waits, retries, reports, and validation behavior. | SHAFT support | What it protects | | --- | --- | | Element lookup timeout and fluent waits | Element actions retry until the element is available instead of relying on sleeps in every test. | | Browser lazy-loading waits | Browser actions can wait for document readiness, network quiet time, jQuery, and Angular signals when the page exposes them. | | Explicit condition waits | Tests can wait for business states such as a status value, count, URL, title, or spinner state. | | Fluent assertions and verifications | UI, API, file, number, object, and schema checks use one reporting path. | | Retry analyzer and JUnit extension | Retries are opt-in and bounded by `retryMaximumNumberOfAttempts`. | | Retry evidence capture | `forceCaptureSupportingEvidenceOnRetry=true` enables richer evidence on retry attempts. | | Allure report integration | Actions, logs, screenshots, request/command evidence, and attachments are grouped with the test result. | | SHAFT Doctor | Failed-run evidence can be packaged and diagnosed without relying on a live provider by default. | ```properties title="src/main/resources/properties/custom.properties" defaultElementIdentificationTimeout=10 waitForUiStateTimeout=600 retryMaximumNumberOfAttempts=1 forceCaptureSupportingEvidenceOnRetry=true evidenceLevel=CUSTOM screenshotParams_whenToTakeAScreenshot=ValidationPointsOnly attachFullLog=true ``` Use explicit waits for application states that the browser cannot infer: ```java title="ReliableStateWait.java" driver.browser().navigateToURL("https://example.test/orders"); driver.element().click("Refresh orders"); driver.element().waitUntil(ExpectedConditions.textToBePresentInElementLocated( By.id("sync-status"), "Complete")); driver.assertThat(By.id("order-count")).text().isEqualTo("25"); ``` ## Maintainability Maintainability means product changes require small, obvious test updates. SHAFT helps by making tests describe user intent, keeping configuration outside test logic, and supporting common automation design patterns. | SHAFT support | What it keeps maintainable | | --- | --- | | `SHAFT` facade | Test code uses one entry point for GUI, API, CLI, DB, test data, properties, reporting, and validations. | | Fluent action chains | Tests and page objects read as workflows instead of low-level WebDriver calls. | | Page Object Model and thin base-class patterns | Locators, page actions, and lifecycle setup stay in predictable places. | | Locator Builder | Generated and repository locators use `hasAnyTagName().hasId(...)`, then `hasRole(...)`, then native relative `By.xpath(...)` only. | | Smart Locators | `inputField(...)` and `clickableField(...)` stay for human exploration only; generated code must not emit them. | | Native WebDriver access | Teams can drop to Selenium/Appium APIs only when a specialized case needs it. | | External test data | JSON, CSV, Excel, YAML, and properties files keep data and environment values out of test methods. | | Programmatic and file-based properties | Environment changes can be made through properties or Maven `-D` overrides. | | Optional SHAFT Heal | Web locator recovery is explicit, trust-gated, reported, and kept separate from normal test code. | ```java title="MaintainableLoginTest.java" public class LoginTest { private SHAFT.GUI.WebDriver driver; private SHAFT.TestData.JSON users; @BeforeMethod public void setUp() { driver = new SHAFT.GUI.WebDriver(); users = new SHAFT.TestData.JSON("testData/users.json"); } @Test public void userCanLogIn() { driver.browser().navigateToURL("/login"); driver.element() .type(SHAFT.GUI.Locator.hasAnyTagName().hasId("email").build(), users.getTestData("valid.email")) .type(SHAFT.GUI.Locator.hasAnyTagName().hasId("password").build(), users.getTestData("valid.password")) .click(SHAFT.GUI.Locator.hasRole(Role.BUTTON).hasNormalizedText("Log in").build()); driver.assertThat().browser().url().contains("/dashboard"); } @AfterMethod(alwaysRun = true) public void tearDown() { driver.quit(); } } ``` ## Choosing Features By Pillar | If the team needs... | Start with... | | --- | --- | | Faster CI feedback | TestNG/JUnit parallel execution, ThreadLocal drivers, headless execution, and remote Grid capacity. | | Wider platform coverage | `executionAddress`, `targetOperatingSystem`, `targetBrowserName`, BrowserStack, Appium, and CrossBrowserMode. | | Less UI timing noise | Built-in synchronization, explicit waits, and a low retry budget with retry evidence. | | Better failure triage | Allure evidence, screenshots, full logs, retry artifacts, Doctor, video, and Playwright tracing where used. | | Lower selector churn | Author-written ids via the locator builder, ARIA roles, Page Objects, and optional SHAFT Heal. | | Cleaner test data changes | `SHAFT.TestData` with JSON, CSV, Excel, YAML, or properties files. | | Smaller dependency footprint | `shaft-engine` plus only the optional SHAFT modules the project actually uses. | ## Related - [Architecture](/docs/features/architecture) - [Features and modules](/docs/features/modules) - [Web testing](/docs/testing/web) - [How SHAFT reduces flakiness](/docs/testing/flakiness) - [Parallel Execution](/docs/reference/configuration/parallelExecution) - [Solution Design](/docs/reference/guides/Solution_Design) - [Generated locator policy](/docs/reference/actions/GUI/Locators_And_Self_Healing#generated-locator-policy) - [Smart Locators](/docs/reference/actions/GUI/Locators_And_Self_Healing#smart-locators) - [Reporting and evidence](/docs/features/reporting) - [SHAFT Heal](/docs/agentic/heal) --- # Platform and setup updates Canonical HTML: https://shafthq.github.io/docs/features/whats-new/platform Markdown: https://shafthq.github.io/md/features/whats-new/platform.md # Platform and setup updates Start here when you need to install SHAFT, upgrade a project, or prepare an execution service. Open the full guide for exact commands and environment rules. Verify the selected setup with a focused run: ```bash mvn test ``` ## Related - [Installation](/docs/start/installation) - [What's new since modularization](/docs/features/whats-new) --- # Reporting and evidence Canonical HTML: https://shafthq.github.io/docs/features/reporting Markdown: https://shafthq.github.io/md/features/reporting.md # Reporting and evidence SHAFT records test actions as structured Allure steps and attaches the evidence needed to understand failures. Generated element assertions keep the Allure step text concise: the locator is reported once as a step parameter, while internal element reads used to evaluate the assertion are kept in the debug execution log instead of repeated child steps. Text-entry and dropdown element actions keep the Allure timeline readable by showing the value being typed or selected in the step title. Long or multiline values are capped in the title. The step metadata keeps the normalized locator, including Smart Locator labels, and includes the resolved element name only when the engine captures one. Secure typing stays masked. ```mermaid flowchart LR Action["Test action"] --> Step["Allure step"] Step --> Logs["Logs"] Step --> Screenshot["Screenshot when relevant"] Step --> Request["API / command evidence"] Logs --> Result["allure-results"] Screenshot --> Result Request --> Result Result --> Report["Allure report"] Result --> Doctor["SHAFT Doctor"] ``` Use [reporting configuration](/docs/reference/reporting) and [custom report messages](/docs/reference/reporting#custom-report-messages) for detailed controls. Enable [source code attachments](/docs/reference/reporting#source-code-attachments) to show the Java call site beside an action or validation's other evidence. ```properties title="src/main/resources/properties/custom.properties" evidenceLevel=CUSTOM screenshotParams_whenToTakeAScreenshot=ValidationPointsOnly attachFullLog=true createAnimatedGif=false ``` ## Execution logs SHAFT writes the engine execution log through asynchronous Log4j2 appenders so normal test actions do not wait on file I/O. The console shows the concise INFO-level story, while diagnostic entries and engine internals are written at DEBUG level to the log file. Set `evidenceLevel=CUSTOM` before granular evidence controls such as `attachFullLog=true` when you want the control to override the default `FAILURE_ONLY` profile. The full-log attachment is streamed from a temporary deduplicated snapshot so the live `target/logs/log4j.log` file remains available for retry diagnostics, local investigation, and CI artifact collection. ## Failure diagnostics bundle Failed and broken tests attach `shaft-diagnostics.zip` to Allure by default. The ZIP contains `diagnostics.json`, a sanitized handoff file for humans, SHAFT Doctor, and MCP clients. It includes stable test metadata, failure type, message, stacktrace, root-cause chain, the top project stack frame, bounded logs, referenced artifacts such as videos, GIFs, and the trace viewer archive, selected runtime/configuration metadata, redaction rules applied, size limits, and suggested Doctor/MCP commands. The single trace archive includes `shaft-trace.json`, `shaft-network.har`, and `SHAFT Trace Report.html`. The JSON `actions` array records ordered Selenium browser, element, touch, and validation events with status, duration, locator, URL, exception summary, attachment summaries, and redacted metadata. When enabled, the trace JSON also includes `network`, `console`, and `browserObservability` sections for browser HTTP exchanges, console messages, and unsupported-capability warnings. SHAFT also writes the archive under `target/shaft-traces/ /` with an `index.json` for local tooling. The bundle references existing artifacts instead of copying every raw file. Secrets in common headers, cookies, token/password assignments, and sensitive URL query values are masked before the JSON is attached. Disable the bundle with `shaft.diagnostics.enabled=false` or lower the ZIP entry cap with `shaft.diagnostics.maxArtifactMb`. ## Failure briefs and attachment manifest Failed and broken tests also attach a compact failure brief beside the diagnostics bundle: - `SHAFT Failure Brief.html` opens first in the Allure report and summarizes the failure category, message, top project stack frame, and the first artifacts to inspect. - `shaft-failure-brief.json` exposes the same triage data for CI bots and local tooling. - `shaft-attachments-manifest.json` lists SHAFT-owned evidence such as screenshots, videos, traces, page snapshots, API request/response artifacts, accessibility reports, performance reports, and logs. The brief uses the same redaction rules as the diagnostics bundle, so common headers, cookies, token/password assignments, and sensitive URL query values are masked before the JSON and HTML are attached. API request labels omit query strings and include the method, path, status code, and response time when available. Allure 3 runs also receive a `categories.json` file in `allure-results/` with SHAFT's default failure groups for assertion, stale element, timeout, locator, API, accessibility, visual, and infrastructure failures. These categories are plain Allure metadata and do not require a custom Allure plugin. The attachment manifest is built from SHAFT's common attachment path and API filter. Specialized Allure-native attachments, such as visual comparison diffs, can still appear directly in the Allure report before they are routed through the central manifest path. ## Locator health reports Enable locator health reporting when you want a run-level view of slow or flaky web locators without changing test code. ```properties title="src/main/resources/properties/custom.properties" shaft.locatorHealth.enabled=true shaft.locatorHealth.warnBelowScore=70 shaft.locatorHealth.attachDashboard=true shaft.locatorHealth.failBelowScore=-1 slowLocatorThresholdMillis=750 ``` When enabled, SHAFT records lookup counts, unique/no-match/multi-match/stale rates, average and p95 lookup time, polling attempts, timeouts, slow lookups, SHAFT Heal attempts, accepted recoveries, confidence, and selected replacement locators when available. It scores each locator, flags selector smells such as absolute XPath, index-heavy XPath, generated IDs, text-only selectors, and deep CSS chains, then adds plain-language recommendations. At the end of the run it writes HTML and JSON reports under `execution-summary/locator-health/` and attaches the JSON export to Allure. The HTML dashboard is attached when `shaft.locatorHealth.attachDashboard=true`. Keep `shaft.locatorHealth.failBelowScore=-1` while introducing the report; set it to a score threshold only after the suite has a stable baseline. The older `locatorHealthReportEnabled=true` key remains supported. When the failure trace viewer is enabled, failed-test trace JSON also includes the current locator health snapshot. ## Flake and auto-wait profiler Enable the flake profiler when you need action-level timing in Allure without turning every wait into custom test code. ```properties title="src/main/resources/properties/custom.properties" shaft.flakeProfiler.enabled=true shaft.flakeProfiler.attachPerTest=true shaft.flakeProfiler.failOnSevereFlakeRisk=false shaft.flakeProfiler.slowActionThresholdMs=2000 ``` When enabled, SHAFT attaches `flake-profile.json` and `Flake Profile` HTML to Allure for tests that produce profiler signals. The profile records element actions, assertions, verifications, retry attempts, locator lookup counts, match counts, wait polling loops, stale/healing signals, and slow or wait-heavy actions. Screenshot capture, page snapshots, and report attachment time are tracked as evidence costs so element action duration is not inflated by report generation. Keep `shaft.flakeProfiler.failOnSevereFlakeRisk=false` while establishing a baseline. Turn it on only after the suite has known thresholds for slow, wait-heavy, stale, healed, or failing actions. ## Related - [What's new: evidence](/docs/features/whats-new/evidence) - [Architecture](/docs/features/architecture) - [Modules](/docs/features/modules) - [Underlying technology](/docs/features/modules#technology) - [BrowserStack](/docs/integrations/browserstack) --- # Testing API updates Canonical HTML: https://shafthq.github.io/docs/features/whats-new/testing Markdown: https://shafthq.github.io/md/features/whats-new/testing.md # Testing API updates Pick the test surface that owns the behavior, then follow its full guide for exact Java APIs and lifecycle rules. Create a supported Playwright session explicitly: ```java SHAFT.GUI.Playwright driver = new SHAFT.GUI.Playwright(); ``` ## Related - [Web testing](/docs/testing/web) - [What's new since modularization](/docs/features/whats-new) --- # What's new since modularization Canonical HTML: https://shafthq.github.io/docs/features/whats-new Markdown: https://shafthq.github.io/md/features/whats-new.md # What's new since modularization Find user-facing capabilities added since `10.2.20260610` (2026-06-10). Search by feature name or filter by the job you need to complete. Each result opens a focused page with setup, usage, and canonical guide links. Use the search field directly. No setup is required: ```text GraphQL ``` ## Related - [Upgrade an existing project](/docs/start/upgrade) - [Features and modules](/docs/features/modules) --- # Maintainer runbooks Canonical HTML: https://shafthq.github.io/docs/maintainers/overview Markdown: https://shafthq.github.io/md/maintainers/overview.md # Maintainer runbooks These pages document repository operations. They are not required to consume SHAFT as a test framework. - [Maven reactor](/docs/maintainers/reactor) - [Documentation site operations](/docs/maintainers/site-operations) - [Maven Central publication](/docs/maintainers/publication) - [Pilot release](/docs/maintainers/pilot-release) - [MCP publication and deployment](/docs/maintainers/mcp-deployment) - [CI failure investigation](/docs/maintainers/ci-failure-investigation) - [Agent guidance maintenance](/docs/maintainers/agent-guidance) - [Agent tooling](/docs/maintainers/agent-tooling) - [Visual provider boundary](/docs/maintainers/visual-provider) - [Repository history rewrite](/docs/maintainers/history-rewrite) ## Repository-local operating contracts Volatile instructions stay beside the scripts and configuration they govern. Use these source files instead of copying their inventories into this guide: - [Agent skills map](https://github.com/ShaftHQ/SHAFT_ENGINE/blob/main/.agents/skills/README.md) - [GitHub Actions workflow inventory](https://github.com/ShaftHQ/SHAFT_ENGINE/blob/main/.github/workflows/README.md) - [SHAFT skill evaluation contract](https://github.com/ShaftHQ/SHAFT_ENGINE/blob/main/agent-plugins/shaft-skills/evals/README.md) - [IntelliJ capture validation runbook](https://github.com/ShaftHQ/SHAFT_ENGINE/blob/main/tools/intellij-plugin-recording/README.md) - [Graphify repository-map runbook](https://github.com/ShaftHQ/SHAFT_ENGINE/blob/main/tools/repository-map/README.md) --- # Documentation site operations Canonical HTML: https://shafthq.github.io/docs/maintainers/site-operations Markdown: https://shafthq.github.io/md/maintainers/site-operations.md # Documentation site operations The Docusaurus repository is the canonical source for SHAFT product, architecture, usage, migration, and maintainer documentation. The canonical public URL is [shafthq.github.io](https://shafthq.github.io). GitHub Pages publishes the public guide for users. Cloudflare Workers hosts the `/api/gemini-proxy` backend API that AutoBot calls from the browser and can also serve the same static build as an operational mirror. Do not change Squarespace DNS for this GitHub Pages recovery path. ## Local development Use Node.js 20 and Yarn 1.22.22. Install the repository's version-matched Chromium once after installing dependencies: ```bash yarn install yarn playwright install chromium yarn start ``` Cloudflare Workers Builds uses `wrangler.toml`, builds with `yarn build`, uploads `build/` through `[assets]`, and routes `/api/gemini-proxy` through `worker/index.js`. Set `YARN_VERSION=1.22.22` and `GEMINI_API_KEY` in Cloudflare environment variables; do not commit secrets. GitHub Pages uses `.github/workflows/deploy.yml` and publishes `build/` as the canonical public guide at `https://shafthq.github.io/`. ## Scheduled checks - `.github/workflows/link-check.yml` runs lychee weekly (Monday) with `lychee.toml`. It never fails a PR; on broken external links it opens or updates one issue labelled `broken-links`, and closes it when a later run is clean. Internal links and anchors are enforced by `yarn build`. - `.github/workflows/a11y-nightly.yml` builds the site and runs `scripts/a11y-sitewide.mjs` every night: axe (WCAG 2.2 A/AA) on every page in light and dark themes plus a 320px page-overflow sweep. The report is uploaded as an artifact, and one issue labelled `accessibility` is opened, updated, or closed. Run it locally against a served build with `node scripts/a11y-sitewide.mjs --base http://127.0.0.1:3000 --build build`. ## Egypt accessibility If Egyptian users cannot open the guide, test both providers before changing site code: ```bash curl -4 -I https://shafthq.github.io/ curl -4 -I https://shaft-engine.mohab-mohieeldeen.workers.dev/autobot-index.json curl -4 -i -X OPTIONS https://shaft-engine.mohab-mohieeldeen.workers.dev/api/gemini-proxy \ -H "Origin: https://shafthq.github.io" \ -H "Access-Control-Request-Method: POST" \ -H "Access-Control-Request-Headers: content-type" ``` On 2026-06-23, Cairo probes on Link Egypt timed out against Netlify while Cloudflare and GitHub Pages returned HTTP 200. The recovery path is to keep users on `https://shafthq.github.io/` and let AutoBot call the Cloudflare Worker API with CORS enabled for that origin. ## Validation ```bash yarn test yarn typecheck yarn build yarn test:a11y yarn test:playwright ``` The build fails on broken Markdown links and duplicated long prose or code blocks. The Playwright suite covers desktop and mobile rendering, canonical routes, code copying, and dark mode. The focused accessibility command audits the built homepage in headless Playwright-managed Chromium with axe-core. It does not depend on an installed browser or ChromeDriver. After changing the Playwright version, run `yarn playwright install chromium` before the audit. ## Search and AutoBot Local search runs on `@easyops-cn/docusaurus-search-local` (migrated from `@cmfcmf/docusaurus-search-local` in issue #865 for better result relevance). Exclusions are now declared at the source level via the plugin's `ignoreFiles` option in `docusaurus.config.js`, instead of the old `scripts/prune-search-index.mjs` post-build step (removed): it excludes only `docs/archive/` from `build/search-index.json`. Maintainer docs stay indexed, matching the #795 decision ("make docs/maintainers/ fully public"). Because search is static, it works anywhere the Docusaurus assets load. AutoBot indexes Markdown and MDX by route and heading at startup, retrieves the eight best chunks for each question, and caps injected documentation context at 80,000 characters. `yarn build` writes `static/autobot-index.json`; the Cloudflare function fetches that static index at runtime, so AutoBot does not depend on GitHub Pages server-side code. `EXCLUDED_DIRECTORIES` in `shared/docs-loader.mjs` excludes `archive/` and `superpowers/` only -- `docs/maintainers/` is intentionally included per #795 ("make docs/maintainers/ fully public"), and `tests/docs-loader.test.js` asserts that inclusion; local search's `ignoreFiles` matches. The public HTTP request and response contract remains unchanged. ## Content changes - Keep one canonical page per concept. - Reuse commands and dependency snippets from `src/components/DocSnippets`. - Put historical records under `docs/archive/`; archive routes are unlisted and emit `noindex`. - Add redirects to the `plugin-client-redirects` config in `docusaurus.config.js` when replacing a public route. - Coordinate public behavior changes with a linked SHAFT_ENGINE pull request. ## AI / search visibility After a deploy that changes SEO, schema, `robots.txt`, or `llms.txt`: 1. Confirm homepage ` ` still has `msvalidate.01` (Bing Webmaster) and `https://shafthq.github.io/robots.txt` still Allows GPTBot, OAI-SearchBot, ChatGPT-User, Google-Extended, PerplexityBot, ClaudeBot, Claude-SearchBot, Applebot-Extended, and bingbot (and still Disallows `/assets/`). 2. Confirm `https://shafthq.github.io/llms.txt` and `llms-full.txt` return HTTP 200. 3. Confirm `/search` HTML has `noindex` and is absent from `sitemap.xml`. 4. Confirm an unknown path returns HTTP 404 with `noindex`. 5. Spot-check homepage LD+JSON for `FAQPage`, `SoftwareApplication`, `Organization` (with Maven Central in `sameAs`), and `SoftwareSourceCode`. 6. Spot-check one docs page for `BreadcrumbList`. 7. Re-run AI-style queries (ChatGPT / Gemini / Perplexity / Claude / Grok) for "What is SHAFT_ENGINE test automation?" and note whether the user guide is cited. --- # MCP publication and deployment Canonical HTML: https://shafthq.github.io/docs/maintainers/mcp-deployment Markdown: https://shafthq.github.io/md/maintainers/mcp-deployment.md # MCP publication and deployment The supported public distribution channels are Maven Central, GHCR, and the MCP Registry. Deployment templates do not imply that a SHAFT-hosted HTTP endpoint is active. ## Local container check Run from the SHAFT_ENGINE repository root: ```bash docker build -f shaft-mcp/Dockerfile.smithery.build -t shaft-mcp-http . docker run --rm -p 8081:8081 shaft-mcp-http ``` Connect an MCP client to `http://localhost:8081/mcp`. ## Publication flow 1. The Maven Central workflow publishes `io.github.shafthq:shaft-mcp` with the complete reactor. 2. `publish-shaft-mcp.yml` pushes `ghcr.io/shafthq/shaft-engine-mcp: ` and `latest`. 3. The workflow renders `shaft-mcp/server.json`, validates it against the MCP Registry schema, and publishes it with GitHub OIDC. 4. `deploy-shaft-mcp.yml` invokes configured Render and Fly.io integrations and records the Smithery handoff. Missing external configuration is an explicit no-op. All container builds use the repository root as their build context, keeping the MCP server aligned with the canonical `shaft-engine` dependency. ## Optional hosted deployments Hosted deployments use Streamable HTTP at `/mcp` with `SPRING_PROFILES_ACTIVE=http`. The server defaults to port `8081`; platforms may override it through `PORT`. - **Smithery:** connect the `ShaftHQ/SHAFT_ENGINE` repository. The root `smithery.yaml` selects `shaft-mcp/Dockerfile.smithery.build`. - **Render:** the root `render.yaml` selects `shaft-mcp/Dockerfile.render`. Releases call `RENDER_DEPLOY_HOOK_URL` only when configured. - **Fly.io:** deploy from the repository root: ```bash flyctl deploy --remote-only --config shaft-mcp/fly.toml ``` - **Dockhold:** the root `dockhold.json` selects `shaft-mcp/Dockerfile.dockhold`, which starts from the published GHCR image. Open the [Dockhold deploy form](https://app.dockhold.eu/new?repo=https://github.com/ShaftHQ/SHAFT_ENGINE&name=shaft-mcp&ref=oss-shaft) and pick the 1 GB app size. A browser session does not fit in 512 MB. Sizes and prices: [dockhold.eu/pricing](https://dockhold.eu/pricing). Do not advertise a hosted endpoint until its external integration and secrets are configured and its `/mcp` endpoint passes initialization. --- # Agent guidance maintenance Canonical HTML: https://shafthq.github.io/docs/maintainers/agent-guidance Markdown: https://shafthq.github.io/md/maintainers/agent-guidance.md # Agent guidance maintenance ## Goal Keep automatically loaded guidance small, stable, and repository-specific. Executable configuration is authoritative; volatile details and long procedures belong in runbooks or skills. ## Ownership This repository's own canonical guidance lives here: | Content | Canonical location | |---|---| | Repository identity, defaults, content rules, validation | `AGENTS.md` | | Style authority: palette, typography tokens, prose rules | `DESIGN_LANGUAGE.md` | | Host-only behavior | `.github/copilot-instructions.md` | | Product and maintainer documentation | Docusaurus routes under `docs/` | | Executable behavior and history | `ShaftHQ/SHAFT_ENGINE` (a separate repository) configuration, source, and Git history | `ShaftHQ/SHAFT_ENGINE` keeps its own agent-guidance layout, referenced above but not present in this repository: | Content | Canonical location in `SHAFT_ENGINE` | |---|---| | Repository identity, global safety, routing, validation | `AGENTS.md` | | Host-only behavior | `CLAUDE.md` | | Codex skill discovery metadata | `.agents/skills/*/SKILL.md` | | Production and test Java rules | `.github/instructions/*.instructions.md` | | CI, flaky-test, and release workflows | `.github/skills/*/SKILL.md` | Codex bridge skills in `SHAFT_ENGINE` contain frontmatter and a link to one canonical rule file; they do not copy the canonical body into `.agents/skills/`. ## Update Rules 1. Add a durable rule only when it is recurring, repository-specific, and not already enforced by executable configuration. 2. Put it in the narrowest owning surface. Keep generic agent behavior out of repository guidance. 3. Replace or remove an existing rule instead of adding another version. 4. Keep dates, incidents, volatile versions, long examples, and session logs outside automatically loaded files. 5. Use task skills for procedures and path-scoped instructions for Java rules. 6. Update a Codex bridge description when its trigger changes, but keep the bridge body minimal. 7. Link a documentation-site pull request for public behavior changes. Do not add public guides or non-root READMEs to SHAFT_ENGINE. ## Audit This repository's own checks run via `yarn test` (including `tests/docs-quality.test.js`, which enforces the admonition vocabulary and content style guide rules from `DESIGN_LANGUAGE.md`), `yarn typecheck`, and `yarn build`: ```bash yarn test yarn typecheck yarn build ``` `ShaftHQ/SHAFT_ENGINE` runs a separate audit against its own agent-guidance corpus, not present in this repository: ```bash python3 scripts/ci/validate_agent_guidance.py python3 -m unittest tests.scripts.test_validate_agent_guidance git diff --check ``` Budgets and routing rules for that audit live in `scripts/ci/agent_guidance_budget.json` in `SHAFT_ENGINE`. It checks file and host context budgets, both skill roots, local links, path scopes, stale references, costly mandates, duplicate paragraphs, and the manual paid-refresh gate; its refresh workflow runs only by manual dispatch, and invokes paid AI review only when the deterministic audit fails or a maintainer supplies a reason and forces review. --- # Agent tooling Canonical HTML: https://shafthq.github.io/docs/maintainers/agent-tooling Markdown: https://shafthq.github.io/md/maintainers/agent-tooling.md # Agent tooling ## Goal One page to (re)install, operate, and update the third-party tools that back agent-assisted SHAFT maintenance. Repository guidance (`AGENTS.md`, `CONTRIBUTING.md`) references this runbook instead of restating it. ## Inventory | Tool | Role | Install source | |---|---|---| | memory CLI | Durable repo memory in `.memory/` | npm `@aictx/memory@0.1.55` (pin in `scripts/ci/validate_agent_setup.py`) | | gbrain | Semantic repo index, knowledge graph, MCP server | Local git checkout, built with Bun | | gbrain-ollama | Embedding backend for gbrain | Docker `ollama/ollama` + `nomic-embed-text` model | | graphify | Deterministic repository map (structure queries, pre-search file selection) | Repository controller using an isolated uv tool environment | | context7 | Post-cutoff library docs MCP | `npx @upstash/context7-mcp` (project `.mcp.json`) | | maven-tools-mcp | Live Maven Central facts MCP | Optional receipt-pinned Java 25 JAR in an installer-owned shared cache discovered by the [ChaosEngine installer](https://github.com/ShaftHQ/SHAFT_ENGINE/blob/main/chaos-engine/INSTALL.md#optional-native-maven-tools-mcp) | | ChaosEngine add-ons | Opt-in `shaft-core-developers`, `shaft-engine-users`, and `design-skills` bundles (never installed by default) | `--with- ` installer flags; see [Add optional ChaosEngine skills](/docs/agentic/chaos-engine-addons) | | Claude Code plugins | jdtls-lsp, frontend-design, mcp-server-dev | Auto-installed from `.claude/settings.json` `enabledPlugins` | The `fable` and `superpowers` plugins were removed in the 2026-07-17 harness consolidation: `act-as-fable` (a repo skill, not a plugin — see `.claude/skills/act-as-fable/`) is now the sole methodology authority, and UI evidence gathering moved from `webapp-testing`/`accessibility-review`/ `chrome-devtools-mcp` to shaft-mcp's own browser tools (screenshots, `browser_accessibility_audit`, `browser_network_requests`). User-level config (`~/.claude`) now deploys from the source-controlled `.claude/user-harness/` via `scripts/agents/sync_user_harness.py` (`--check`/`--apply`) instead of being hand-maintained. ## Task-time knowledge retrieval Treat Memory, MemPalace, and Graphify as advisory for ordinary implementation tasks. Keep session-start summaries best effort, and make one task-specific query only when it answers a concrete question. Verify retrieved paths and claims against current files, then use targeted `rg` to confirm callers and blast radius. A missing, stale, corrupt, timed-out, or inaccessible store never blocks ordinary task work or completion. Do not retry, repair, refresh, mine, checkpoint, poll, or watch a store per task. The scheduled or explicitly requested maintenance owner updates derived stores. Installation, upgrade, explicit maintenance, `status`, and `doctor` remain strict. An unhealthy selected component makes requested `status` or `doctor` health `recovery-required`. Installed `status` and `doctor` output also identifies each component's `owner`, `scope`, `lifecycle`, and `taskImpact`. Use those fields to distinguish installer-owned project files, persistent project data, single-writer derived repository data, and the optional installer-owned Maven cache. Do not infer cleanup authority from a health key alone. ## memory CLI ```bash npm install -g @aictx/memory@0.1.55 memory check ``` Keep the version pinned to the value in `scripts/ci/validate_agent_setup.py` (`MEMORY_PACKAGE`). Saves use intent-first JSON on stdin (`memory remember --stdin`); never mirror durable facts outside `.memory/`. ## gbrain ### Install / update gbrain is installed from a local git checkout and built with Bun (the public npm package named `gbrain` is an unrelated project — do not install it): ```bash cd # e.g. ~/gbrain git pull && bun install gbrain apply-migrations --yes --non-interactive gbrain doctor ``` On Windows, Bun's postinstall hook currently fails on a shell-redirect parse bug; running `gbrain apply-migrations --yes` manually afterwards is the documented workaround. `gbrain upgrade` / `gbrain check-update` automate this flow where the release channel is reachable. Brain home is `~/.gbrain/` (PGLite database, `config.json`). Required config: ```json { "engine": "pglite", "embedding_model": "ollama:nomic-embed-text", "embedding_dimensions": 768 } ``` Inline embedding through Ollama is mandatory on PGLite: the background jobs-work queue is Postgres-only and never drains on PGLite, so without the `ollama:` model syncs import unembedded, invisible chunks. ### Embedding backend ```bash docker run -d --name gbrain-ollama --restart unless-stopped \ -p 127.0.0.1:11434:11434 ollama/ollama docker exec gbrain-ollama ollama pull nomic-embed-text curl http://127.0.0.1:11434/api/tags # must list nomic-embed-text ``` The `unless-stopped` restart policy matters: without it the container stays down after a reboot and every sync silently loses embedding coverage. ### Sources and continuous operation Register both repos once, then let autopilot keep the brain fresh: ```bash gbrain sources add shaft-engine --path gbrain sources add shaft-userguide --path gbrain sync --all --no-hard-deadline # first full sync outlives the 1h watchdog ``` Continuous operation: on macOS/Linux, `gbrain autopilot --install --repo ` registers the self-maintaining daemon. **`autopilot --install` has no Windows target** (launchd/systemd/cron only). The SHAFT_ENGINE Windows installer manages Graphify only; operate gbrain separately on Windows. The source-controlled installer registers the daily Graphify refresh task: ```powershell cd powershell -ExecutionPolicy Bypass -File tools\agent-infra\install-agent-tasks.ps1 ``` It points the user-level `graphify-refresh` Scheduled Task at the repository's thin `tools/agent-infra/graphify-refresh.cmd` adapter. The adapter derives its repository root, then calls the same portable Python controller used by maintainers. Logs stay machine-local under `~/.agent-infra/logs/`. For gbrain, `gbrain dream --dry-run` previews a maintenance cycle. Health and recommendations: `gbrain doctor`, `gbrain features`, `gbrain stats`. Embed backlogs queued as jobs never drain on PGLite (no worker); cancel the job (`gbrain jobs cancel `) and run `gbrain embed --stale`, or let the nightly dream absorb them. Enabled quality probes (doctor recommendations): ```bash gbrain config set autopilot.nightly_quality_probe.enabled true gbrain config set autopilot.conversation_parser_probe.enabled true ``` ### Operating caveats - **PGLite is single-writer.** A running `gbrain serve` (Claude Code MCP session) holds the database lock; concurrent CLI commands time out. Stop the MCP process or run CLI maintenance between sessions. - **Never run `gbrain frontmatter validate --fix` against the docs repo.** Docusaurus `slug:` frontmatter defines public site URLs; gbrain reads the field as its own page slug and would otherwise reject it as `SLUG_MISMATCH` — the fix would rewrite published routes, so this stays permanently off-limits regardless of the flag below. - **This source has opted in to `gbrain sources trust-frontmatter-slug shaft-userguide`** (upstream PR: [garrytan/gbrain#2899](https://github.com/garrytan/gbrain/pull/2899), tracked as [SHAFT_ENGINE#3618](https://github.com/ShaftHQ/SHAFT_ENGINE/issues/3618)). Sync now honors the Docusaurus-declared slug for mismatched files instead of skipping them — the 83 previously-unindexed blog posts and custom-route docs pages import cleanly. Re-run `gbrain sources trust-frontmatter-slug shaft-userguide` after any brain re-init (the flag lives in `sources.config`, not in this repo). `gbrain doctor`'s `frontmatter_integrity` check still WARNs on these files — that's a separate lint pass unaffected by the trust flag, not a regression. Until the upstream PR merges, the flag requires the local ` ` on the `feature/trust-frontmatter-slug` branch (or any branch built from it). - **Migration 0.32.2** refuses to run while a registered source has uncommitted git changes; commit first, then re-run `gbrain apply-migrations --yes`. - gbrain **supplements** `.memory/` and grep; it never replaces them. The `retrieval-reflex` policy skill in SHAFT_ENGINE (`skills/retrieval-reflex/`) defines when agents should query it. ## graphify Deterministic repository map, complementary to gbrain — graphify answers *structure* (which files/modules relate, zero DB locking, works offline); gbrain answers *meaning* (semantic retrieval). Both stay. For an ordinary task, query an available shared cache only as an untrusted lead. Check it once when a concrete structure question justifies the query, verify every returned path against live files, and supplement caller searches with targeted `rg`. Never infer completeness or "no callers" from the graph. An absent, stale, or inaccessible cache is a non-blocking degraded result; do not refresh or watch it from the task. The following refresh command is for the explicit maintenance owner, not a per-task or pre-PR requirement: ```powershell py -3 tools/repository-map/graphify_maintenance.py refresh --root . ``` Run the controller from the repository's primary checkout. It resolves the explicit `--root`, builds the gitignored `graphify-out/` cache, audits extraction coverage, clusters the graph, and records the freshness marker in this fixed order: ```text build -> audit -> cluster -> marker ``` Refresh requires clean tracked sources. The resolver also reports a cache as stale while staged or unstaged tracked changes exist, so a marker can never mislabel worktree content as the checked-out `HEAD`. The marker binds the completed cache to the exact Git revision and manifest that Graphify indexed. A failed build, audit, or cluster stage leaves no current marker, so readers cannot accept a partial cache. Linked worktrees must not refresh or record the shared cache. The controller pins Graphify and runs it through an isolated uv tool invocation: ```powershell uv tool run --with tree-sitter-sql --from graphifyy==0.9.42 graphify ``` `graphifyy` is the distribution name, while `graphify` is its command. The ephemeral `tree-sitter-sql` dependency enables SQL parsing without changing a persistent global tool installation. Accepted caches use Graphify's deterministic hub-derived community labels. Before clustering, the controller removes saved label and membership-signature sidecars, clears Graphify's ambient backend selectors for that subprocess, and uses an isolated home inside the ignored cache. This prevents user or repository provider configuration, an API key, or a local endpoint from silently turning refresh into a networked, model-dependent labeling run. It also prevents a previous semantic label from being reused after community membership changes. Semantic labels are optional and are not part of cache freshness. Run `graphify label .` separately when you want an ephemeral model-generated view; the next accepted refresh replaces those names with current hub-derived labels. Audit an existing cache without modifying it: ```powershell py -3 tools/repository-map/graphify_maintenance.py audit --root . ``` The audit compares every normalized manifest path with graph node sources and reports four classifications: | Classification | Meaning | Result | |---|---|---| | `covered` | Graphify emitted at least one node for the source | Pass | | `expected_data_only` | A JSON data file emitted no code node | Visible in the report, but nonfatal | | `missing_optional_parser` | A SQL source emitted no node | Actionable failure | | `unexpected_parser_gap` | Any other source emitted no node | Actionable failure | Zero-node JSON files remain visible because they are expected data inputs, not proof of parser coverage. Zero-node SQL or other source files fail the audit; fix the parser or upstream extraction gap before accepting the cache. This user-guide repository defines its credential-free code/configuration corpus in the root `.graphifyignore`. YAML, plain-text, standalone HTML, SVG, and raster media are explicit code-only exclusions, so they never enter the manifest as false parser gaps. Keep supported JavaScript, TypeScript, Markdown, MDX, and JSON inputs in the corpus; do not make an uncovered supported source nonfatal. Only one refresh may run for a repository at a time. The controller holds a nonblocking advisory operating-system lock across build, audit, cluster, and marker recording. A contender fails before cache mutation. The operating system releases the lock if the process exits or is killed, so there is no stale lock file to delete. When a concrete task question needs it, agents can check the shared cache once with `py -3 tools/repository-map/resolve_graph_out.py --check`. The command exits successfully only when the marker matches the revision being inspected. Missing caches report `absent`; unmarked, changed, or revision-mismatched caches report `stale`. In either degraded mode, continue with live files and targeted `rg` instead of treating the map as current evidence or starting maintenance. The daily `graphify-refresh` Scheduled Task uses the same controller and safety rules. See the [repository-map runbook](https://github.com/ShaftHQ/SHAFT_ENGINE/blob/main/tools/repository-map/README.md) for the executable shared-cache contract. ## MCP servers and plugins Context7 is project-scoped in SHAFT_ENGINE `.mcp.json` and runs through `npx`, so it needs Node. ChaosEngine installs its own pinned Node.js 24.19.0 and does not depend on ambient Node, npm, or Python after installation. Maven Tools is an optional native Java 25 server: the [ChaosEngine installer](https://github.com/ShaftHQ/SHAFT_ENGINE/blob/main/chaos-engine/INSTALL.md#optional-native-maven-tools-mcp) discovers a verified, receipt-pinned JAR and writes the project host entries. It omits those entries when no verified runtime is installed; Docker is not required. The gbrain MCP server is user-scoped (`~/.claude.json`): `gbrain serve` over stdio. Claude Code plugins install themselves from `.claude/settings.json` `enabledPlugins`/`extraKnownMarketplaces` on first session start. ### Maven Tools MCP cache The Maven Tools MCP version directory is an immutable, installer-owned cache. Parallel projects may reuse the same verified JAR and `install-receipt.json` pair without mutation. Project install and uninstall change only project host configuration; they never install, reference-count, purge, or remove the shared cache automatically. Install it with `--with-maven-tools`; this cannot be combined with `--skip-tools`. Java selection prefers `CHAOSENGINE_JAVA`, then `JAVA_HOME`, then `PATH`, followed by verified Temurin 25.0.4+7. Windows arm64 uses the official Windows x64 build through Windows 11 x64 emulation and fails closed if its Java 25 probe cannot run. Normal installation needs no preinstalled Python or Node: ```powershell irm "https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/chaos-engine/install.ps1" | iex ``` ```sh url="https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/chaos-engine/install.sh"; curl -fsSL "$url" | bash -s -- "$url" ``` Install Maven Tools on POSIX: ```sh url="https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/chaos-engine/install.sh"; curl -fsSL "$url" | bash -s -- "$url" --with-maven-tools ``` Install Maven Tools from PowerShell: ```powershell $installer = irm "https://raw.githubusercontent.com/ShaftHQ/SHAFT_ENGINE/main/chaos-engine/install.ps1"; & ([scriptblock]::Create($installer)) -WithMavenTools ``` The capture below is a real `--with-maven-tools` install in a throwaway directory whose `PATH` had no `python`, `python3`, `node`, or `npm`. The wrapper bootstrapped CPython 3.14.6 through uv. Install Maven Tools and Verify installation both passed. ![ChaosEngine --with-maven-tools install with no preinstalled Python or Node: command -v python, python3, node, and npm miss, uv bootstraps cpython-3.14.6, and Install Maven Tools plus Verify installation pass.](/img/agentic/chaos-engine-installer-managed-runtimes.png) Wrappers verify uv 0.11.29 before using uv-managed Python 3.10. Checksum, extraction, or health failures leave the previous immutable generation active. Inspect the selected cache or purge exactly version `3.2.0`: ```text python .chaos-engine/install.py cache status --component maven-tools-mcp python .chaos-engine/install.py cache purge --component maven-tools-mcp --version 3.2.0 ``` `cache status` validates the path, reparse points, receipt, version, pinned commit, and SHA-256, then reports `healthy`, `absent`, `invalid`, or `busy`. `cache purge` takes a non-waiting user-cache lock and removes only the exact verified version's receipt-owned files. It refuses modified, unknown, linked, broad, or busy targets. An absent version is already a successful result. Project uninstall removes its Python and Node generations but retains this shared cache. Exact-version purge removes only receipt-verified owned files. Plan Mode stays read-only. It may finish in a checkout that was already dirty without demanding commits, pushes, harness synchronization, tracker writes, cleanup, or mutation of unrelated work. Confirmed NUL corruption still blocks because it is an immediate repository-safety condition. ## Local model runs Moved from the SHAFT_ENGINE README (ShaftHQ/SHAFT_ENGINE#7095). - FreeToken / llama.cpp on the ROG laptop: resolve it with `chaos-engine/skills/local-agency` (`dispatch.py --prefer freetoken`, or the OpenAI-compatible endpoint on `:8080`). - Colibri is optional, and only when `http://127.0.0.1:8000` is READY. It is not the default on 6 GB VRAM laptops. - Proof-runner note (ShaftHQ/SHAFT_ENGINE#6045): the preferred dense coder is Qwen2.5-Coder-7B Q4_K_M via llama-server when the FreeToken KV cache is about 8k. ## Health checklist ```bash memory check gbrain doctor --fast curl http://127.0.0.1:11434/api/tags # ollama up, nomic-embed-text present docker ps --format '{{.Names}} {{.Status}}' | grep gbrain-ollama py -3 scripts/ci/validate_agent_setup.py # in SHAFT_ENGINE ``` ## Related - [Add optional ChaosEngine skills](/docs/agentic/chaos-engine-addons) - [Maintainer overview](/docs/maintainers/overview) - [Agent guidance maintenance](/docs/maintainers/agent-guidance) --- # CI failure investigation Canonical HTML: https://shafthq.github.io/docs/maintainers/ci-failure-investigation Markdown: https://shafthq.github.io/md/maintainers/ci-failure-investigation.md # CI failure and Allure artifact investigation Use this runbook when an agent is assigned a ticket for failing GitHub Actions E2E, local, cloud, or scheduled workflows. ## 1. Start from authenticated GitHub metadata Normalize GitHub CLI credentials first so private logs and artifacts are available without exposing tokens: ```bash source scripts/ci/github-auth-env.sh GH_TOKEN="${GITHUB_TOKEN:-${GH_TOKEN:-}}" gh auth status -h github.com ``` Collect run, job, failed-step, and artifact metadata before changing code: ```bash RUN_ID= GH_TOKEN="${GITHUB_TOKEN:-${GH_TOKEN:-}}" gh run view "$RUN_ID" \ --repo ShaftHQ/SHAFT_ENGINE \ --json name,workflowName,conclusion,status,createdAt,updatedAt,url,jobs \ | jq '{name,workflowName,conclusion,status,createdAt,updatedAt,url,jobs:[.jobs[]|{name,conclusion,status,databaseId,failedSteps:[.steps[]|select(.conclusion=="failure")|{name,number,conclusion}]}]}' GH_TOKEN="${GITHUB_TOKEN:-${GH_TOKEN:-}}" gh api \ "repos/ShaftHQ/SHAFT_ENGINE/actions/runs/$RUN_ID/artifacts" \ --jq '.artifacts[] | [.name,.size_in_bytes,.expired,.archive_download_url] | @tsv' ``` Download job logs for each failed job and search for post-test summaries, setup failures, and root-cause stack traces: ```bash JOB_ID= mkdir -p /tmp/shaft-ci/logs GH_TOKEN="${GITHUB_TOKEN:-${GH_TOKEN:-}}" gh api \ "repos/ShaftHQ/SHAFT_ENGINE/actions/jobs/$JOB_ID/logs" \ > "/tmp/shaft-ci/logs/$JOB_ID.txt" rg -n "(Allure Report Summary|::error::|FAILED|BROKEN|AssertionError|Exception|SessionNotCreated|401|TimeoutException)" \ "/tmp/shaft-ci/logs/$JOB_ID.txt" ``` ## 2. Traverse SHAFT single-file Allure artifacts quickly Many SHAFT workflow artifacts named `*_Allure.html` are already self-contained HTML reports, not zip archives. `gh run download` may fail with `zip: not a valid zip file`; in that case, save the artifact API response directly as an HTML file. ```bash RUN_ID= mkdir -p /tmp/shaft-ci/allure-html GH_TOKEN="${GITHUB_TOKEN:-${GH_TOKEN:-}}" gh api \ "repos/ShaftHQ/SHAFT_ENGINE/actions/runs/$RUN_ID/artifacts" \ --jq '.artifacts[] | select(.name | endswith("_Allure.html")) | [.name,.archive_download_url] | @tsv' \ | while IFS=$'\t' read -r name url; do GH_TOKEN="${GITHUB_TOKEN:-${GH_TOKEN:-}}" gh api "$url" > "/tmp/shaft-ci/allure-html/$name" done ``` The self-contained Allure HTML embeds report files as JavaScript calls like `d("data/test-results/...json"," ")`. Parse those embedded JSON payloads instead of opening a browser when you need the failure list fast: ```bash python3 - <<'PY' from pathlib import Path for report in sorted(Path('/tmp/shaft-ci/allure-html').glob('*_Allure.html')): failures = [] for match in re.finditer(r'd\("([^"]+)","([A-Za-z0-9+/=]+)"\)', report.read_text(errors='ignore')): path, payload = match.groups() if path.startswith('data/test-results/') and path.endswith('.json'): data = json.loads(base64.b64decode(payload)) if data.get('status') in {'failed', 'broken'}: error = data.get('error') or {} failures.append((data.get('status'), data.get('fullName') or data.get('name'), error.get('message', ''))) print(f'\n==== {report.name}: {len(failures)} failed/broken ====') for status, name, message in failures: print(f'- {status}: {name}') print(' ' + message.replace('\r', '').replace('\n', ' | ')[:1000]) PY ``` Always count populated result JSON files before trusting any Allure status analysis: ```bash python3 - <<'PY' from pathlib import Path for report in sorted(Path('/tmp/shaft-ci/allure-html').glob('*_Allure.html')): total = 0 statuses = {} for match in re.finditer(r'd\("([^"]+)","([A-Za-z0-9+/=]+)"\)', report.read_text(errors='ignore')): path, payload = match.groups() if path.startswith('data/test-results/') and path.endswith('.json'): total += 1 status = json.loads(base64.b64decode(payload)).get('status', 'unknown') statuses[status] = statuses.get(status, 0) + 1 print(report.name, total, statuses) PY ``` If the count is zero or unexpectedly low, treat the report as empty/invalid and investigate report generation before diagnosing tests. When a run passes locally or after retries but the logs show transient failures, parse all result JSON files, not only the final summary. Retried attempts can appear as `skipped` in Allure with useful failure messages. In parallel TestNG grid jobs, messages such as `FileNotFoundException`, `Can't create an ImageInputStream`, or missing files under `target/temp/...` often point to test setup/cleanup state shared through instance fields rather than a browser-specific defect. ## 3. Separate test defects from environment/provider defects Document each failed/broken test with: - workflow/run ID, job ID, and artifact name; - Allure `fullName`, status, and concise failure signature; - whether the failure is deterministic in local targeted tests; - whether the signature points to code, test isolation, CI host behavior, credentials, or an external provider outage. - whether nearby log lines show concurrent methods from the same TestNG class sharing or deleting a resource. Under `setParallel=METHODS`, shared instance fields can race even when every method has `@BeforeMethod` and `@AfterMethod` hooks. Do not hide provider or credential failures by changing assertions. For example, a cloud-provider `401` during setup should be reported as credential/provider evidence; only fix framework/test teardown if it adds a secondary `NullPointerException` or masks the real setup failure. ## 4. PR authorship and title hygiene for agent-created work When an AI agent creates a branch or PR, make that explicit even if the GitHub token belongs to a human maintainer: - Branch names should be descriptive and preferably start with `codex/` for Codex-authored work. - PR titles created by Codex should start with `codex:` unless the user explicitly requests a different prefix. - PR bodies must state that Codex/AI authored the branch and PR, for example: `This PR was created by Codex, not manually by the maintainer whose token opened it.` - Avoid placeholder titles or bodies. Include summary, validation, limitations, and links to the issue/run evidence. --- # Maven Central publication Canonical HTML: https://shafthq.github.io/docs/maintainers/publication Markdown: https://shafthq.github.io/md/maintainers/publication.md # Maven Central publication contract SHAFT publishes the complete reactor as one Maven Central deployment. Publication is intentionally driven from the root `shaft-parent` project so the Central Portal either receives the coherent artifact set below or the release workflow stops before creating any downstream release signal. ## Public artifact set | Coordinate | Packaging | Additional required artifacts | | --- | --- | --- | | `io.github.shafthq:shaft-parent` | POM | aggregate CycloneDX JSON, signatures | | `io.github.shafthq:shaft-engine` | JAR | sources, JavaDocs, signatures | | `io.github.shafthq:shaft-pilot-core` | JAR | sources, JavaDocs, signatures | | `io.github.shafthq:shaft-capture` | JAR | sources, JavaDocs, signatures | | `io.github.shafthq:shaft-capture-proxy` | JAR | sources, JavaDocs, signatures | | `io.github.shafthq:shaft-doctor` | JAR | sources, JavaDocs, signatures | | `io.github.shafthq:shaft-ai` | JAR | sources, JavaDocs, signatures | | `io.github.shafthq:shaft-heal` | JAR | sources, JavaDocs, signatures | | `io.github.shafthq:shaft-browserstack` | JAR | sources, JavaDocs, signatures | | `io.github.shafthq:shaft-video` | JAR | sources, JavaDocs, signatures | | `io.github.shafthq:shaft-visual` | JAR | sources, JavaDocs, signatures | | `io.github.shafthq:shaft-sikulix` | JAR | sources, JavaDocs, signatures | | `io.github.shafthq:shaft-mcp` | thin JAR | sources, JavaDocs, signatures | | `io.github.shafthq:shaft-cli` | JAR | sources, JavaDocs, signatures | | `io.github.shafthq:shaft-bom` | POM | signature | | `io.github.shafthq:SHAFT_ENGINE` | relocation POM | signature | `report-aggregate` is build support only. It participates in the reactor to produce aggregate JaCoCo output but skips Maven deployment, Central publishing, and GPG signing so it cannot enter a Central deployment. The BOM may manage only artifacts in the public set. `scripts/ci/validate_maven_publication.py` rejects unpublished BOM references, missing publication plugins, an unsafe release-workflow order, or an incomplete build output set. ## Disposable dry run A normal unsigned local validation does not contact Maven Central: ```bash python3 scripts/ci/validate_reactor_versions.py python3 scripts/ci/validate_maven_publication.py python3 scripts/ci/validate_shaft_pilot_release.py mvn --batch-mode clean install -DskipTests -Dgpg.skip python3 scripts/ci/validate_maven_publication.py --check-build-outputs python3 scripts/ci/validate_shaft_pilot_release.py --check-build-outputs mvn --batch-mode -f tools/modularization/consumer-fixtures/pilot-core/pom.xml \ verify -Dshaft.version="$(mvn -f pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout)" mvn --batch-mode -f tools/modularization/consumer-fixtures/combined-modules/pom.xml \ verify -Dshaft.version="$(mvn -f pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout)" mvn --batch-mode -f tools/modularization/consumer-fixtures/mcp/pom.xml \ verify -Dshaft.version="$(mvn -f pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout)" python3 scripts/ci/validate_maven_publication.py \ --create-bundle target/publication-dry-run/central-bundle.zip ``` The combined consumer imports `shaft-bom`, resolves the engine and established optional library modules, enforces dependency convergence, detects duplicate classes outside the BrowserStack SDK's documented shaded JAR, and writes a CycloneDX SBOM. `shaft-heal` is validated by its own compile, test, JavaDocs, and publication-output checks. The `pilot-core` fixture compiles against provider-neutral contracts while banning `shaft-ai`; the separate `mcp` fixture resolves the preserved MCP coordinate and its runtime dependencies without adding it to ordinary engine consumers. For a signed staging rehearsal, import a disposable GPG key into an isolated `GNUPGHOME`, run the reactor without `-Dgpg.skip`, and add `--require-signatures` to both validator commands. The aggregate CycloneDX artifact is attached before the shared `maven-gpg-plugin` `verify` execution so it is signed like every POM, JAR, sources JAR, and JavaDocs JAR. Never use release credentials for a local rehearsal. ## Atomic release order `.github/workflows/mavenCentral_cd.yml` performs these irreversible actions in order: 1. Immediately after checkout, probe the root parent POM coordinate on Maven Central. An existing version or an inconclusive Central response stops the job before JDK, Maven, signing, or build setup. 2. Read the version from the root parent POM and reject reactor version drift. 3. Validate the Pilot contract, run deterministic Pilot and SHAFT Heal module tests with populated Allure results, complete the headless Capture release journey, build all publication artifacts, run the provider-neutral core, combined-module, and MCP consumer checks, and smoke-test the HTTP MCP container. 4. Sign and deploy the complete reactor through the Central Publishing Maven Plugin, waiting for publication success. 5. Verify every POM, JAR, sources JAR, JavaDocs JAR, and signature from Maven Central, then compile canonical, provider-neutral core, combined-module, legacy-relocation, and MCP consumers from isolated repositories. 6. Create the GitHub tag and release. 7. Dispatch the guide update and announce the release on Slack. This ordering prevents a GitHub release, guide update, or Slack announcement from claiming availability when Central publication failed. ## Failure and rollback behavior - **Version already exists:** choose a new reactor version. Maven Central releases are immutable, so the workflow deliberately stops before environment setup or artifact construction. - **Before Central succeeds:** fix the validation, signing, metadata, or Central error and rerun. No GitHub release or announcement has been created. - **After Central succeeds but before GitHub release creation:** Maven Central releases are immutable. Do not overwrite or delete the published version. A maintainer may rerun the workflow to create the missing GitHub release if the tag does not exist, or create it manually using the exact published version. - **After GitHub release creation but before dispatch/Slack:** rerun or manually perform only the missing notification. Do not republish the Maven version. - **Incorrect published content:** publish a new release version with the correction. Document the affected version; Maven Central does not support replacing immutable artifacts. - **Partial local output:** delete generated `target/` directories and repeat the unsigned dry run. Generated bundles and build outputs must never be committed. --- # Maven reactor Canonical HTML: https://shafthq.github.io/docs/maintainers/reactor Markdown: https://shafthq.github.io/md/maintainers/reactor.md # Maven reactor layout SHAFT_ENGINE is built as a Maven reactor. The canonical published engine coordinate is `io.github.shafthq:shaft-engine`; the former `io.github.shafthq:SHAFT_ENGINE` coordinate is a POM-only relocation artifact that points consumers to the canonical JAR. ## Modules - The root `pom.xml` is the `io.github.shafthq:shaft-parent` aggregator and build parent. It owns shared version properties, dependency management, and plugin management. - `shaft-engine/pom.xml` builds the engine JAR. All framework source, resources, tests, examples, and runtime support assets remain under `shaft-engine/src/`; Java packages remain under `com.shaft`. - `shaft-bom/pom.xml` publishes the consumer BOM. Importing it manages the `shaft-engine`, `shaft-pilot-core`, `shaft-capture`, `shaft-capture-proxy`, `shaft-doctor`, `shaft-ai`, `shaft-heal`, `shaft-browserstack`, `shaft-video`, `shaft-visual`, `shaft-sikulix`, and `shaft-mcp` versions without adding dependencies by itself. - `shaft-pilot-core/pom.xml` builds provider-neutral Pilot contracts, security controls, configuration snapshots, and deterministic fallback. It depends on `shaft-engine`; the engine has no reverse dependency. - `shaft-capture/pom.xml` builds managed Chrome/Edge recording, versioned contracts, deterministic privacy classification, Java/TestNG generation, compile/replay validation, schema migration, and atomic JSON persistence. It depends on `shaft-pilot-core` and has no dependency on `shaft-ai`. - `shaft-capture-proxy/pom.xml` isolates the MITM HTTP(S) proxy used to record native mobile API traffic so Netty and Bouncy Castle stay off the classpath of consumers that do not enable that capability. It depends on `shaft-engine` and `shaft-capture`. - `shaft-doctor/pom.xml` builds allowlisted local evidence collection, redacted portable bundles, deterministic failure rules, JSON/Markdown reports, provider-neutral optional advisory integration, and isolated approval-gated repair proposals. It depends on `shaft-pilot-core` and has no dependency on `shaft-ai`. - `shaft-ai/pom.xml` builds optional direct OpenAI, Anthropic, Gemini, GitHub Models, and Ollama adapters. It depends on `shaft-pilot-core` and exposes no provider SDK types. - `shaft-heal/pom.xml` builds optional deterministic web element recovery, bounded local history, structured reports, and provider-neutral optional reranking. It depends on `shaft-engine` and `shaft-pilot-core`. - `shaft-mcp/pom.xml` builds the optional executable MCP server plus SHAFT Capture and Doctor CLIs. It depends on `shaft-engine`, `shaft-capture`, `shaft-doctor`, and `shaft-ai` so the executable can offer explicitly configured direct-provider advisories; the engine has no reverse dependency on MCP. - `shaft-cli/pom.xml` builds the command-line client over the SHAFT MCP capability set for scripts and CI. It publishes independently and is not managed by the consumer BOM. - `shaft-browserstack/pom.xml` builds the optional BrowserStack Java SDK integration. Direct BrowserStack WebDriver/Appium sessions remain in `shaft-engine`. - `shaft-video/pom.xml` builds the optional desktop video recording provider. Add it when local desktop screen recording is needed; Appium-native recording remains in `shaft-engine`. - `shaft-visual/pom.xml` builds the optional OpenCV, Applitools Eyes, and Shutterbug visual-processing provider and its focused visual tests. - `shaft-sikulix/pom.xml` builds the optional SikuliX desktop image automation module. Add it for image matching; Appium Windows desktop automation remains in `shaft-engine`. - `legacy-shaft-engine/pom.xml` publishes the legacy `SHAFT_ENGINE` coordinate as relocation metadata only; it contains no classes. API, Appium/mobile, database, and other retained core capabilities remain dependencies of the engine module. Optional SHAFT Heal, BrowserStack SDK, desktop video/FFmpeg, OpenCV/Eyes/Shutterbug visual-processing, and SikuliX desktop image support are provided by `shaft-heal`, `shaft-browserstack`, `shaft-video`, `shaft-visual`, and `shaft-sikulix` respectively. ## Consumer usage Use the canonical dependency directly: ```xml io.github.shafthq shaft-engine ${shaft.version} ``` Or import the BOM and omit the engine version: ```xml io.github.shafthq shaft-bom ${shaft.version} pom import io.github.shafthq shaft-engine ``` ## Common commands Run commands from the repository root: ```bash python3 scripts/ci/validate_reactor_versions.py mvn clean install -DskipTests -Dgpg.skip mvn -pl shaft-engine -am test -Dtest=TestClassName mvn -pl shaft-browserstack -am test -Dtest=BrowserStackHelperUnitTest mvn -pl shaft-video -am test -Dtest=DesktopVideoRecordingProviderRegistrationTest mvn -pl shaft-visual -am test -Dtest=ImageProcessingActionsUnitTest mvn -pl shaft-sikulix -am test -Dtest=SikuliWindowsDesktopE2ETest -DrunWindowsDesktopE2E=true mvn -pl shaft-heal -am test mvn -pl shaft-pilot-core,shaft-capture,shaft-doctor,shaft-ai -am test mvn -pl shaft-mcp -am test -Dtest=ShaftMcpApplicationTests mvn -pl shaft-mcp -am install -DskipTests -Dgpg.skip python3 scripts/ci/validate_shaft_mcp_transports.py mvn -pl shaft-engine -am test mvn clean install -DskipTests -Dgpg.skip python3 scripts/ci/assemble_javadocs.py ``` Build and test outputs are module-local. Important locations include: - Engine JAR: `shaft-engine/target/shaft-engine- .jar` - Pilot contracts JAR: `shaft-pilot-core/target/shaft-pilot-core- .jar` - Capture contracts JAR: `shaft-capture/target/shaft-capture- .jar` - Capture proxy JAR: `shaft-capture-proxy/target/shaft-capture-proxy- .jar` - Doctor analyzer JAR: `shaft-doctor/target/shaft-doctor- .jar` - Optional direct providers JAR: `shaft-ai/target/shaft-ai- .jar` - Optional SHAFT Heal JAR: `shaft-heal/target/shaft-heal- .jar` - Optional BrowserStack SDK JAR: `shaft-browserstack/target/shaft-browserstack- .jar` - Optional desktop video JAR: `shaft-video/target/shaft-video- .jar` - Optional visual-processing JAR: `shaft-visual/target/shaft-visual- .jar` - Optional SikuliX JAR: `shaft-sikulix/target/shaft-sikulix- .jar` - MCP thin JAR: `shaft-mcp/target/shaft-mcp- .jar` - CLI JAR: `shaft-cli/target/shaft-cli- .jar` - Surefire reports: ` /target/surefire-reports/` - Aggregate JaCoCo report: `target/jacoco/` - Per-module JaCoCo execution data: ` /target/jacoco.exec` - Allure results: `shaft-engine/allure-results/` - Allure report: `shaft-engine/allure-report/` When validating a test run, count `shaft-engine/allure-results/*-result.json` before evaluating statuses. An empty result directory is not a successful test oracle. ## Dependency baseline fixtures The cold-cache fixtures cover direct canonical dependencies, BOM-managed versionless dependencies, and the legacy relocation path. Build the reactor before measuring them: ```bash mvn clean install -DskipTests -Dgpg.skip python3 scripts/ci/measure_consumer_dependencies.py --verify ``` The measurement script seeds the canonical engine JAR, optional integration JARs including `shaft-heal`, the BOM, the legacy relocation POM, and the reactor parent into each isolated Maven repository. ## GitHub Actions reactor policy Workflow commands run from the repository root and select only the modules required by each job: | Workflow | Reactor policy | | --- | --- | | `e2eTests.yml` | Engine and grid jobs use `-pl shaft-engine -am`; BrowserStack jobs use `-pl shaft-browserstack -am`; focused visual and desktop-video jobs use their respective optional modules; Moon runs from the engine module with the JUnit profile. | | `e2eLocalTests.yml`, `lambdatestTests.yml` | Local browser, local Cucumber, Windows desktop Appium, and LambdaTest jobs use `-pl shaft-engine -am`; SikuliX uses `-pl shaft-sikulix -am`; ordinary browser/provider jobs do not resolve `shaft-video` or `shaft-sikulix`. | | `coverage-readiness.yml` | Runs focused engine tests, builds `report-aggregate`, gates `target/jacoco/jacoco.csv`, and uploads only `target/jacoco/jacoco.xml` to Codecov. | | `code-quality-scan.yml`, `codeql-analysis.yml`, `copilot-setup-steps.yml` | Select the code-bearing engine, optional integrations, and MCP module explicitly; BOM and relocation-only modules are excluded from compilation/analysis. | | `shaft-mcp.yml` | Runs manually and daily at 01:00 UTC with local E2E workflows; packages the executable and smokes stdio plus Streamable HTTP. | | `publishJavaDocs.yml` | Builds JavaDocs for all Java-bearing modules, assembles a navigable site at `target/javadocs`, and publishes it only after a successful Central release. | | `mavenCentral_cd.yml` | Intentionally deploys the complete reactor because every published module, BOM, and relocation POM belongs to a release. | | `reactor-version-check.yml` | Uses the reactor-version validation script and does not invoke Maven. | | `dependency_review.yml`, `link-check.yml`, `refresh-agent-instructions.yml` | Do not build Java modules and therefore require no reactor selection. | | `sync-sample-projects-version.yml`, `update-selenium-grid-versions.yml` | Operate on module-relative assets under `shaft-engine/src/main/resources`. | The shared post-test action defaults to `shaft-engine` but accepts `module-directory` for optional-module jobs. It reads and uploads ` /allure-results`, ` /allure-report`, ` /target/surefire-reports`, and ` /target/jacoco`. Coverage upload is centralized in `coverage-readiness.yml` so parallel E2E jobs do not upload duplicate source reports. It counts raw `*-result.json` files before interpreting Allure summary status and fails an empty test run. ## Aggregate coverage continuity `report-aggregate` is a build-only module that depends on every Java-bearing module, including the Pilot modules, `shaft-sikulix`, and `shaft-mcp`, and writes JaCoCo XML, CSV, and HTML to root `target/jacoco`. It is skipped by Maven deploy and is not a published SHAFT artifact. Use pre-reactor commit `570a83674b70477074621ea24d7cfa05517260c6` as the coverage-continuity reference. Run the same test selector on that commit and on the current branch, then compare the resulting CSV summaries with `scripts/ci/jacoco_coverage_gate.py`. The current reactor command is: ```bash mvn -pl shaft-engine -am test -Dtest=PathParamTest,SwaggerContractTest -Dgpg.skip mvn -pl report-aggregate -am verify -DskipTests -Dgpg.skip scripts/ci/jacoco_coverage_gate.py target/jacoco/jacoco.csv --metric line --minimum 0 ``` Codecov receives only `target/jacoco/jacoco.xml`; automatic coverage-file discovery is disabled so module-local reports cannot duplicate source entries. Dependabot scans every reactor POM and groups the same Maven dependency across directories into one aligned update. `scripts/ci/validate_quality_configuration.py` protects these settings, while the `shaft-engine` Enforcer execution rejects optional SHAFT Heal, BrowserStack, desktop-video, or visual-processing dependencies that leak back into the core engine. Consumer-facing method boundaries are documented in the [upgrade guide](/docs/start/upgrade), with focused guides for SHAFT Heal, BrowserStack, desktop video, and visual processing. Deterministic element recovery, Healenium coexistence, privacy, and optional visual/AI evidence are documented in [SHAFT Heal](/docs/agentic/heal). MCP client configuration, transport behavior, and credential boundaries are documented in [SHAFT MCP](/docs/agentic/mcp). Pilot contracts, direct-provider configuration, approval, and redaction are documented in [optional AI providers](/docs/agentic/providers). Managed-browser recording, its CLI/MCP controls, the versioned format, and the deterministic privacy boundary are documented in [SHAFT Capture](/docs/agentic/capture). Portable evidence, deterministic diagnosis, CLI/MCP usage, and sharing guidance are documented in [SHAFT Doctor](/docs/agentic/doctor). --- # Repository history rewrite Canonical HTML: https://shafthq.github.io/docs/maintainers/history-rewrite Markdown: https://shafthq.github.io/md/maintainers/history-rewrite.md # Repository history rewrite ## Goal Optimize clone/fetch performance by flattening repository history and reducing historical object payload while preserving maintainer attribution. ## Chosen Flattening Mode - **Mode**: single-commit snapshot (maximum pack-size reduction) - **Why**: this repository contains large historical binary artifacts; flattening to a single root commit yields the largest transfer reduction. - **Maintainer preservation strategy**: preserve full pre-flatten attribution in the unlisted [maintainer history manifest](/docs/archive/engine/contributors-history). ## Pre-Cutover Inputs 1. Ensure full history is present (`git fetch --unshallow origin` when required). 2. Generate baseline artifacts and manifest: ```bash scripts/maintenance/flatten-history.sh ``` 3. Review baseline files in `/tmp/shaft-history-rewrite/`: - `pre-maintainers-shortlog.txt` - `pre-identities-raw.txt` - `pre-largest-blobs.txt` - `pre-count-objects.txt` - `pre-dotgit-size.txt` ## Retention / Removal Policy - **Retain**: current `main` tree content needed for build/test/runtime/docs. - **Remove from history**: all prior commit graph and unreachable historical blobs. - **Result**: new history starts from one fresh root commit, with maintainer attribution preserved in manifest. ## Execute Local Rewrite ```bash TARGET_BRANCH=main \ FLATTEN_BRANCH=history/flattened-main \ scripts/maintenance/flatten-history.sh --execute ``` > The script auto-configures a local fallback git identity when `user.name`/`user.email` are not set. > You can override rewrite commit identity explicitly with `GIT_AUTHOR_NAME`, `GIT_AUTHOR_EMAIL`, `GIT_COMMITTER_NAME`, and `GIT_COMMITTER_EMAIL`. ## Validation Checklist - Maintainer manifest is published in the documentation archive. - Object/pack improvement: ```bash git count-objects -vH du -sh .git ``` - Clone benchmark (same runner, no local hardlink optimization): ```bash /usr/bin/time -f '%E real' git clone --no-local /tmp/clone-baseline /usr/bin/time -f '%E real' git clone --no-local /tmp/clone-rewritten ``` - Ref/tag integrity review completed (release tags preserved only if intentionally recreated). - Example measured results are retained in the [history rewrite validation record](/docs/archive/engine/history-rewrite-validation). ## Maintainer Cutover Procedure (Force Push) 1. Announce maintenance window and temporary push freeze. 2. Create remote backup mirror before cutover: ```bash git clone --mirror /tmp/repo-backup-mirror.git ``` Replace ` ` with the full repository URL, for example: `https://github.com/ShaftHQ/SHAFT_ENGINE.git`. 3. Push rewritten branch to a staging remote for verification. 4. After approval, force-update protected default branch (maintainer operation only). 5. Broadcast migration instructions to contributors: ```bash git fetch --all --prune git switch main git reset --hard origin/main git gc --prune=now ``` ## Rollback Plan - Keep backup mirror and pre-flatten backup ref until stability checks complete. - If rollback is required, force-push backup mirror/ref back to default branch. - Re-run post-rollback verification (tags, CI pipelines, release automation). --- # SHAFT Pilot release Canonical HTML: https://shafthq.github.io/docs/maintainers/pilot-release Markdown: https://shafthq.github.io/md/maintainers/pilot-release.md # SHAFT Pilot release This runbook prepares and verifies the first monorepo SHAFT Pilot release. Publication and repository archival are ordered so no external channel points to an unverified artifact. ## Release-candidate matrix | Area | Required release evidence | | --- | --- | | Runtime | JDK 25 and Maven 3.9 or newer. | | Modules | `shaft-pilot-core`, `shaft-capture`, `shaft-doctor`, `shaft-ai`, and `shaft-mcp` tests plus full reactor package. | | No-AI path | `pilot.ai.enabled=false`, provider `none`, Capture generation/replay, Doctor diagnosis, and MCP tools pass without credentials. | | Browsers | Headless Chrome and Edge record the representative local fixture; generated Chrome replay compiles, passes, and populates Allure. | | Doctor | Product, test, locator, data, timing, environment, infrastructure, unknown, empty, and retry evidence fixtures pass. | | Providers | OpenAI, Anthropic, Gemini, GitHub Models, and Ollama use local mock endpoints for success, timeout, rate-limit, authentication, malformed-response, schema, redaction, and fallback conformance. | | MCP transports | Packaged stdio and Streamable HTTP `/mcp` initialization and tool discovery pass. | | Containers | The release Docker image starts in HTTP mode and completes MCP initialization. | | External agents | ChatGPT, Codex, Claude, Gemini, and GitHub Copilot setup is documented with current client limitations and credential ownership. | | Publication | Maven Central artifacts, sources, JavaDocs, signatures, BOM, thin MCP JAR, aggregate SBOM, GHCR image, MCP registry metadata, and GitHub release use one version. | | Migration | The preserved `io.github.shafthq:shaft-mcp` coordinate and `shaft-mcp` server ID resolve. `ghcr.io/shafthq/shaft-mcp:10.2.20260612` remains a tested compatibility image, while `ghcr.io/shafthq/shaft-engine-mcp` is published by the monorepo for current and future releases. | Live paid-provider smoke tests are optional. Run them only with explicit approval and credentials in the provider-native environment variable. Their absence cannot fail the normal release gate. ## Deterministic gate Run from a clean checkout: ```bash python3 scripts/ci/validate_reactor_versions.py python3 scripts/ci/validate_modular_documentation.py python3 scripts/ci/validate_shaft_pilot_release.py mvn -pl shaft-engine,shaft-pilot-core,shaft-capture,shaft-doctor,shaft-ai,shaft-mcp \ -am install -DskipTests -Dgpg.skip mvn -pl shaft-pilot-core,shaft-capture,shaft-doctor,shaft-ai,shaft-mcp test -Dgpg.skip python3 scripts/ci/validate_shaft_pilot_release.py --check-test-results mvn -pl shaft-capture test \ -DincludeCaptureBrowserE2E \ -Dtest=ManagedCaptureRecorderBrowserTest,CaptureGeneratedReplayBrowserTest \ -Dgpg.skip mvn clean install -DskipTests -Dgpg.skip python3 scripts/ci/validate_maven_publication.py --check-build-outputs python3 scripts/ci/validate_shaft_pilot_release.py --check-build-outputs python3 scripts/ci/validate_shaft_mcp_transports.py ``` On PowerShell, single-quote every Maven `-D` argument. Verify that each Pilot module has populated `allure-results/*-result.json`; Surefire summaries alone are not sufficient. The Maven Central workflow repeats this evidence before deployment, then validates clean consumer fixtures for provider-neutral core, combined modules, and the executable MCP coordinate. ## Distribution verification After Maven Central succeeds: 1. Run `scripts/ci/verify_maven_central_release.py ` from isolated local repositories. 2. Pull `ghcr.io/shafthq/shaft-engine-mcp: ` and initialize `/mcp` in a clean container. For the first monorepo release, also verify the `ghcr.io/shafthq/shaft-mcp:10.2.20260612` compatibility image. 3. Confirm the MCP registry entry `io.github.ShaftHQ/shaft-mcp` references the same package version. 4. Confirm the GitHub release, JavaDocs, aggregate CycloneDX SBOM, sources, signatures, BOM, and legacy relocation coordinate use the same version. 5. Exercise one credential-free stdio client configuration and the packaged Streamable HTTP transport. Exercise a hosted HTTPS endpoint only when its external deployment account is configured; otherwise record it as a known limitation instead of presenting the endpoint as live. Record exact versions, immutable URLs, checksums, workflow runs, and known limitations in the release or pull request before migration finalization. For release `10.2.20260612`, the Render and Fly repository secrets were not configured, the Fly hostname did not resolve, Render returned `404` at `/mcp`, and the historical Smithery listing was unavailable. These optional hosted services are not release installation paths. GHCR, MCP Registry, the executable JAR, stdio, and packaged/container Streamable HTTP were verified. ## Standalone repository migration Do not archive `ShaftHQ/shaft-mcp` until every distribution check above passes. Then: 1. Replace its README introduction with a prominent notice that development, releases, documentation, issues, and source moved to `ShaftHQ/SHAFT_ENGINE`. 2. Link the verified monorepo release, [SHAFT Pilot](/docs/agentic/pilot), and [SHAFT MCP](/docs/agentic/mcp). 3. Update the repository description and homepage to the canonical repository. 4. Disable write-oriented workflows and direct users to the canonical issue tracker while preserving existing history, issues, tags, and releases. 5. Verify the notice on the default branch. 6. Archive the repository with administrator approval. 7. Recheck the README, release links, Maven coordinate, image, and registry entry from a logged-out browser. Archival is a post-release operation. A failed or incomplete distribution check leaves the old repository unarchived. ## Rollback - Before Maven Central succeeds, fix the gate and rerun with the same available version. - After Maven Central succeeds, never overwrite or delete the immutable release. Publish a newer version for corrections. - If GHCR, registry, hosted MCP, JavaDocs, or release creation fails, repair only that downstream channel and keep the verified Maven version. - If migration verification fails, leave `ShaftHQ/shaft-mcp` writable and restore its normal automation until the canonical path is confirmed. - If a secret canary appears in a recording, report, generated test, image, log, example, or packaged artifact, stop publication, remove the output, rotate any real credential, and repeat the full gate. --- # User guide usability practices Canonical HTML: https://shafthq.github.io/docs/maintainers/usability-practices Markdown: https://shafthq.github.io/md/maintainers/usability-practices.md # User guide usability practices This note is the research record for the usability pass. Each practice names a source and the change on this site. ## Human practices 1. **Task-first path.** Say what the page is, who it is for, and the next step before the detail. Source: [Diátaxis](https://diataxis.fr/start-here/). Site: every guide page opens with a page-orientation block (title, description, next link). The landing page leads with the user guide. 2. **Lead with the answer.** Put the conclusion in the first lines. Source: [Nielsen Norman Group, Inverted Pyramid](https://www.nngroup.com/articles/inverted-pyramid/). Site: landing hero states what SHAFT is and points at the guide; orientation blocks repeat the page description first. 3. **Scannable headings.** Short sections with real headings, not walls of prose. Source: [Nielsen Norman Group, How Users Read on the Web](https://www.nngroup.com/articles/how-users-read-on-the-web/). Site: existing heading outline kept; dependency and reporting facts are separate terms in definition lists. 4. **One term per concept.** Do not rename the product or the engine mid-page. Source: [Microsoft Writing Style Guide, Use simple words and sentences](https://learn.microsoft.com/en-us/style-guide/word-choice/use-simple-words-concise-sentences). Site: pages say SHAFT for the framework and `shaft-engine` for the Maven artifact. The orientation line uses that pair. 5. **Examples a reader can copy.** Source: [Google developer documentation style guide, Code samples](https://developers.google.com/style/code-samples). Site: the docs quality check still requires a fenced sample on every public page. Boundary pages keep their Java and XML samples next to the lists. 6. **Reflow on a phone.** Two-dimensional scrolling is for real data tables, not prose. Source: [WCAG 2.2, Understanding 1.4.10 Reflow](https://www.w3.org/WAI/WCAG22/Understanding/reflow.html). Site: issue #1085 lists are definition lists. Property matrices stay in a named scroll region because they are columnar data. 7. **One primary action.** Source: [Nielsen Norman Group, Visual Hierarchy](https://www.nngroup.com/articles/visual-hierarchy-ux-definition/). Site: the landing hero states one path into the guide, “Read the user guide”, and keeps a single filled button for creating a project. 8. **Link text says where it goes.** Source: [WCAG 2.2, Understanding 2.4.4 Link Purpose](https://www.w3.org/WAI/WCAG22/Understanding/link-purpose-in-context.html). Site: the existing content check rejects “click here”. Orientation links use the next page title. 9. **Chunk related facts.** Source: [Nielsen Norman Group, Chunking](https://www.nngroup.com/articles/chunking/). Site: each former table row is one term and one definition, not a stretched row. 10. **Stay inside one design language.** Source: [Nielsen Norman Group, Maintain Consistency and Adhere to Standards](https://www.nngroup.com/articles/consistency-and-standards/). Site: buttons, type, and color stay on the existing landing and Infima tokens. No new palette. ## Agent practices 1. **A small `llms.txt` index.** Source: [llms.txt](https://llmstxt.org/). Site: `scripts/build-llms-txt.mjs` writes `static/llms.txt` during the production build. 2. **A Markdown form of each page.** Source: [llms.txt, Markdown versions](https://llmstxt.org/). Site: the same script writes `static/md/ .md` and the index links there. 3. **Keep the index to one fetch.** Source: [llms.txt](https://llmstxt.org/). Site: descriptions in the index are capped at 140 characters. Full text stays in `llms-full.txt` and the per-page Markdown files. 4. **Same-origin links.** Source: [llms.txt](https://llmstxt.org/). Site: index URLs use the `siteUrl` and `baseUrl` from `docusaurus.config.js`. 5. **An in-page pointer.** Source: [llms.txt](https://llmstxt.org/). Site: the orientation block links `/llms.txt` and the Markdown URL for that page. 6. **Sections that do not depend on “above”.** Source: [Google developer documentation style guide, Cross-references](https://developers.google.com/style/cross-references). Site: the IntelliJ upgrade step names the prerequisites sequence instead of “see above”. The pointer tells agents to cite heading anchors. 7. **Stable heading anchors.** Source: [Docusaurus, Heading IDs](https://docusaurus.io/docs/markdown-features/headings). Site: the restructured sections keep the previous heading text, so the existing ids remain. 8. **Front matter a tool can read.** Source: [Docusaurus Markdown front matter](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-docs#markdown-front-matter). Site: the index uses `title`, `description`, and slug from front matter. The orientation block prints the same description. 9. **Text, not pictures, carries the facts.** Source: [WCAG 2.2, Understanding 1.1.1 Non-text Content](https://www.w3.org/WAI/WCAG22/Understanding/non-text-content.html). Site: the Markdown export is the docs-loader text, and the landing evidence images keep text alternatives. 10. **State the canonical HTML URL inside the Markdown file.** Source: [llms.txt](https://llmstxt.org/). Site: each generated Markdown file starts with `Canonical HTML:` and `Guide index:`. ## Scroll-region exception These pages were restructured so the named lists do not scroll horizontally at 390px, including the reporting quick reference at 1440px: - `/docs/start/upgrade/reference` (methods that require `shaft-visual`, functionality that remains in `shaft-engine`, BrowserStack SDK rows) - `/docs/start/upgrade/run` (missing-provider troubleshooting) - `/docs/reference/reporting` (All Reporting Properties) Other tables, including the properties catalog, stay in `.table-scroll`. They are columnar data. The wrapper is a named, keyboard-reachable region from the earlier wide-table work. --- # Visual provider boundary Canonical HTML: https://shafthq.github.io/docs/maintainers/visual-provider Markdown: https://shafthq.github.io/md/maintainers/visual-provider.md # Visual-processing provider boundary Issue [#2816](https://github.com/ShaftHQ/SHAFT_ENGINE/issues/2816) introduced the implemented boundary that moved optional computer-vision behavior out of `shaft-engine` without moving screenshot capture or Healenium integration. ## Operation inventory | `ImageProcessingActions` operation | Classification | Boundary decision | | --- | --- | --- | | `compareImageFolders` | JDK image/file comparison | Remains in `shaft-engine`; uses `ImageIO` and image data buffers. | | `highlightElementInScreenshot` | Core screenshot decoration | Remains in `shaft-engine`; implemented with JDK `BufferedImage`/`Graphics2D`, so ordinary screenshots do not initialize OpenCV. | | `findImageWithinCurrentPage` | Optional computer vision | Delegates to the discovered `VisualProcessingProvider`. | | `formatElementLocatorToImagePath` | Core baseline naming | Remains in `shaft-engine`. | | `getReferenceImage` | Core baseline file access | Remains in `shaft-engine`. | | `getShutterbugDifferencesImage` | Core baseline file access | Remains in `shaft-engine` for backward compatibility. | | `compareAgainstBaseline` / `EXACT_OPENCV` | Optional computer vision | Delegates to the discovered provider. | | `compareAgainstBaseline` / Shutterbug and Applitools engines | Third-party visual integrations | Delegates to the discovered provider. | | `loadOpenCV` | Backward-compatible OpenCV entry point | Delegates provider loading and no longer embeds OpenCV types in the core class. | Public fluent APIs that reach the provider include `matchesReferenceImage(...)`, `doesNotMatchReferenceImage(...)`, `TouchActions.tap(String)`, `TouchActions.type(String, ...)`, `TouchActions.waitUntilElementIsVisible(String)`, and the image-path `swipeElementIntoView(...)` overloads. The focused [visual module guide](/docs/integrations/visual) is the consumer-facing reference. Image-path touch actions must pass viewport screenshot bytes into `findImageWithinCurrentPage`, because Selenium/Appium coordinate actions target the viewport. The OpenCV provider handles DPI/display-scale drift by matching scaled reference templates and returning the matched center point. `ScreenshotManager`, Selenium screenshot capture, and Healenium integration remain outside this provider boundary. ## Discovery contract Providers implement `VisualProcessingProvider` and are discovered with `ServiceLoader`. Discovery sorts implementations by class name and rejects multiple providers with a deterministic error instead of choosing based on classpath order. The OpenCV implementation and its service descriptor are packaged in `io.github.shafthq:shaft-visual`, so adding that artifact enables OpenCV, Applitools Eyes, and Shutterbug behavior without a new initialization API. When no provider is installed, provider-dependent operations report the Maven coordinate `io.github.shafthq:shaft-visual`. Core screenshot capture, JDK highlighting, animated GIF generation, baseline file handling, Healenium, and non-OpenCV assertions remain in `shaft-engine` and do not perform provider discovery.