Talking About TestingATT</>
Agentic Testing with Cypress Tap

Score 100% on the quiz to continue

Lesson
Free Preview

Cypress Tap, by hand

Drive Cypress Tap yourself: run a spec, wait for a fresh verdict, read the app, and time travel.

Before you hand Cypress Tap to an agent, you will drive it yourself, once, by hand. Everything your agent does in this course, it does with the commands in this lesson. Knowing them is how you check its claims instead of believing them.

What Cypress Tap is

Cypress Tap is a set of commands, npx cypress tap <command>, that drive an already running cypress open session from the terminal. It ships inside the Cypress App (Cypress 15.21.0 and later), so there is nothing extra to install. It is free, needs no Cypress Cloud account, and runs only on your machine.

Three things to know before the first command:

  • It attaches to cypress open, never to cypress run. Tap reads the same Command Log and DOM snapshots you see in the Cypress window, so the window has to be open. Headless runs, like the ones in CI, are not reachable.
  • It only works in Chromium-based browsers: Chrome, Chromium, Edge, and Electron. That is why the cy:open script you wrote in the setup lesson passes --browser chrome.
  • The terminal and the Cypress window share one browser frame, with no lock between them. If you click around the app in the Cypress window while Tap (or your agent) is reading it, each of you sees the other's changes. Watch the window; do not touch it.
👨‍🏫 Cypress Tap is in beta. Its commands and output can still change in any release, which is one more reason the setup lesson pinned Cypress to an exact version.

The workflow

Every test in this course is written the same way, whether by you or by your agent. These six steps come from the project's Tap guide, docs/cypress/07-cypress-tap.md:

  1. Arrange the page state with a minimal spec. Tap can only read the app after a spec has taken it somewhere.
  2. Read the real app with aria, dom, and inspect to pick selectors and expected texts.
  3. Write the test: act and assert.
  4. Wait for a fresh verdict.
  5. Read the results and the Command Log.
  6. Refine the assertions using what the app actually shows.

Step 4 is the one people get wrong, so it gets its own section right below.

Waiting for a fresh verdict

run returns before the spec has started. And until the new run starts, status keeps showing the previous run's verdict. So a passed right after a run may describe the run before it.

The only safe way to wait is to compare startedAt:

  1. Before you run (or save) the spec, note the current startedAt from npx cypress tap status --json. Not afterwards: by then the new run may have started, and you would wait forever for a run that already happened.
  2. Poll npx cypress tap status --json about every two seconds, one call at a time.
  3. Accept only passed or failed, for the spec you expect, with a startedAt different from the one you noted.
  4. Give up after a bounded number of attempts, and stop at once if status exits with 1.

Here is that loop as a Bash script, which uses Node.js to read the JSON. Run it in a terminal at the root of your project: on macOS and Linux, the default terminal works; on Windows, use Git Bash, as recommended in the prerequisites. Replace the value of base with the startedAt you noted (or leave it as null before the very first run):

