Agentic test automation from the command line (CLI)

Delegate the whole testing workflow to Step AI Agent from the command line: write the specification, drive the agent with the Step CLI, and gate your CI/CD pipeline on a validated Automation Package.

Get Step SaaS for free to follow this tutorial
Agentic Testing CLI: a terminal window with a command prompt beside a Step Plan built from stacked Keyword cubes.

This tutorial shows how to create automated test cases from plain-text specifications with the Step AI Agent, from within your IDE or your coding assistant. The agent runs from the CLI, where your code is: it explores the application, designs and implements the automation, runs it, and repairs what fails before handing back a Step Automation Package. What comes back is a plain Automation Package: its descriptor and fragments in YAML, and the keyword implementations in code (TypeScript here), which you review, commit, and run in CI/CD like any other code.

Note: Step AI Agent can also be run from the Step Studio UI: see Agentic test automation with Step Studio. And where you would rather drive your own AI assistant than delegate a whole workflow, see AI-Assisted Testing with the Step MCP Server.

Scenario

A guest checkout on our own instance of OpenCart, from the test specification you would write to a job that gates your pipeline.

Prerequisites

  • The Step AI Agent jar and the Step CLI 31 or newer: see Getting started
  • Node.js 24 or newer
  • An Anthropic API key exported as environment variable:
  
  export ANTHROPIC_API_KEY=sk-ant-...
  
Note: This tutorial runs against a demo storefront. Read Data and privacy before running the agent against an application that holds real data.

1. Create a test

Write the specification

It starts from one line: a visitor who is not registered can buy a product and receive an order confirmation.

The test summarizes in one line. We make steps explicit. For the CLI, the specification goes in instructions.json, the input file the Step AI Agent expects:

  
  {
  "testCases": [
    {
      "name": "opencart_checkout",
      "mode": "create",
      "spec": "OpenCart storefront - Guest checkout flow for a MacBook reached via the Laptops and Notebooks category. Steps: 1. Navigate to the Laptops and Notebooks category and find the MacBook product. 2. Add the MacBook to the cart. 3. Open the cart and proceed to checkout. 4. Select Guest Checkout and continue. 5. Fill in the billing/shipping form with customer details. 6. Confirm shipping method. 7. Accept terms and confirm payment method. 8. Place the order and verify the confirmation page. Test data: Storefront URL https://opencart-prf.stepcloud.ch/, customer Gustav Muster, customer@opencart.demo, Bahnhofstrasse 1, 8001 Zurich, Zürich, Switzerland."
    }
  ]
}
  
🧪 Where testers can write the specification instead. The same text, pasted into Step Studio rather than a JSON file, produces the same package. See Agentic test automation with Step Studio.

Run the agent

Lay the repository out like this, and there is nothing to move between runs:

  
  my-repo/
  instructions.json          the specification you edit and commit
  opencart_tutorial_ap/      apDirectory — generated, deployable on its own
  

Then, from its root:

  
  cd my-repo

INSTRUCTIONS=$(tr -d '\n' < instructions.json)

step ap execute --local \
  --package=/path/to/step-ai-agent \
  --includePlans="AI Agent - Automate" \
  --localAgentWorkDir=/tmp/step-ai-tutorial \
  -ep "apDirectory=$PWD/opencart_tutorial_ap|instructions=$INSTRUCTIONS|agentDebug=true"
  
Note:
  • --package — the agent jar file you downloaded, not the directory holding it.
  • --includePlans — the agent’s own plan. Quote it: the name contains spaces.
  • --localAgentWorkDir — where the local agents are installed and run. Optional: it defaults to a folder in the system temporary directory, kept between runs so the agents are not reinstalled each time.
  • -ep — execution parameters, as name=value pairs separated by |:
    • apDirectory — where the generated package is written: an existing automation package, an empty directory, or a path that does not exist yet.
    • instructions — the specification above, as one line.
    • agentDebug=true — every phase also reports its technical fields, and the working tree is kept after the run. In Step Studio it is a global parameter of that name instead.

The command blocks until the run ends and prints the report tree. Between the two it prints nothing at all — add --debug to follow the run as it happens, which logs at debug level in the CLI and in the local agent, where the phases run.

Every flag is documented in the Step CLI reference.

The specification stays outside the package because it is the input, not part of what you ship: the package is what CI runs and what you deploy to a Step instance. The request is not lost by separating them: the agent files a copy of every one under .ai/runs/<executionId>/ inside the package, so a package records what each run was asked for. That is the request only — logs/ holds the phase logs of the most recent run and is overwritten by the next, so keep a run’s logs elsewhere if you need to compare two.

The agent explores the storefront before writing any code, implements the test case, runs it, and repairs what fails before reporting success. A create run of this scenario takes several minutes.

It does not make assumptions or stop to ask for clarification. Before the first model call it rejects a duplicate test case name, a name Step would not accept as a plan name, or a request that reads as both a test and an RPA workflow. Later, while designing the keywords, it checks the requested steps against the running storefront, and a step the application cannot satisfy stops the run before any code is generated. Each case fails the command with a message naming what to change, so a broken specification costs you seconds rather than a full run.

The run closes with a report, written to opencart_tutorial_ap/logs/report.md. This is an extract:

  
  ## ✅ PASSED

✅ Run PASSED — 8 keyword(s) implemented, $1.7490, 6.42 min

## What this run produced

| Test case         | useCase | mode   | Verified by                          |
|-------------------|---------|--------|--------------------------------------|
| opencart_checkout | fct     | create | local execution (1/1 plans passing)  |

**Plan fidelity:** 1 covered · 0 partial · 0 missing
  

The verdict PASSED or FAILED is the principal information, and it comes from running the automation: Step executes the generated keywords against the storefront, and a keyword whose check is not met fails its plan, which fails the run. Additional metrics are reported for information only, like plan fidelity, where a second model compares the plan against the specification you wrote and marks each test case covered, partial or missing. It never fails the run: it reads the plan rather than running it, and a plan the storefront cannot satisfy was already caught earlier, while the keywords were designed.

Read what came back

Everything the run produced is written in opencart_tutorial_ap, the directory set by the apDirectory execution parameter:

  
  opencart_tutorial_ap/
  automation-package.yaml          plan, stamped with the run that produced it
  fragments/
    nodejs-keywords.yaml           keyword declarations: role, inputs, outputs
  nodejs-keywords/src/keywords/    their TypeScript implementations
  keywords-specs/
    keywords-spec.md               the design, written before the code
  specs/
    plan.yaml                      the plan as designed, with the SUT URL
    plan-fidelity.json             request vs. plan: covered / partial / missing
  logs/                            one log per phase, with its cost and its reasoning
    design-browser-trace.zip       the agent exploring the application
    local-execution-traces/        one Playwright trace per keyword, on a remote run
  .ai/runs/<executionId>/
    instructions.json              the request, exactly as submitted
  

The file automation-package.yaml contains the plan of the automation package.

The automation package open in VS Code. The explorer shows the opencart_tutorial_ap directory with its .ai, fragments, keywords-specs, logs, nodejs-keywords and specs folders, an .apignore, a .gitlab-ci.yml and automation-package.yaml. The editor shows the plan in automation-package.yaml: name opencart_checkout, metadata marking it origin ai with the executionId that produced it, agents auto_detect, and the first callKeyword of the test case.

The plan reads as the test case, one keyword per test step, and is stamped origin: ai with the execution that produced it, so AI-generated automation stays identifiable later. Eight keywords implement it, each performing its action and asserting the outcome it owns. openCartAndProceedToCheckout is typical:

  
  export async function openCartAndProceedToCheckout(input, output, session, properties) {
    const productName = str(input, 'productName', DEFAULTS.productName);
    const { context, page } = getPlaywright(session);

    await traced('openCartAndProceedToCheckout', context, output, async () => {
        await page.locator('#top-links').getByRole('link', { name: 'Shopping Cart' }).click();
        await page.waitForLoadState('networkidle');

        if (productName) {
            const cartLine = page
                .locator('#content form td.text-left')
                .getByRole('link', { name: productName, exact: true });
            try {
                await cartLine.waitFor({ state: 'visible' });
            } catch {
                throw new Error(`Product "${productName}" is not in the shopping cart (${page.url()})`);
            }
        } else {
            await page.locator('#content form').waitFor({ state: 'visible' });
        }

        await page.locator('#content').getByRole('link', { name: 'Checkout', exact: true }).click();
        await page.waitForLoadState('networkidle');
        await page.getByLabel('Guest Checkout').waitFor({ state: 'visible' });
    });
}
  

What the agent worked out on its own