base="null"
for i in $(seq 1 60); do
  out=$(npx cypress tap status --json 2>/dev/null) || { echo "status failed"; break; }
  s=$(echo "$out" | node -e 'let d="";process.stdin.on("data",c=>d+=c).on("end",()=>{try{const j=JSON.parse(d);console.log(j.status+"|"+j.startedAt)}catch{console.log("?|?")}})')
  st=${s%%|*}; sa=${s#*|}
  if { [ "$st" = passed ] || [ "$st" = failed ]; } && [ "$sa" != null ] && [ "$sa" != undefined ] && [ "$sa" != "?" ] && [ "$sa" != "$base" ]; then
    echo "$st $sa"; break
  fi
  sleep 2
done

You will rarely type it yourself: it is what your agent runs, and the project's Tap guide carries it. But you should be able to read it, because when an agent says "the test passed", this is how it should know.

Saving a spec runs it

Once a spec is selected (the one status shows), Cypress watches it: saving it starts a new run on its own, exactly as when you work in the Cypress window. So after editing the selected spec, do not also call run, or you put two runs in flight. Note startedAt, save, and wait.

👨‍🏫 The watcher only reruns the selected spec. Saving a different spec does not run it; use npx cypress tap run <spec> for that.

Commands overview 📖

Every command accepts these shared flags:

  • --json prints machine-readable output instead of a table.
  • --session <pid> (or -s) picks a session when more than one cypress open is running.
  • --timeout <ms> sets how long Tap waits for the session to answer (default 30000).
  • --help (or -h) prints the command's usage.

Every command exits with 0 on success and 1 on failure, so an agent (or a script) can tell the difference without reading the output.

sessions

Lists the running cypress open sessions: process id (PID), project folder, testing type, and browser.

Syntax:

npx cypress tap sessions

Example:

npx cypress tap sessions --json

status

Reports where the session is in its lifecycle, which spec is selected, and the counts of the last run. The stages are not connected, browser not selected, spec not selected, loading, running, passed, and failed. Only passed and failed are verdicts; everything else means "not finished".

Syntax:

npx cypress tap status [--json]

Example:

npx cypress tap status --json

With --json, look for status, spec, startedAt (when the current run started, null until a run begins), totalTests, and results (passed, failed, pending, and skipped counts).

specs

Lists the specs the session can run, most recently modified first.

Syntax:

npx cypress tap specs

Example:

npx cypress tap specs

run

Starts a spec and returns immediately, before a single test has run. It does not wait for, or print, the verdict.

Syntax:

npx cypress tap run <spec-path>

Example:

npx cypress tap run cypress/e2e/explore.cy.js

reporter

Without --test-id, it prints the spec overview, like the Cypress window's reporter panel: each test with its result and a test id (such as r2). With --test-id, it prints that test's full Command Log: hooks, every command, network requests, and the error, if any. --attempt picks a retry attempt (1-based).

Syntax:

npx cypress tap reporter [--test-id <id>] [--attempt <n>]

Example:

npx cypress tap reporter --test-id r2
👨‍🏫 Copy test ids from reporter's output. Never infer them from the order of the tests in the spec.

command

Prints one row of a test's Command Log in detail: its snapshots and console properties. --command-id is the row number from reporter --test-id.

Syntax:

npx cypress tap command --test-id <id> --command-id <row> [--depth <n|all>]

Example:

npx cypress tap command --test-id r2 --command-id 3

pin

Time travel. It loads a command's DOM snapshot into the app frame, so the next aria, dom, or inspect reads the page as it was at that moment. --at picks the snapshot: before, after, or a 1-based index. --clear releases the pin and returns to the live app.

Syntax:

npx cypress tap pin --test-id <id> --command-id <row> [--at <before|after|n>]
npx cypress tap pin --clear

Example:

npx cypress tap pin --test-id r2 --command-id 1
👨‍🏫 Always run pin --clear when you are done reading a pinned snapshot. Until you do, every read shows the past.

aria

Prints the page's accessibility tree: each element's role, accessible name, and state, as a screen reader sees them. It is the best first look at a page: it shows what is there and how it is labeled, in a few lines. It reads up to 200 nodes by default (--max-nodes, or -m).

Syntax:

npx cypress tap aria [--selector <css>] [--at <index>]

Example:

npx cypress tap aria

dom

Prints the markup of the whole page, or of the element a CSS selector matches. Use it for exact texts and attributes, such as data-testid, id, type, and placeholder. It prints up to 30000 characters by default (--max-chars, or -m).

Syntax:

npx cypress tap dom [--selector <css>] [--at <index>]

Example:

npx cypress tap dom --selector '[data-testid="emoji-card"]' --at 1

inspect

Prints one element's tag, attributes, accessibility node, box model, and its most relevant computed styles. --selector is required.

Syntax:

npx cypress tap inspect --selector <css> [--at <index>]

Example:

npx cypress tap inspect --selector '[data-testid="emoji-card"]' --at 1
👨‍🏫 When a selector matches more than one element, dom, aria, and inspect refuse to guess. They exit with 1 and list the candidates with an index, and you pick one with --at <index>, which is 0-based here. pin --at is different: there, a number is 1-based, because it counts a command's snapshots, not elements. A deliberately broad selector is a good way to discover what is on the page.

Telemetry

Cypress Tap records which command you ran, with which flags, and whether it failed. The Cypress reference documents the opt-out as the environment variable CYPRESS_DISABLE_GUEST_TELEMETRY=0. Yes, a 0 that disables something: check the reference before relying on it, since Tap is in beta.

Suggested content 📚 and documentation links 🔗

Exercise 🎯

You will drive Cypress Tap by hand from a first cy.visit to a test that opens a product, reading the app at each step.

  • 1. In one terminal, at the root of your project, run npm run cy:open. When the browser opens on the spec list, leave it there: select nothing. Keep this terminal running for the whole course; Tap needs it.
  • 2. In a second terminal, run npx cypress tap sessions (one row, your project), then npx cypress tap status and npx cypress tap status --json. The stage is spec not selected, and startedAt is missing or null. Run npx cypress tap specs: there are none yet.
  • 3. Create cypress/e2e/explore.cy.js. Its only job, for now, is step 1 of the workflow, arranging the state:
describe('Exploring Emoji Mart', () => {
  it('opens the store', () => {
    cy.visit('/')
  })
})
  • 4. Run npx cypress tap specs again and find your spec. Note startedAt from npx cypress tap status --json. Then run npx cypress tap run cypress/e2e/explore.cy.js, and poll status --json (or run the Bash script from the section "Waiting for a fresh verdict") until you get a fresh verdict. Watch the Cypress window as it runs, without touching it.
  • 5. Run npx cypress tap reporter and copy the test's id. Then run npx cypress tap reporter --test-id <id> and read its Command Log. Look for network rows (requests the app made while the test ran): there are none. Emoji Mart loads everything it needs with the page and makes no requests after that, so there will be nothing to intercept in this course.
  • 6. Now read the app. Run npx cypress tap aria and find the store's buttons. Some of them have a name you can read; some have no name at all. Write down which ones. Then run npx cypress tap dom --selector '[data-testid="emoji-card"]': it refuses, because six cards match, and lists them. Run it again with --at 1 to read the second card, and npx cypress tap inspect --selector '[data-testid="emoji-card"]' --at 1 to see its attributes and box.
  • 7. Look in that aria output for a control that takes you back from a product's page. There is none, because no product page is open: the test stopped on the store. That is step 1 again: to read a state, a test has to reach it. Note the current startedAt, then change the test so it opens the Rocket and asserts that its page is showing, and save:
describe('Exploring Emoji Mart', () => {
  it('opens the Rocket', () => {
    cy.visit('/')

    cy.contains('[data-testid="emoji-card"]', 'Rocket').click()

    cy.contains('button', '← Back to all emojis').should('be.visible')
  })
})
👨‍🏫 Saving the selected spec runs it, so do not call run this time: wait for the fresh verdict. Then run npx cypress tap aria and confirm the role and the exact text of the back control. I wrote button because that is the role aria showed me. If yours shows something else, the assertion follows the app, not my example.
  • 8. Time travel: run npx cypress tap reporter --test-id <id> (the id may have changed, so copy it again), find the row number of the visit, and run npx cypress tap pin --test-id <id> --command-id <row>. The Cypress window shows the store again, before the click. Run npx cypress tap aria and see the six products, even though the live page shows the Rocket. Then run npx cypress tap pin --clear.
  • 9. Commit cypress/e2e/explore.cy.js and push.
🙊 The instructor solution will be linked here soon.

Show the world what you learned 🌎

To show your professional network what you learned in this lesson, post the following on LinkedIn.

I am taking the "Agentic Testing with Cypress Tap" course by @Walmyr Lima e Silva Filho at the @Talking About Testing School, and I just drove Cypress Tap by hand: I ran a spec from the terminal, waited for a fresh verdict instead of trusting a stale one, read the Command Log, the accessibility tree, and the DOM, and time traveled to an earlier snapshot. #TalkingAboutTesting #TATSchool #AgenticTestingWithCypressTap #CypressTap #AgenticTesting

👨‍🏫 Remember to tag me in your post. Here is my LinkedIn profile.

Quiz

Question 1 of 2
Score: 0

You run a spec with npx cypress tap run and immediately read status --json. It says passed. Before trusting it, what must you check, and why?

Score 100% on the quiz to continue