Most of that time went on questions nobody wrote down. The agent drove the storefront before writing any code, and a detail the specification left open is its to resolve: a selector that is wrong or ambiguous, a label that reads differently on screen, a screen that needs a wait. It keeps exploring until it has an answer that holds, and records the choice it made.

Examples from this run:

  • The specification’s words are not the application’s. It says “Laptops and Notebooks”; the store’s menu says “Laptops & Notebooks”. The automation uses the menu label, and the difference is recorded rather than silently absorbed.
  • The application behaves in a way nobody would think to specify. The region dropdown is re-loaded over AJAX when the country changes, so the keyword waits for the new options instead of selecting into a stale list.

That is what makes the selectors look fussy, and why keywords-specs/keywords-spec.md — the design the agent wrote before the code — is worth reading alongside the code itself.

Note: The package is yours to commit. It is reviewed, diffed and versioned like the rest of your code, and from here it runs without the agent.

Regenerate or extend a test case (optional)

Because the package is code, the agent can build on it rather than start again. Run it a second time with the same test case name and "mode": "extends", and it reads what the package already contains, adds what is missing, and reports every keyword and plan it touched:

  
  {
  "testCases": [
    {
      "name": "opencart_checkout",
      "mode": "extends",
      "spec": "… the same journey, plus: in the cart, change the quantity to two and verify that Sub-Total, Eco Tax, VAT and Total are recalculated."
    }
  ]
}
  

The name must match a test case the package already holds, or the run stops in its first phase, before any model call. extends rewrites the package in place, so run it on a copy if the current state is not committed. See Configuration for the three modes.


2. Validate a test

The finished package is a standard Step Automation Package and runs independently of the agent, with no model cost, which is what makes it usable as regression coverage. Run it once from where you are:

  
  cd /path/to/opencart_tutorial_ap
step ap execute --local --package=.
  

The same deterministic automation runs each time, and a failing plan makes the command fail, so the result already works as a build verdict.

To validate it on your Step instance too, drop --local and point the command at the instance, where STEP_URL is its URL, STEP_API_KEY an API key, and STEP_PROJECT_NAME the project to run in:

  
  cd /path/to/opencart_tutorial_ap
step ap execute --stepUrl=$STEP_URL --token=$STEP_API_KEY --projectName=$STEP_PROJECT_NAME --package=.
  

3. Integrate in CI/CD

Gate the pipeline

From here the package runs unattended, on a Step instance rather than your machine: a vanilla and controlled environment, results the whole team can read, and the JUnit export a pipeline needs. That is the instance command above with the report flags added, and there is nothing to run by hand.

Since Step 31 the CLI ships as a Docker image, so the runner installs nothing. GitLab reads .gitlab-ci.yml from the repository root, and this is the whole file:

  
  stages:
  - test

step-ap-execute:
  stage: test
  image: docker.exense.ch/step/cli:3.31.0
  script:
    - >
      step ap execute
      --stepUrl=$STEP_URL --token=$STEP_API_KEY --projectName=$STEP_PROJECT_NAME
      --package=opencart_tutorial_ap --reportType=junit --reportDir=reports
  artifacts:
    when: always
    paths: [reports/]
    reports:
      junit: reports/**/*.xml
  

STEP_URL, STEP_API_KEY and STEP_PROJECT_NAME are CI/CD variables, the key masked. Tag the image with the version of your Step server.

Note: Step Enterprise uses docker.exense.ch/step-enterprise/cli:<version> instead. That image is private, so the runner also needs a DOCKER_AUTH_CONFIG CI/CD variable holding registry credentials, as {"auths":{"docker.exense.ch":{"auth":"<base64 of user:password>"}}}. It belongs in the variable, not in .gitlab-ci.yml: the runner pulls the image before the job’s script runs, so a docker login there would come too late.

The job starts at the repository root, so --package is the package directory relative to it — opencart_tutorial_ap in the layout above. Where the package is the repository root, use . instead.

Two things make this a gate rather than a log. A failing plan fails the command, so the job fails with it and the merge request is blocked. And reports: junit: publishes the results into the merge request, where a reviewer reads which test case failed instead of opening the job output.

Note: The JUnit file lands in a timestamped subdirectory of --reportDir, so a collector matching reports/*.xml matches nothing: the job then passes while reporting nothing. Match reports/**/*.xml.
A passed GitLab job named step-ap-execute, on a shared runner. The log tail shows the artifact upload: reports/ found 3 matching artifact files and directories, uploaded as archive, then reports slash star star slash star dot xml found 1 matching artifact file, uploaded as junit, and the job succeeded.

What each run leaves behind

GitLab shows the pass or fail and the JUnit report. The details are in Step, where each run is kept as a full execution with a trace attached to every step:

The opencart_checkout execution on a Step instance, under Reporting then Executions. A summary chart counts eight keyword calls, all eight passed and none failed or skipped. The execution tree lists the test case step by step, each node showing the data it worked with, such as the category URL it navigated to and the cart total it read, and carrying its Playwright trace as an attachment named after the keyword that produced it.

Eight keyword calls, all passed. That is where a failure gets diagnosed: the failing step, the data it was working with, and the Playwright trace of the browser at that moment.

Hand the package over

The executions are there, but the package is not. step ap execute runs it in an ephemeral context that is destroyed afterwards, which is exactly what a merge-request gate wants and exactly what makes the package invisible between runs. To install it, deploy it:

  
  step ap deploy --stepUrl=$STEP_URL --token=$STEP_API_KEY --projectName=$STEP_PROJECT_NAME --package=.
  

That makes its plans and keywords available on the instance and activates any schedule the package carries. Run it once when the package is ready to be used by people other than the pipeline, or add it to a job on your default branch so the instance always holds the current version.

🧪 Where testers take over. Once deployed, the plan is theirs to work with: they find it under Plans, run it, schedule it against the live application, and read each execution with its traces, without touching the code or the pipeline. See Agentic test automation with Step Studio.

Where it goes from here

The package gates your pipeline on every change, without the agent and at no model cost. Deployed to the instance, it is also readable automation: the plan reads as the test case in plain language, and every execution carries a trace for each step, which is what lets whoever owns the specification confirm it still matches, and schedule it as recurring coverage once the application is live.

👉 Agentic test automation with Step Studio tells the same story from that side.

Illustration for Using Step with Grafana
Using Step with Grafana

This article demonstrates how to connect Grafana to data generated by Step.

Illustration for Setting up system monitoring with a Step agent
Setting up system monitoring with a Step agent

This article demonstrates how to set up distributed system monitoring using Keyword executions, and analyze the results as measurements.

Illustration for NET tutorials: Microsoft Office automation with Step
NET tutorials: Microsoft Office automation with Step

This tutorial demonstrates how to automate interaction with Microsoft Office applications using the Office Interop Assembly.

Illustration for JUnit Plan Runner
JUnit Plan Runner

This article provides documentation for how to integrate JUnit tests into Step.

Illustration for How to monitor services availability and performance
How to monitor services availability and performance

This tutorial demonstrates how Step can be used to monitor services, availability and performance metrics.

Illustration for .NET tutorials: AutoIt with Step
.NET tutorials: AutoIt with Step

This tutorial demonstrates how to utilize the AutoIt C# binding to automate interactions with Windows applications.

Illustration for Android Testing using Step and Appium
Android Testing using Step and Appium

This article demonstrates the automation of mobile applications on Android using the Appium framework.

Illustration for Browser-based automation with Step and Selenium
Browser-based automation with Step and Selenium

This article defines three Keywords which will be used in browser-based automation scenarios, using Step and Selenium, as general drivers.

Illustration for Playwright with TypeScript
Keyword-driven Automation with Playwright and TypeScript

Write native Step Keywords with Playwright and TypeScript, unit-test them locally, then build and run them on a Step cluster — reusing one Keyword library for both a functional test and a load test.

Illustration for Load Testing with Cypress
Load Testing with Cypress using the Step UI - advanced

This tutorial shows you how to efficiently set up a browser-based load test using existing Cypress tests in the Step automation platform.

Illustration for Adding and Configuring New Agents
Adding and Configuring New Agents

In this short tutorial, we show how to quickly implement a simple browser-based load test based on Cypress scripts in Step.

Illustration for Load Testing with Playwright
Load Testing with Playwright using the Step UI

This tutorial shows you how to set up a browser-based load test using existing Playwright tests in the Step UI.

Illustration for Basic Keyword Development
Basic Keyword Development

This article explains Keywords in Step and demonstrates how to create simple ones.

Illustration for Designing functional tests
Designing functional tests

This tutorial demonstrates the design, execution, and analysis of functional tests using the web interface of Step.

Illustration for Robotic Process Automation (RPA) with Selenium
Robotic Process Automation (RPA) with Selenium

This tutorial will demonstrate how to use Step and Selenium to automate various browser tasks.

Illustration for Robotic Process Automation (RPA) with Cypress
Robotic Process Automation (RPA) with Cypress

This tutorial demonstrates how to use Step and Cypress to automate various browser tasks.

Illustration for Synthetic Monitoring with Selenium
Synthetic Monitoring with Selenium

This tutorial demonstrates how Selenium automation tests can be turned into full synthetic monitoring using Step.

Illustration for Load Testing with Cypress
Load Testing with Cypress

In this tutorial, you'll learn how to reuse existing Cypress tests to quickly set up and run a browser-based load test using the automation as code approach.

Illustration for Load Testing with Cypress
Load Testing with Serenity BDD and Cucumber

In this tutorial, you'll learn how to reuse existing tests written with Serenity BDD and Cucumber for load testing.

Illustration for Synthetic Monitoring with Cypress
Synthetic Monitoring with Cypress

This tutorial demonstrates how Cypress automation tests can be turned into full synthetic monitoring using the automation as code approach.

Illustration for Load Testing with Cypress
Load Testing with Cypress using the Step UI

In this tutorial, you'll learn how to reuse existing Cypress tests to quickly set up and run a browser-based load test using the Step UI.

Illustration for Load Testing with Selenium
Load Testing with Selenium

This tutorial demonstrates how to leverage existing Selenium tests to set up and execute browser-based load tests, following a full code-based approach.

Illustration for Load Testing with Selenium
Load Testing with Selenium using the Step UI

This tutorial demonstrates how to set up a browser-based load test in the Step UI using existing Selenium tests.

Illustration for Synthetic Monitoring with Playwright
Synthetic Monitoring with Playwright

This tutorial demonstrates how Playwright automation tests can be turned into full synthetic monitoring using Step.

Illustration for Synthetic Monitoring with Cypress
Synthetic Monitoring with Cypress using the Step UI

This tutorial demonstrates how Cypress automation tests can be turned into full synthetic monitoring using the Step UI.

Illustration for Robotic Process Automation (RPA) with Playwright
Robotic Process Automation (RPA) with Playwright

This tutorial will demonstrate how to use Step and Playwright to automate various browser tasks.

Illustration grafana devops tutorial
Distributed load testing with JMeter

This tutorial shows how to distribute JMeter tests across multiple nodes.

Illustration for Load Testing with Playwright
Load Testing with Playwright for Java

In this tutorial, you'll learn how to reuse existing Playwright tests written in Java to quickly set up and run a browser-based load test using the automation as code approach.

Illustration for playwright synthetic monitoring in a devops workflow
DevOps Synthetic Monitoring with Playwright - Advanced

This tutorial demonstrates how Playwright tests can be reused for synthetic monitoring of a productive environment in a DevOps workflow

Illustration grafana devops tutorial
Distributed load testing with Grafana K6

This tutorial shows how to distribute Grafana K6 tests across multiple nodes.

Illustration for playwright synthetic monitoring in a devops workflow
DevOps Synthetic Monitoring with Playwright

This tutorial demonstrates how Playwright tests can be reused for synthetic monitoring of a productive environment in a DevOps workflow

Illustration for okhttp devops
Protocol-based load testing with okhttp

In this tutorial you'll learn how to quickly set up a protocol-based load test with okhttp

Illustration for playwright devops
Continuous end-to-end testing

Learn how to set up continuous end-to-end testing across several applications based on Playwright tests in your DevOps pipeline using Step

Illustration for playwright devops
Continuous load testing with Playwright

Learn how to quickly set up continuous browser-based load testing using Playwright tests in your DevOps pipeline

Testing with MCP: a speech bubble behind three cubes representing execution, validation, and reporting.
AI-Assisted Testing with the Step MCP Server

End-to-end walkthrough: author a test, run it, and analyse the results using plain English prompts in Claude via the Step MCP Server.

Agentic Testing with Step Studio: a test specification checklist beside a Step Plan built from stacked Keyword cubes.
Agentic test automation with Step Studio (UI)

Delegate the whole testing journey to Step AI Agent: write your test cases in plain language in Step Studio, and collect a validated Automation Package you can schedule and share.

Want to hear our latest updates about automation?

Don't miss out on our regular blog posts - Subscribe now!

Image of a laptop device to incentivize users to subscribe