<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Cypress</title>
    <description>The latest articles on DEV Community by Cypress (cypress).</description>
    <link>https://dev.to/cypress</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F1689%2F7be136d1-5863-4f25-804d-90437a10c851.jpg</url>
      <title>DEV Community: Cypress</title>
      <link>https://dev.to/cypress</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cypress"/>
    <language>en</language>
    <item>
      <title>How to connect Claude Code to Cypress</title>
      <dc:creator>Jennifer Shehane</dc:creator>
      <pubDate>Thu, 15 Oct 2026 13:52:42 +0000</pubDate>
      <link>https://dev.to/cypress/how-to-connect-claude-code-to-cypress-1hll</link>
      <guid>https://dev.to/cypress/how-to-connect-claude-code-to-cypress-1hll</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5l52g3012hyitjctakip.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5l52g3012hyitjctakip.png" alt="Connect Claude to Cypress" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You ask Claude Code to fix a failing checkout test. It reads the spec, spots a likely timing issue, and suggests a reasonable fix. What it doesn’t know is that the same test has failed on and off in CI all week, or what the page looked like when it failed. A fix that looks right in the editor may not touch what’s really going on.&lt;/p&gt;

&lt;p&gt;Sound familiar? That gap usually isn’t about the model. It’s about context. Claude Code can read your spec files and pick up some of your patterns along the way. What it can’t see is your run history: which tests failed in CI, which ones are flaky, and what happened in your last recorded run.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, some terminology
&lt;/h2&gt;

&lt;p&gt;If you’re new to AI coding tools, the vocabulary piles up fast. Here’s what each term means in this post:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Context:&lt;/strong&gt; everything Claude Code has in front of it during a session, including your messages, the files it's read, its instructions, and the results of the tools it uses. Claude Code can only work with what’s in its context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token:&lt;/strong&gt; a small chunk of text, often part of a word. AI models measure context and usage in tokens, so a larger context uses more tokens on every turn.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CLI (command-line interface):&lt;/strong&gt; a tool you run by typing commands in a terminal, like &lt;code&gt;npx cypress run&lt;/code&gt;. Claude Code can run CLI commands too, such as the Cypress Cloud CLI, &lt;code&gt;cy-cloud&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skill:&lt;/strong&gt; a folder of instructions that teaches Claude Code how to handle a specific kind of task. Claude Code loads a skill only when a task calls for it. Cypress AI Skills cover writing, explaining, running, and debugging Cypress tests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP (Model Context Protocol):&lt;/strong&gt; an open standard that lets AI tools connect to other systems. An MCP server offers tools that Claude Code can call to get live data. Cloud MCP is our MCP server for Cypress Cloud.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connector:&lt;/strong&gt; an MCP server you add to your Claude account at claude.ai, so Claude can use it across Claude apps. If you sign in to Claude Code with your claude.ai account, your connectors are available there automatically. Cypress Cloud is &lt;a href="https://claude.ai/directory/cypress-cloud?ref=cypress-io.ghost.io" rel="noopener noreferrer"&gt;available as a connector&lt;/a&gt; in the Claude connectors directory, which gives you a way to use Cloud MCP without running a setup command.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plugin:&lt;/strong&gt; a package that installs several pieces into Claude Code at once, such as skills and MCP server settings. You install a plugin from a marketplace, a catalog of plugins that anyone can publish. The Cypress plugin installs all five Cypress AI Skills and sets up Cloud MCP in one step.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt; file:&lt;/strong&gt; a markdown file of instructions in your project, such as your team’s testing conventions, that Claude Code reads as context.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post sets up three pieces: Cypress AI Skills, which help Claude Code work the way your team does; Cloud MCP, which gives it your run history; and &lt;code&gt;cypress tap&lt;/code&gt;, which lets it check its work in your open Cypress session. You can use any of them on its own.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmh38a8mx3khggco58g0g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmh38a8mx3khggco58g0g.png" alt="How the pieces fit: Cypress AI Skills, CLAUDE.md files, and the Cypress app on your machine; recorded runs in Cypress Cloud, reached through Cloud MCP or the Cloud CLI" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Skills, &lt;code&gt;CLAUDE.md&lt;/code&gt; files, and &lt;code&gt;cypress tap&lt;/code&gt; all work with what’s on your machine. Only Cloud MCP and the Cloud CLI reach into Cypress Cloud for your recorded runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which setup do you need?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You want better tests from Claude Code, and you don’t use Cypress Cloud.&lt;/strong&gt; Install Cypress AI Skills. They’re free and need no account.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You write tests locally and want Claude Code to check its own work.&lt;/strong&gt; Install the skills and keep a &lt;code&gt;cypress open&lt;/code&gt; session running so Claude Code can use &lt;code&gt;cypress tap&lt;/code&gt;. This works without Cypress Cloud.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You record runs to Cypress Cloud and spend time investigating flake and failures.&lt;/strong&gt; Install the skills and connect Cloud MCP or the Cloud CLI. This is where the two work best together.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You’re rolling this out to a team.&lt;/strong&gt; Commit the skills and your Cypress CLAUDE.md file to your repository, ask an admin to turn on Cloud MCP, and consider a &lt;code&gt;project&lt;/code&gt; scope OAuth configuration so everyone starts from the same setup.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Quick start: install the Cypress plugin
&lt;/h2&gt;

&lt;p&gt;The fastest way to set up the skills and &lt;a href="https://claudemarketplaces.com/mcp/io.cypress.mcp/cypress-cloud?ref=cypress-io.ghost.io" rel="noopener noreferrer"&gt;Cloud MCP&lt;/a&gt; together is the Cypress plugin for Claude Code. It installs all five Cypress AI Skills and configures Cloud MCP in one step. In a Claude Code session, add the Cypress marketplace and install the plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/plugin marketplace add cypress-io/ai-toolkit
/plugin &lt;span class="nb"&gt;install &lt;/span&gt;cypress@cypress

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Choose an install scope when Claude Code asks.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;User scope&lt;/strong&gt; gives you the plugin in every project on your machine. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project scope&lt;/strong&gt; turns it on for everyone who works in the repository, and each person installs it once.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An organization admin still needs to &lt;a href="https://on.cypress.io/cloud-mcp%20?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=connect-claude-to-cypress#Enable-for-Organization" rel="noopener noreferrer"&gt;turn on Cloud MCP in Cypress Cloud&lt;/a&gt;, and you sign in the first time Claude Code uses it.&lt;/p&gt;

&lt;p&gt;If you’d rather install the pieces one at a time, follow the steps below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install Cypress AI Skills
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://on.cypress.io/ai-skills?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=connect-claude-to-cypress" rel="noopener noreferrer"&gt;Cypress AI Skills&lt;/a&gt; live in the open source &lt;a href="https://github.com/cypress-io/ai-toolkit?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=connect-claude-to-cypress" rel="noopener noreferrer"&gt;cypress-io/ai-toolkit repository on GitHub&lt;/a&gt;. They don’t require a Cypress Cloud account, a subscription, or have usage limits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There are five skills:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Skill&lt;/th&gt;
&lt;th&gt;What it helps Claude Code do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cypress-author&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create, update, and fix end-to-end and component tests. It reads your config, custom commands, and fixtures first, so new tests match the patterns you already use.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cypress-explain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Review and explain existing tests. It flags brittle selectors and missing assertions and uses accurate Cypress terminology.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cypress-tap&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run and debug tests against a live &lt;code&gt;cypress open&lt;/code&gt; session from the command line. Claude Code can verify its own work by reading reporter output and inspecting the app where a test fails.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cypress-cloud-cli&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Investigate recorded runs through the Cypress Cloud CLI, including test attempts and failures reconstructed from Test Replay data.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cypress-docs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Route Cypress questions to the official documentation, with awareness of the Cypress version you use.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Install all Cypress skills
&lt;/h3&gt;

&lt;p&gt;From the root of your project, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add cypress-io/ai-toolkit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The skills install at the project level, so you can commit them and give everyone on your team the same Cypress context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install one Cypress skill
&lt;/h3&gt;

&lt;p&gt;If you only want one skill, name it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add https://github.com/cypress-io/ai-toolkit &lt;span class="nt"&gt;--skill&lt;/span&gt; cypress-author
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prefer the GitHub CLI? This works too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh skill &lt;span class="nb"&gt;install &lt;/span&gt;cypress-io/ai-toolkit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also copy the skill folders into &lt;code&gt;.claude/skills&lt;/code&gt; in your project by hand.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use a skill
&lt;/h3&gt;

&lt;p&gt;Claude Code picks up the right skill when a task matches it. You can also call one directly with a slash command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/cypress-author Write an end-to-end test for the guest checkout flow. A guest should be able to add a product to the cart, enter a shipping address, and see the order confirmation page.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you installed the Cypress plugin, include its prefix: &lt;code&gt;/cypress:cypress-author&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Skills won’t turn a vague request into a precise one. The clearer your acceptance criteria, the closer the first draft will be to what you want.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Connect Claude Code to Cypress Cloud
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://on.cypress.io/cloud-mcp?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=connect-claude-to-cypress" rel="noopener noreferrer"&gt;Cloud MCP&lt;/a&gt; is free on every Cypress Cloud plan, including Starter. It reads recorded run data, so you need to record your runs to Cypress Cloud to use it.&lt;/p&gt;

&lt;p&gt;Claude Code can reach your recorded runs in three ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cypress Cloud connector&lt;/li&gt;
&lt;li&gt;Cloud MCP server&lt;/li&gt;
&lt;li&gt;Cypress Cloud CLI&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All three read recorded run data, so you need to record your runs to Cypress Cloud to use them. Cloud MCP and Cypress Cloud CLI are free on every Cypress Cloud plan, including Starter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ask an admin to turn on the Cloud MCP integration
&lt;/h3&gt;

&lt;p&gt;An organization admin turns on Cloud MCP from the &lt;strong&gt;Integrations&lt;/strong&gt; page in your Cypress Cloud organization settings. After that, each person connects their own Claude Code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyv2ymdxc0gfjh8t75xsc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyv2ymdxc0gfjh8t75xsc.png" alt="Cypress MCP enable toggle in Cypress Cloud organization setting" width="800" height="85"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1: Add the Cypress Cloud connector
&lt;/h3&gt;

&lt;p&gt;If you sign in to Claude Code with your claude.ai account, the connector is the quickest route. You add it once, and it works in Claude Code and the Claude apps.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the &lt;a href="https://claude.ai/directory/cypress-cloud" rel="noopener noreferrer"&gt;Cypress Cloud connector&lt;/a&gt; in the Claude connectors directory.&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Connect to Claude&lt;/strong&gt; , then sign in to Cypress Cloud when prompted.&lt;/li&gt;
&lt;li&gt;In Claude Code, run &lt;code&gt;/mcp&lt;/code&gt; to confirm the connector is listed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8rs8lcbfvaw0l2yj7op7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8rs8lcbfvaw0l2yj7op7.png" alt="Cypress Cloud connector in Claude directory shows 'Connect to Claude' button" width="799" height="171"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2: Add the Cloud MCP server from your terminal
&lt;/h3&gt;

&lt;p&gt;Use this route if you sign in to Claude Code another way, such as with an API key, or if you want to control the scope more precisely. If you already added the connector, you can skip it. If you do both, Claude Code uses the server you added here and hides the connector as a duplicate.&lt;/p&gt;

&lt;p&gt;We recommend signing in with OAuth. Run this command in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add cypress-cloud &lt;span class="se"&gt;\&lt;/span&gt;
  https://mcp.cypress.io/mcp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--transport&lt;/span&gt; http &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--scope&lt;/span&gt; user
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then start Claude Code with &lt;code&gt;claude&lt;/code&gt; and ask it something about Cypress Cloud. Claude Code opens your browser so you can sign in to Cypress Cloud. Your session lasts 30 days, so you won’t need to rotate tokens, and you can revoke access at any time from your &lt;strong&gt;Manage Profile&lt;/strong&gt; page in Cypress Cloud. To check the connection, run &lt;code&gt;/mcp&lt;/code&gt; inside Claude Code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Choose a scope for the Cloud MCP
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;--scope&lt;/code&gt; flag decides where the connection is available:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--scope user&lt;/code&gt; makes Cloud MCP available in every project on your machine. This is the setting we recommend.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--scope local&lt;/code&gt; limits it to the current project.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--scope project&lt;/code&gt; saves the configuration to a &lt;code&gt;.mcp.json&lt;/code&gt; file in your repository, so teammates who pull the repository get the same setup. Each person still signs in with their own Cypress Cloud account.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use a personal access token instead of OAuth
&lt;/h3&gt;

&lt;p&gt;If OAuth doesn’t fit your environment, create an &lt;strong&gt;MCP personal access token&lt;/strong&gt; from your &lt;strong&gt;Manage Profile&lt;/strong&gt; page in Cypress Cloud.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjetphdjw3i9i138kh06i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjetphdjw3i9i138kh06i.png" alt="Personal access token UI to generate token for Cloud MCP server" width="798" height="180"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cypress Cloud shows the token once, so copy it right away. Then add it as a header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add cypress-cloud &lt;span class="se"&gt;\&lt;/span&gt;
  https://mcp.cypress.io/mcp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--transport&lt;/span&gt; http &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--scope&lt;/span&gt; user &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_MCP_TOKEN"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep token-based setups at &lt;code&gt;user&lt;/code&gt; or &lt;code&gt;local&lt;/code&gt; scope. The &lt;code&gt;project&lt;/code&gt; scope writes the configuration to a file in your repository, and a token doesn’t belong there.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 3: Use the Cypress Cloud CLI
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://on.cypress.io/cloud-cli?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=connect-claude-to-cypress" rel="noopener noreferrer"&gt;Cypress Cloud CLI&lt;/a&gt;, &lt;code&gt;cy-cloud&lt;/code&gt;, reads the same recorded runs as Cloud MCP, from your terminal. The &lt;code&gt;cypress-cloud-cli&lt;/code&gt; skill from Step 1 teaches Claude Code how to use it. The CLI can also download Test Replay data, so Claude Code can step through a test’s timeline and inspect the page at the moment it failed.&lt;/p&gt;

&lt;p&gt;Before you start, an organization admin needs to turn on the Cloud CLI integration in Cypress Cloud.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqkujewtegk1p54n3qgba.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqkujewtegk1p54n3qgba.png" alt="Cloud CLI toggle to enable for organization" width="797" height="86"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install the CLI and sign in:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--global&lt;/span&gt; @cypress/cloud
cy-cloud login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;cy-cloud login&lt;/code&gt; opens your browser to sign in. In CI or a Claude Code web session, set &lt;code&gt;CYPRESS_CLOUD_TOKEN&lt;/code&gt; to a personal access token instead. It signs you in without writing credentials to disk.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloud MCP or the Cloud CLI?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cloud MCP&lt;/strong&gt; is the quickest to set up. It works in the Claude apps and any MCP client, and it has tools for Cypress Accessibility and UI Coverage reports.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud CLI&lt;/strong&gt; adds nothing to Claude Code’s context until it runs, and it can dig into Test Replay data in great detail.&lt;/li&gt;
&lt;li&gt;You can use both.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Prompts to try with Cypress Cloud MCP
&lt;/h3&gt;

&lt;p&gt;Here are a few prompts to start with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Find the latest passing run on main in Cypress Cloud and get the duration of every spec, paging through all results. Then suggest how to balance our specs across parallel CI machines.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;For latest run on this branch in Cypress Cloud, pull spec durations and tell me which specs are the slowest. Then break the slowest one down by test so I know whether to split it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;List every skipped and pending test in the last run on this branch in Cypress Cloud, grouped by spec.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run, spec, test, and failure data are available on every plan.&lt;/p&gt;

&lt;p&gt;Flaky test data, Cypress Accessibility results, and UI Coverage reports are available for plans that have access to those features, so those tools return data when your plan includes them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;List all flaky tests from the last five runs in Cypress Cloud. Do the error messages share any patterns?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pull the Cypress Accessibility report for the latest run on this branch. List any critical or serious violations, grouped by rule.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Using Cypress Cloud, pull the UI Coverage report for the latest run on this branch. What is the overall score, and which untested elements are in the riskiest view?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cloud MCP limits how many tool requests you can make per hour. The &lt;a href="https://on.cypress.io/cloud-mcp?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=connect-claude-to-cypress" rel="noopener noreferrer"&gt;Cloud MCP documentation&lt;/a&gt; lists the current limit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Let Claude Code check its work with &lt;code&gt;cypress tap&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Writing a test is only half the job. The other half is watching it run and seeing what went wrong. &lt;a href="https://on.cypress.io/cypress-tap?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=connect-claude-to-cypress" rel="noopener noreferrer"&gt;&lt;code&gt;cypress tap&lt;/code&gt;&lt;/a&gt; is a command-line interface (CLI) built into the Cypress App that lets Claude Code work with the Cypress App you already have open. It can run a spec, read the results and the Command Log, go back to the snapshot at any command, and inspect the page’s DOM and accessibility tree. That’s the same context you use when you debug a test yourself.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;cypress-tap&lt;/code&gt; skill you installed in Step 1 teaches Claude Code how to use these commands well. For example, it makes sure Claude reads the result of the run it just started, not the previous one.&lt;/p&gt;

&lt;h3&gt;
  
  
  How it differs from &lt;code&gt;cypress run&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;cypress run&lt;/code&gt; runs your specs headlessly and exits, which is what you want in CI. &lt;code&gt;cypress tap&lt;/code&gt; attaches to a running &lt;code&gt;cypress open&lt;/code&gt; session instead, so Claude Code can rerun a spec, look at the app at the moment a test failed, and try again without starting over.&lt;/p&gt;

&lt;h3&gt;
  
  
  Start a &lt;code&gt;cypress tap&lt;/code&gt; session
&lt;/h3&gt;

&lt;p&gt;Open Cypress the way you normally would, and leave it running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx cypress open &lt;span class="nt"&gt;--e2e&lt;/span&gt; &lt;span class="nt"&gt;--browser&lt;/span&gt; electron

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then start Claude Code from the same project folder. Claude Code finds the open session automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ask Claude Code to verify a test
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Run cypress/e2e/checkout/guest-checkout.cy.ts in my open Cypress session. If a test fails, look at the page at the failing command and fix the selector or assertion.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here’s what that looks like in practice. Say Claude Code wrote the checkout test with a guessed selector, &lt;code&gt;.submit-btn&lt;/code&gt;, and the test fails. Claude Code reads the failing test from the reporter, goes back to the snapshot at the failing command, and inspects the page. It finds that the button is actually &lt;code&gt;[data-cy="checkout-submit"]&lt;/code&gt;, updates the spec, and runs it again. No guessing, and no copying error messages out of the Cypress app.&lt;/p&gt;

&lt;p&gt;You can run the same commands yourself, which is useful when you want to see what Claude Code sees:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx cypress tap status
npx cypress tap run cypress/e2e/checkout/guest-checkout.cy.ts
npx cypress tap dom &lt;span class="nt"&gt;--selector&lt;/span&gt; &lt;span class="s2"&gt;"[data-cy=checkout-submit]"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: give your Cypress tests their own CLAUDE.md
&lt;/h2&gt;

&lt;p&gt;Skills teach Claude Code how Cypress works. A &lt;code&gt;CLAUDE.md&lt;/code&gt; file teaches it how your team works. It’s a plain markdown file of instructions that Claude Code reads as context, and you can put one right in your &lt;code&gt;cypress&lt;/code&gt; folder so your testing conventions live next to your tests.&lt;/p&gt;

&lt;p&gt;Why a separate file? Your root &lt;code&gt;CLAUDE.md&lt;/code&gt; probably covers how to build and run the app. Cypress conventions, like which selectors to use or which custom commands exist, matter only when Claude Code works on tests. A separate file keeps the root file short and gives the people who own your test suite a file they can maintain.&lt;/p&gt;

&lt;p&gt;The skills already read your config and custom commands. &lt;code&gt;CLAUDE.md&lt;/code&gt; is where you put the decisions that aren’t visible in the code, like which of two similar commands your team prefers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a CLAUDE.md file in your &lt;code&gt;cypress&lt;/code&gt; folder
&lt;/h3&gt;

&lt;p&gt;Add a &lt;code&gt;CLAUDE.md&lt;/code&gt; file to your &lt;code&gt;cypress&lt;/code&gt; folder. Here’s an example; swap in your own commands, paths, and rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Cypress tests&lt;/span&gt;

&lt;span class="gu"&gt;## Selectors&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Select elements with &lt;span class="sb"&gt;`data-cy`&lt;/span&gt; attributes, for example &lt;span class="sb"&gt;`cy.get('[data-cy="checkout-submit"]')`&lt;/span&gt;.
&lt;span class="p"&gt;-&lt;/span&gt; Don't select elements by CSS class or position.

&lt;span class="gu"&gt;## Custom commands&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Use &lt;span class="sb"&gt;`cy.login(role)`&lt;/span&gt; to sign in. Only tests in &lt;span class="sb"&gt;`cypress/e2e/auth/`&lt;/span&gt; drive the login form directly.
&lt;span class="p"&gt;-&lt;/span&gt; Use &lt;span class="sb"&gt;`cy.seedCart(products)`&lt;/span&gt; to set up cart state through the API.

&lt;span class="gu"&gt;## Running tests&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Run one spec with &lt;span class="sb"&gt;`npx cypress run --spec cypress/e2e/checkout/guest-checkout.cy.ts`&lt;/span&gt;.
&lt;span class="p"&gt;-&lt;/span&gt; Reuse fixtures in &lt;span class="sb"&gt;`cypress/fixtures`&lt;/span&gt; before creating new ones.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep it short and specific. Anthropic recommends staying under 200 lines per file, because longer files use more context and Claude Code follows them less consistently.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Claude Code scopes a CLAUDE.md file to the folder
&lt;/h3&gt;

&lt;p&gt;You don’t need any configuration. Claude Code loads a &lt;code&gt;CLAUDE.md&lt;/code&gt; file in a subfolder on demand: when it reads, writes, or edits a file inside &lt;code&gt;cypress/&lt;/code&gt;, it adds &lt;code&gt;cypress/CLAUDE.md&lt;/code&gt; to its context. In a session where you only work on application code, the file doesn’t load.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scope Cypress rules by file pattern instead
&lt;/h3&gt;

&lt;p&gt;A folder file only covers files inside that folder. If some of your specs live elsewhere, like component tests next to the components they cover, use a path-scoped rule instead. Create &lt;code&gt;.claude/rules/cypress.md&lt;/code&gt; at the root of your project and list the paths it applies to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cypress/**/*"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;src/**/*.cy.{js,ts,jsx,tsx}"&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="c1"&gt;# Cypress tests&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Select elements with `data-cy` attributes.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude Code loads the rule only when it reads, writes, or edits a file that matches one of the patterns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Share it with your team
&lt;/h3&gt;

&lt;p&gt;Commit &lt;code&gt;cypress/CLAUDE.md&lt;/code&gt; or your rule file so everyone gets the same conventions. For personal notes that shouldn’t be shared, like the port your local server runs on, use a &lt;code&gt;CLAUDE.local.md&lt;/code&gt; file at the project root and add it to your &lt;code&gt;.gitignore&lt;/code&gt; file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Put them together: from a CI failure to a verified fix
&lt;/h2&gt;

&lt;p&gt;Say a spec fails on your branch in CI. Instead of opening Cypress Cloud, finding the run, copying the error, and pasting it into a chat, you ask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The checkout spec failed in Cypress Cloud on my branch in CI. Find out why and fix it.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here’s what Claude Code can do with all the Cypress connections in place:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Find the failure.&lt;/strong&gt; Through Cloud MCP, it pulls the failed test, the error message, the stack trace, and a Test Replay link for the run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Understand the test.&lt;/strong&gt; With &lt;code&gt;cypress-explain&lt;/code&gt;, it reads the spec and identifies the likely cause, such as an assertion that runs before an API response arrives.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write the fix.&lt;/strong&gt; With &lt;code&gt;cypress-author&lt;/code&gt;, it updates the test using your existing custom commands and selector conventions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify the fix.&lt;/strong&gt; With &lt;code&gt;cypress-tap&lt;/code&gt;, it runs the spec against your open Cypress session and reads the results.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You stay in the loop the whole way. Claude Code proposes changes to your local files, and you review them like you’d review a teammate’s pull request. What changes is how much time you spend gathering context before you can make a decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Cypress data Claude Code can and can’t see
&lt;/h2&gt;

&lt;p&gt;Giving an AI assistant access to your test data is a reasonable thing to be careful about, so here’s exactly how Cloud MCP works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It’s read-only.&lt;/strong&gt; Cloud MCP can’t change your tests or delete runs in Cypress Cloud.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It follows your permissions.&lt;/strong&gt; Claude Code sees only the projects your Cypress Cloud account can see, based on your existing role in your organization and team.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;We don’t see your conversation.&lt;/strong&gt; Cypress has no access to your chat history or Claude Code’s context. We receive only the inputs to each tool call, such as a project ID.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Your test data isn’t used for model training.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You control the connection.&lt;/strong&gt; Cloud MCP works only where you configure it, and you can revoke OAuth access at any time from your &lt;strong&gt;Manage Profile&lt;/strong&gt; page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI Skills run locally in your project and don’t send data to Cypress Cloud on their own. The &lt;code&gt;cypress-cloud-cli&lt;/code&gt; skill reads Cloud data only through the Cloud CLI you authenticate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get started
&lt;/h2&gt;

&lt;p&gt;The setup takes a few minutes: one command for the skills and one for Cloud MCP or the Cloud CLI. The &lt;a href="https://docs.cypress.io/app/tooling/ai-skills?utm_source=blog&amp;amp;utm_medium=inline-link&amp;amp;utm_content=Cypress%20AI%20Skills%20documentation&amp;amp;utm_campaign=claude-code-cypress-oct-2026&amp;amp;utm_term=individual-contributor" rel="noopener noreferrer"&gt;Cypress AI Skills documentation&lt;/a&gt;, the &lt;a href="https://docs.cypress.io/cloud/integrations/cloud-mcp?utm_source=blog&amp;amp;utm_medium=inline-link&amp;amp;utm_content=Cloud%20MCP%20documentation&amp;amp;utm_campaign=claude-code-cypress-oct-2026&amp;amp;utm_term=individual-contributor" rel="noopener noreferrer"&gt;Cloud MCP documentation&lt;/a&gt;, and the &lt;a href="https://docs.cypress.io/cloud/integrations/cloud-cli?utm_source=blog&amp;amp;utm_medium=inline-link&amp;amp;utm_content=Cloud%20CLI%20documentation&amp;amp;utm_campaign=claude-code-cypress-oct-2026&amp;amp;utm_term=individual-contributor" rel="noopener noreferrer"&gt;Cloud CLI documentation&lt;/a&gt; cover every option in detail, and the &lt;a href="https://code.claude.com/docs/en/mcp?ref=cypress-io.ghost.io" rel="noopener noreferrer"&gt;Claude Code MCP documentation&lt;/a&gt; explains how Claude Code manages servers.&lt;/p&gt;

&lt;p&gt;To go deeper on &lt;code&gt;CLAUDE.md&lt;/code&gt; files and path-scoped rules, see &lt;a href="https://code.claude.com/docs/en/memory?ref=cypress-io.ghost.io" rel="noopener noreferrer"&gt;how Claude Code remembers your project&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Do I need Cypress Cloud to use Claude Code with Cypress?
&lt;/h3&gt;

&lt;p&gt;No. Cypress AI Skills and &lt;code&gt;cypress tap&lt;/code&gt; work with your local project and your open Cypress session. You need Cypress Cloud only for Cloud MCP and the Cloud CLI, which read your recorded runs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Cloud MCP free?
&lt;/h3&gt;

&lt;p&gt;Yes. Cloud MCP is included on every Cypress Cloud plan, including Starter. Some tools return data only when your plan includes the related feature, such as flaky test detection, Cypress Accessibility, or UI Coverage. Usage limits are subject to change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Are Cypress AI Skills free?
&lt;/h3&gt;

&lt;p&gt;Yes. They’re open source, and they don’t require a Cypress Cloud account or subscription.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can Cypress see my Claude Code conversations?
&lt;/h3&gt;

&lt;p&gt;No. Cypress receives only the inputs to each Cloud MCP tool call, such as a project ID. We have no access to your conversation or Claude Code’s context, and your test data isn’t used for model training. Prompts sent directly to the &lt;code&gt;submit_mcp_feedback&lt;/code&gt; tool call are sent to our team, so only call this when you have feedback to share with us.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does this work with Cursor or GitHub Copilot?
&lt;/h3&gt;

&lt;p&gt;Yes. Cypress AI Skills work with AI coding tools that support skills, including Cursor and GitHub Copilot, and Cloud MCP works with any MCP-compatible client. The setup steps differ by tool. Check the &lt;a href="https://on.cypress.io/cloud-mcp?ref=cypress-io.ghost.io#Configure-AI-Assistant?utm_source=blog&amp;amp;utm_medium=inline-link&amp;amp;utm_content=Cloud%20MCP%20documentation&amp;amp;utm_campaign=claude-code-cypress-oct-2026&amp;amp;utm_term=individual-contributor" rel="noopener noreferrer"&gt;setup steps in our documentation&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>claude</category>
      <category>testing</category>
      <category>cypress</category>
      <category>ai</category>
    </item>
    <item>
      <title>Find elements the way your users do with cy.getByRole()</title>
      <dc:creator>Jennifer Shehane</dc:creator>
      <pubDate>Wed, 14 Oct 2026 17:54:38 +0000</pubDate>
      <link>https://dev.to/cypress/find-elements-the-way-your-users-do-with-cygetbyrole-2l43</link>
      <guid>https://dev.to/cypress/find-elements-the-way-your-users-do-with-cygetbyrole-2l43</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsgtaem08drjexpvsr7px.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsgtaem08drjexpvsr7px.png" alt=" " width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Starting in Cypress 16.2.0, &lt;code&gt;cy.getByRole()&lt;/code&gt; is a built-in query that finds elements by their role and accessible name. The role is what a screen reader announces an element as, such as a button or a link, and the accessible name is the text it announces for it.&lt;/p&gt;

&lt;p&gt;This offers a built-in alternative to the &lt;code&gt;.findByRole()&lt;/code&gt; selector from the popular &lt;a href="https://github.com/testing-library/cypress-testing-library" rel="noopener noreferrer"&gt;Cypress Testing Library&lt;/a&gt; plugin. That package no longer has an active maintainer and the previous maintainer suggested Cypress build these queries into Cypress directly.&lt;/p&gt;

&lt;p&gt;We agree with that direction. Role-based queries are a core way to write resilient, accessible tests, and they belong in Cypress itself. &lt;code&gt;cy.getByRole()&lt;/code&gt; is the first of these built-in queries. We plan to bring more of the Testing Library queries teams depend on into Cypress.&lt;/p&gt;

&lt;h2&gt;
  
  
  What &lt;code&gt;cy.getByRole()&lt;/code&gt; does
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;cy.getByRole()&lt;/code&gt; finds every element with a given role, such as &lt;code&gt;button&lt;/code&gt;, &lt;code&gt;link&lt;/code&gt;, &lt;code&gt;heading&lt;/code&gt;, or &lt;code&gt;tab&lt;/code&gt;, and can narrow the results to elements whose accessible name matches. Because those are what assistive technology announces, a test written this way finds elements the way assistive technology does. If a control’s role or label changes, the test fails.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;account settings&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;saves profile changes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/settings&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;textbox&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Display name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Jane Doe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Save changes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alert&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;have.text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Changes saved&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few behaviors on how &lt;code&gt;cy.getByRole()&lt;/code&gt; works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It yields every match.&lt;/strong&gt; Like &lt;a href="https://on.cypress.io/get?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_content=cy.get%28%29&amp;amp;utm_campaign=getbyrole-launch-oct-2026" rel="noopener noreferrer"&gt;&lt;code&gt;cy.get()&lt;/code&gt;&lt;/a&gt;, &lt;code&gt;cy.getByRole()&lt;/code&gt; yields all matching elements in document order. Narrow the &lt;code&gt;name&lt;/code&gt;, or chain &lt;code&gt;.first()&lt;/code&gt; or &lt;code&gt;.eq()&lt;/code&gt;, to act on one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A string name matches the whole name, case-sensitively.&lt;/strong&gt; Pass a regular expression to match part of a name or ignore case, or a function for custom logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It skips elements hidden from the accessibility tree&lt;/strong&gt; by default, such as elements with &lt;code&gt;aria-hidden="true"&lt;/code&gt; or &lt;code&gt;display: none&lt;/code&gt;. Pass &lt;code&gt;hidden: true&lt;/code&gt; to include them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It retries&lt;/strong&gt; like any Cypress query, so it waits for an element to appear and for chained assertions to pass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It prefers built-in HTML elements.&lt;/strong&gt; If a native HTML element exists for a given role, &lt;code&gt;cy.getByRole()&lt;/code&gt; matches that element only, unless &lt;code&gt;native: false&lt;/code&gt; is passed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To narrow by state, such as checked, selected, expanded, or heading level, chain &lt;code&gt;.filter()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The tab that is currently selected&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tab&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[aria-selected="true"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Level 2 headings&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;heading&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[aria-level="2"], h2:not([aria-level])&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// A checked native checkbox&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checkbox&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:checked&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// A checked checkbox that gets its role from a role attribute&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checkbox&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;native&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[aria-checked="true"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// The menu button whose menu is open&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[aria-expanded="true"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// A toggle button that is switched on&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[aria-pressed="true"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// The link to the page the reader is on&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;link&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[aria-current="page"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Every button except disabled ones&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;not&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:disabled, [aria-disabled="true"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://on.cypress.io/api/commands/getbyrole?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_content=cy.getByRole%28%29%20API%20reference&amp;amp;utm_campaign=getbyrole-launch-oct-2026" rel="noopener noreferrer"&gt;&lt;code&gt;cy.getByRole()&lt;/code&gt; API reference&lt;/a&gt; covers every option, including shadow DOM, TypeScript, and scoping with &lt;code&gt;.within()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why native elements are the default in &lt;code&gt;cy.getByRole()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;For any role that HTML has a native element for, such as &lt;code&gt;button&lt;/code&gt;, &lt;code&gt;link&lt;/code&gt;, &lt;code&gt;heading&lt;/code&gt;, &lt;code&gt;dialog&lt;/code&gt;, or &lt;code&gt;checkbox&lt;/code&gt;, &lt;code&gt;cy.getByRole()&lt;/code&gt; matches only elements whose tag gives them that role. A &lt;code&gt;&amp;lt;div role="button"&amp;gt;&lt;/code&gt; does not match &lt;code&gt;cy.getByRole('button')&lt;/code&gt; unless you pass &lt;code&gt;native: false&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Save&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;tabindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Cancel&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
&lt;span class="c1"&gt;// Yields and clicks the native &amp;lt;button&amp;gt;&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Save&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// The &amp;lt;div&amp;gt; has the role only through its role attribute, so it's skipped&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cancel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;not.exist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why make more strict behavior the default? Because a role-based locator that accepts both elements can cover up accessibility issues that could be easily discovered by your tests. Native controls are the most likely to work well for keyboard-only users, including many people with motor disabilities, or for screen reader users, because the browser handles the behavior automatically and consistently.&lt;/p&gt;

&lt;p&gt;On the other hand, custom controls may implement a &lt;code&gt;role&lt;/code&gt; attribute in order to satisfy a byRole selector, and often skip the rest of the required behavior. This creates a false impression that something is accessible.&lt;/p&gt;

&lt;h3&gt;
  
  
  What a &lt;code&gt;role&lt;/code&gt; attribute does not support natively
&lt;/h3&gt;

&lt;p&gt;Adding &lt;code&gt;role="button"&lt;/code&gt; to a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; changes one thing: assistive technology announces the element as a button. As &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles/button_role" rel="noopener noreferrer"&gt;MDN’s documentation of the button role&lt;/a&gt; explains, the role does not add keyboard handling or other button behavior. The application code has to account for all of it.&lt;/p&gt;

&lt;p&gt;A native &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; gives you these behaviors built in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automatically joins the keyboard tab order.&lt;/li&gt;
&lt;li&gt;Activates with both the Enter and Space keys.&lt;/li&gt;
&lt;li&gt;Fires a &lt;code&gt;click&lt;/code&gt; event for keyboard activation, not only for mouse clicks.&lt;/li&gt;
&lt;li&gt;Supports the &lt;code&gt;disabled&lt;/code&gt; attribute, which removes the button from the tab order and blocks activation.&lt;/li&gt;
&lt;li&gt;Submits the surrounding form.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A &lt;code&gt;&amp;lt;div role="button"&amp;gt;&lt;/code&gt; gets none of these. To match a native button, a developer has to add &lt;code&gt;tabindex="0"&lt;/code&gt;, a &lt;code&gt;keydown&lt;/code&gt; handler for both keys, logic to stop Space from scrolling the page, and their own disabled state handling. Each of those is a place for a bug to hide, and a mouse-driven test won’t notice any of them, so if the accessibility-friendly byRole locator is passing, it can be easy to miss the risk.&lt;/p&gt;

&lt;h3&gt;
  
  
  What people experience when the native element doesn’t match the role
&lt;/h3&gt;

&lt;p&gt;Here’s what that gap looks like for real users of a partially-implemented button, for example a &lt;code&gt;&amp;lt;div role="button"&amp;gt;&lt;/code&gt; that only has a click handler:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Keyboard-only users&lt;/strong&gt; press Tab and skip right past the control. Without &lt;code&gt;tabindex&lt;/code&gt;, the browser never puts it in the tab order, so there’s no way to reach it. People who can’t use a mouse, including many people with motor disabilities, are stuck.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Screen reader users&lt;/strong&gt; may reach the content and hear “Save, button” and do what anyone would: press Enter or Space. Depending on their screen reader and how they’re navigating, nothing may happen, because the page only listens for mouse clicks. They will not know if they successfully operated the button.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anyone using Space to activate it&lt;/strong&gt; may watch the page scroll down instead, because Space scrolls the page unless the developer prevents it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Users who encounter a “disabled” state&lt;/strong&gt; may still be able to activate it. Setting &lt;code&gt;aria-disabled="true"&lt;/code&gt; describes a state to screen readers, but doesn’t block clicks or key presses the way the native &lt;code&gt;disabled&lt;/code&gt; attribute does, allowing for unintentional activation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this shows up in a test that clicks the element with a simulated mouse event. That’s exactly why we made native matching the default. If someone replaces a &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; with a &lt;code&gt;&amp;lt;div role="button"&amp;gt;&lt;/code&gt;, your existing &lt;code&gt;cy.getByRole('button', ...)&lt;/code&gt; test fails and points you at the change, and you can decide whether it was intentional and if more tests are needed.&lt;/p&gt;

&lt;p&gt;The broader principle comes from web accessibility: The &lt;a href="https://www.w3.org/TR/using-aria/#rule1" rel="noopener noreferrer"&gt;first rule of ARIA&lt;/a&gt; advises using a native HTML element whenever one already has the semantics and behavior you need, instead of rebuilding it with an ARIA &lt;code&gt;role&lt;/code&gt; attribute. The &lt;a href="https://www.w3.org/WAI/ARIA/apg/patterns/button/" rel="noopener noreferrer"&gt;W3C ARIA Authoring Practices Guide button pattern&lt;/a&gt; shows how much work a custom button takes to get right.&lt;/p&gt;

&lt;h3&gt;
  
  
  When to pass &lt;code&gt;native: false&lt;/code&gt; to &lt;code&gt;cy.getByRole()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;In some cases a &lt;code&gt;role&lt;/code&gt; attribute is the right call, or at least not yours to change. Pass &lt;code&gt;native: false&lt;/code&gt; to also match elements that get their role from a &lt;code&gt;role&lt;/code&gt; attribute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// A dialog rendered by a component library you don't control&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dialog&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Delete project&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;native&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;be.visible&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few common patterns need &lt;code&gt;native: false&lt;/code&gt; even when they follow the ARIA authoring practices, because HTML has a different native element for the role:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An autocomplete &lt;code&gt;&amp;lt;input role="combobox"&amp;gt;&lt;/code&gt;. The native combobox is a &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; or an &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; with a &lt;code&gt;list&lt;/code&gt; attribute.&lt;/li&gt;
&lt;li&gt;An icon &lt;code&gt;&amp;lt;svg role="img"&amp;gt;&lt;/code&gt;. The native image is an &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For widget roles that HTML has no element for, such as &lt;code&gt;tab&lt;/code&gt;, &lt;code&gt;menuitem&lt;/code&gt;, or &lt;code&gt;switch&lt;/code&gt;, a &lt;code&gt;role&lt;/code&gt; attribute is the only option, so &lt;code&gt;cy.getByRole()&lt;/code&gt; always matches it. You don’t need &lt;code&gt;native: false&lt;/code&gt; for those.&lt;/p&gt;

&lt;p&gt;If your app is built on a component library that re-implements native elements with &lt;code&gt;role&lt;/code&gt; attributes, you don’t have to pass &lt;code&gt;native: false&lt;/code&gt; on every call. You can &lt;a href="https://on.cypress.io/getbyrole?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_content=overwrite%20the%20query&amp;amp;utm_campaign=getbyrole-launch-oct-2026#Change-the-native-default-for-your-project" rel="noopener noreferrer"&gt;overwrite the query&lt;/a&gt; in your support file to make &lt;code&gt;native: false&lt;/code&gt; your project’s default. A test can still pass &lt;code&gt;native: true&lt;/code&gt; to require a native element.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// cypress/support/commands.js&lt;/span&gt;
&lt;span class="nx"&gt;Cypress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;overwriteQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;getByRole&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;originalFn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;originalFn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;native&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Moving off of Cypress Testing Library
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Do you need to migrate &lt;code&gt;cy.findByRole()&lt;/code&gt; to &lt;code&gt;cy.getByRole()&lt;/code&gt; right now?
&lt;/h3&gt;

&lt;p&gt;No. Cypress Testing Library still works with Cypress 16.2.0. It registers only &lt;code&gt;findBy*&lt;/code&gt; queries, so it doesn’t conflict with &lt;code&gt;cy.getByRole()&lt;/code&gt;, and you can migrate one spec at a time.&lt;/p&gt;

&lt;p&gt;Most calls translate by renaming the query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before: Cypress Testing Library&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Place order&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findAllByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;listitem&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;have.length&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tab&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;selected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;have.text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Shipping&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// After: cy.getByRole()&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Place order&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;listitem&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;have.length&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tab&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[aria-selected="true"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;have.text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Shipping&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are a few differences that could cause a one-to-one replacement to fail:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Native elements by default.&lt;/strong&gt; A test that found a &lt;code&gt;&amp;lt;div role="button"&amp;gt;&lt;/code&gt; with &lt;code&gt;findByRole()&lt;/code&gt; fails after migrating. Decide for each one whether the app should use the native element, or the test should pass &lt;code&gt;native: false&lt;/code&gt;. I’d encourage you to treat this list as an accessibility review, not a chore. To keep your current behavior while you migrate, overwrite the query to default to &lt;code&gt;native: false&lt;/code&gt;, as shown earlier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every match, not one.&lt;/strong&gt; &lt;code&gt;cy.getByRole()&lt;/code&gt; yields every match instead of failing when more than one element matches. Narrow the &lt;code&gt;name&lt;/code&gt; or chain &lt;code&gt;.first()&lt;/code&gt; to act on one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State options become &lt;code&gt;.filter()&lt;/code&gt;.&lt;/strong&gt; The &lt;code&gt;checked&lt;/code&gt;, &lt;code&gt;selected&lt;/code&gt;, &lt;code&gt;pressed&lt;/code&gt;, &lt;code&gt;expanded&lt;/code&gt;, &lt;code&gt;current&lt;/code&gt;, &lt;code&gt;busy&lt;/code&gt;, &lt;code&gt;level&lt;/code&gt;, and &lt;code&gt;value&lt;/code&gt; options move to a chained &lt;code&gt;.filter()&lt;/code&gt;. If you pass one of these options, the error tells you which filter to use.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;a href="https://on.cypress.io/getbyrole?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_content=migration%20guide&amp;amp;utm_campaign=getbyrole-launch-oct-2026#How-to-migrate-from-Testing-Library-findByRole-to-cygetByRole" rel="noopener noreferrer"&gt;migration guide in the &lt;code&gt;cy.getByRole()&lt;/code&gt; docs&lt;/a&gt; has before-and-after examples and a copyable prompt you can give an AI agent to do the migration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom command failing with &lt;code&gt;cy.getByRole()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;If your project already defines its own &lt;code&gt;getByRole&lt;/code&gt; with &lt;code&gt;Cypress.Commands.add()&lt;/code&gt; or &lt;code&gt;Cypress.Commands.addQuery()&lt;/code&gt;, that call throws an error in Cypress 16.2.0, because &lt;code&gt;getByRole&lt;/code&gt; is now a built-in query. Rename your command, or remove it and use the built-in one.&lt;/p&gt;

&lt;h2&gt;
  
  
  How &lt;code&gt;cy.getByRole()&lt;/code&gt; and Cypress Accessibility fit together
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;cy.getByRole()&lt;/code&gt; only checks the elements you locate in a test. &lt;a href="https://www.cypress.io/accessibility?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_content=Cypress%20Accessibility&amp;amp;utm_campaign=getbyrole-launch-oct-2026" rel="noopener noreferrer"&gt;Cypress Accessibility&lt;/a&gt; widens the view. It runs axe-core checks against every unique page state your recorded tests reach, with no changes to your test code, and reports the results in Cypress Cloud. That catches problems like missing labels, low color contrast, and invalid ARIA on parts of the page no test explicitly asserts.&lt;/p&gt;

&lt;p&gt;Put together, it looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;cy.getByRole()&lt;/code&gt;&lt;/strong&gt; helps ensure the elements your tests use or assert about have a clear accessibility purpose and name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cypress Accessibility&lt;/strong&gt; audits every state your tests reach, including everything they don’t interact with or make assertions about.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;cy.getByRole()&lt;/code&gt; is built into the Cypress app and works without Cypress Cloud. Cypress Accessibility runs with Cypress Cloud. You can adopt either one on its own, and each makes the other more useful, because you will get a more detailed axe-core report if your elements have recognizable roles with associated accessibility expectations, and don’t appear to be generic content.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do next
&lt;/h2&gt;

&lt;p&gt;If you’re starting a new test, reach for &lt;code&gt;cy.getByRole()&lt;/code&gt; first. If you already use Cypress Testing Library, there’s no rush, but migrating gives you a supported query and an accessibility check on every control your tests touch. And if a migrated test fails because of the native default, take that seriously. It may be the first time your test suite noticed something a keyboard user has been running into all along.&lt;/p&gt;

&lt;p&gt;Upgrade to Cypress 16.2.0, then read the &lt;a href="https://on.cypress.io/getbyrole?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_content=cy.getByRole%28%29%20documentation&amp;amp;utm_campaign=getbyrole-launch-oct-2026" rel="noopener noreferrer"&gt;&lt;code&gt;cy.getByRole()&lt;/code&gt; documentation&lt;/a&gt; to get started. We’d love to hear how it goes, and which Testing Library queries you’d like to see built in next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is &lt;code&gt;cy.getByRole()&lt;/code&gt; in Cypress?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;cy.getByRole()&lt;/code&gt; is a built-in Cypress query that finds DOM elements by their role and, optionally, their accessible name. The role is what a screen reader announces an element as, such as a button or a link. It yields every matching element and retries until the elements exist and chained assertions pass.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which version of Cypress includes &lt;code&gt;cy.getByRole()&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;Cypress 16.2.0 and later.&lt;/p&gt;

&lt;h3&gt;
  
  
  What about test IDs?
&lt;/h3&gt;

&lt;p&gt;Test IDs like &lt;code&gt;data-cy&lt;/code&gt; and &lt;code&gt;data-test&lt;/code&gt; specifically avoid depending on anything about the element, which means they have no opinion about accessibility, HTML, or content. They are useful for maximum stability where those things don’t matter, or for separating different instances of otherwise-similar elements.&lt;/p&gt;

&lt;p&gt;When working with &lt;code&gt;cy.getByRole()&lt;/code&gt;, you may want to chain &lt;code&gt;.filter('[data-cy="your-test-id"]')&lt;/code&gt; to ensure a specific element is matched if there is a chance that more than one might be on the same page.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I still need &lt;code&gt;@testing-library/cypress&lt;/code&gt; to find elements by role?
&lt;/h3&gt;

&lt;p&gt;No. &lt;code&gt;cy.getByRole()&lt;/code&gt; replaces &lt;code&gt;findByRole()&lt;/code&gt; and &lt;code&gt;findAllByRole()&lt;/code&gt; without a plugin. If you use other Testing Library queries, such as &lt;code&gt;findByLabelText()&lt;/code&gt;, keep the package for now. The two can run side by side.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why doesn’t &lt;code&gt;cy.getByRole()&lt;/code&gt; find my &lt;code&gt;&amp;lt;div role="button"&amp;gt;&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;For roles that have a native HTML element, &lt;code&gt;cy.getByRole()&lt;/code&gt; matches only the native element by default. Pass &lt;code&gt;{ native: false }&lt;/code&gt; to also match elements that get their role from a &lt;code&gt;role&lt;/code&gt; attribute, or switch the element to a native &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;, which means keyboard and screen reader users get built-in button behavior and you will not need to pass an option. To make &lt;code&gt;native: false&lt;/code&gt; your project’s default, overwrite the query with &lt;code&gt;Cypress.Commands.overwriteQuery()&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why is a native &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; more accessible than &lt;code&gt;role="button"&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;role="button"&lt;/code&gt; only changes how assistive technology announces the element. A native &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; is also in the keyboard tab order, activates with Enter and Space, supports the &lt;code&gt;disabled&lt;/code&gt; attribute, and submits forms. A custom button has to rebuild each of these by hand, and missing any of them can block keyboard and screen reader users.&lt;/p&gt;

&lt;h3&gt;
  
  
  How is &lt;code&gt;cy.getByRole()&lt;/code&gt; different from Testing Library’s &lt;code&gt;findByRole()&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;cy.getByRole()&lt;/code&gt; matches native elements by default, yields every match instead of failing on multiple matches, and uses a chained &lt;code&gt;.filter()&lt;/code&gt; for states such as &lt;code&gt;checked&lt;/code&gt; and &lt;code&gt;selected&lt;/code&gt; instead of options.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I find a checked checkbox or a level 2 heading with &lt;code&gt;cy.getByRole()&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;Chain &lt;code&gt;.filter()&lt;/code&gt;. For example, &lt;code&gt;cy.getByRole('checkbox').filter(':checked')&lt;/code&gt; or &lt;code&gt;cy.getByRole('heading').filter('[aria-level="2"], h2:not([aria-level])')&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does &lt;code&gt;cy.getByRole()&lt;/code&gt; work inside shadow DOM?
&lt;/h3&gt;

&lt;p&gt;Yes. Pass &lt;code&gt;includeShadowDom: true&lt;/code&gt;, set the &lt;code&gt;includeShadowDom&lt;/code&gt; configuration option, or chain &lt;code&gt;cy.getByRole()&lt;/code&gt; off &lt;code&gt;.shadow()&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I use &lt;code&gt;cy.getByRole()&lt;/code&gt; or &lt;code&gt;cy.prompt&lt;/code&gt; self-healing to keep tests stable?
&lt;/h3&gt;

&lt;p&gt;They work well together. &lt;code&gt;cy.getByRole()&lt;/code&gt; ties a test to roles and accessible names, which rarely change by accident, and fails when a control loses native behavior. &lt;code&gt;cy.prompt&lt;/code&gt; heals steps when an element changes and marks each heal so you can review it. Preferring &lt;code&gt;role&lt;/code&gt; and &lt;code&gt;aria-label&lt;/code&gt; in your &lt;code&gt;ElementSelector&lt;/code&gt; priority helps &lt;code&gt;cy.prompt&lt;/code&gt; heal less often.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Cypress Accessibility replace &lt;code&gt;cy.getByRole()&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;No. Cypress Accessibility audits every page state your recorded tests reach, including elements no test touches. &lt;code&gt;cy.getByRole()&lt;/code&gt; is an explicit technique to conveniently locate elements by their accessible role and get a built-in preference for native browser implementations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Will Cypress add other Testing Library queries?
&lt;/h3&gt;

&lt;p&gt;Yes, that’s our plan. &lt;code&gt;cy.getByRole()&lt;/code&gt; is the first, and we’ll announce others as they’re ready.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>a11y</category>
      <category>cypress</category>
      <category>testinglibrary</category>
    </item>
    <item>
      <title>Skip tests that pass on the current build: Re-run optimization in Cypress Cloud</title>
      <dc:creator>Mark Noonan</dc:creator>
      <pubDate>Thu, 01 Oct 2026 16:35:34 +0000</pubDate>
      <link>https://dev.to/cypress/skip-tests-that-pass-on-the-current-build-re-run-optimization-in-cypress-cloud-4ef2</link>
      <guid>https://dev.to/cypress/skip-tests-that-pass-on-the-current-build-re-run-optimization-in-cypress-cloud-4ef2</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flhq8o8mz99p2c3d15ojn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flhq8o8mz99p2c3d15ojn.png" alt=" " width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When Cypress detects a CI rerun, it can now skip the work that already passed and run only what failed, so you get to green faster, burn fewer CI minutes, and record fewer billable test results. Re-runs with few failures can &lt;strong&gt;skip 99% or more&lt;/strong&gt; of the tests that would have re-executed. It works out-of-the-box with common CI providers, and ships with overrides to customize the re-run behavior to your specific needs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://on.cypress.io/rerun-optimization?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=rerun-optimization&amp;amp;utm_content=rerun-optimization" rel="noopener noreferrer"&gt;&lt;u&gt;Re-run Optimization&lt;/u&gt;&lt;/a&gt; is available for all Business plans and higher, and in &lt;a href="https://on.cypress.io/free-trial?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=rerun-optimization&amp;amp;utm_content=free%20trials" rel="noopener noreferrer"&gt;&lt;u&gt;free trials&lt;/u&gt;&lt;/a&gt;, as a part of Smart Orchestration. &lt;/p&gt;

&lt;p&gt;This post will walk through  the highlights for saving time and getting a green build faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why do teams re-execute failed Cypress runs?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;We interviewed current Cypress Cloud customers to get this feature right. Most told us they already use tools like &lt;strong&gt;flake detection&lt;/strong&gt; to stay on top of things they can control from the testing side. Generally their &lt;strong&gt;test design&lt;/strong&gt; is not causing flakiness, and in-run, often flake represents real race-condition bugs in the application logic.&lt;/p&gt;

&lt;p&gt;Re-runs address instability that’s &lt;strong&gt;not&lt;/strong&gt; at the application layer or the test design layer. A re-run &lt;strong&gt;adds information&lt;/strong&gt; to help you understand the full end-to-end picture, especially if your testing environment itself is not fully stable, which means a single failure could have multiple causes. The re-run helps rule some of those causes out cheaply, before you or your AI systems work on debugging.&lt;/p&gt;

&lt;p&gt;For busy teams with large test suites on complicated applications, keeping the test environment stable isn’t always practical, or even necessarily desired. When systems integrate with live devices or depend on third parties, stability itself is part of what a full end-to-end test run is measuring. &lt;/p&gt;

&lt;p&gt;Whatever the underlying reason for a failure, if something is blocking a release or an important merge, your priority in that moment is to understand the issue and get unblocked as fast as possible. At those times, re-run optimization makes your re-executions cheaper, faster, and less painful. &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How much time is saved with Re-run optimization in Cypress Cloud?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The most time savings come from test-level re-runs, which allows Cypress to cherry-pick individual tests that need to re-run. Based on real production runs from customers who have adopted this technique, the median re-run skips &lt;strong&gt;92%&lt;/strong&gt; of the original run’s tests, and finishes &lt;strong&gt;9.3 minutes faster&lt;/strong&gt; than the original run. &lt;/p&gt;

&lt;p&gt;The maximum savings we have seen on re-runs are when there are large test suites with small numbers of failures, where &lt;strong&gt;99.8% of the tests that would have re-executed were skipped&lt;/strong&gt; , and &lt;strong&gt;36 minutes were saved&lt;/strong&gt; for the test run. This is the pattern you may see if you run long nightly test suites or pre-release tests of live environments that might have deployments or other disruptions during the test execution.&lt;/p&gt;

&lt;p&gt;Re-run optimization makes it much faster and more practical to get that extra confidence and confirmation of what’s going on when a build has failed, and avoids the risk of instability leading to the original failures passing on the new run, only for something else to start failing.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Choose spec-level or test-level reruns in Cypress Cloud&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;You set the granularity once in the Smart Orchestration section of your organization or project settings, and projects can inherit the org default or override it.&lt;/p&gt;

&lt;p&gt;When set to run only failed &lt;strong&gt;specs&lt;/strong&gt; , Cypress re-runs every spec that contained a failure on the last run, including the tests in that spec that passed, which is the safer choice if your tests share state inside a spec.&lt;/p&gt;

&lt;p&gt;When set to run only failed &lt;strong&gt;tests&lt;/strong&gt; , Cypress re-runs only the tests that didn't pass, plus any that are new or changed since the last run. It requires Cypress 15.21.0 or later (older versions fall back to failed specs). If a test would pass on its own with .only, it will work in a re-run. If not, prefer spec-based retries for your project.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Controlling the re-run behavior in Cypress Cloud&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;If you re-run before Cloud marks the previous run complete, Cypress plays it safe and runs everything. Keeping your &lt;a href="https://on.cypress.io/parallelization?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=rerun-optimization&amp;amp;utm_content=run-completion-delay" rel="noopener noreferrer"&gt;run completion delay&lt;/a&gt; short can reduce the window where your CI is ready to rerun a job but Cypress Cloud is still open. But in many cases, you may want to manage the previous run’s status explicitly. Our recently released &lt;a href="https://docs.cypress.io/cloud/features/smart-orchestration/run-cancellation-api?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=rerun-optimization&amp;amp;utm_content=run-cancellation-api" rel="noopener noreferrer"&gt;&lt;u&gt;Run Cancellation API&lt;/u&gt;&lt;/a&gt;, and &lt;a href="https://on.cypress.io/parallelization?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=rerun-optimization&amp;amp;utm_content=run-completion-api" rel="noopener noreferrer"&gt;&lt;u&gt;Run Completion API&lt;/u&gt;&lt;/a&gt; give you programmatic control over the run state. Combined with Spec Prioritization and &lt;a href="https://on.cypress.io/run-cancellation?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=rerun-optimization&amp;amp;utm_content=rerun-optimization" rel="noopener noreferrer"&gt;&lt;u&gt;Auto Cancellation&lt;/u&gt;&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Turn it on&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Re-run optimization is part of Smart Orchestration on Business and Enterprise plans, and it's available during a free trial. Owners and Admins can enable it in organization or project settings. The &lt;a href="https://on.cypress.io/rerun-optimization?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=rerun-optimization&amp;amp;utm_content=rerun-optimization-docs" rel="noopener noreferrer"&gt;Re-run optimization docs&lt;/a&gt; cover setup for each CI provider.&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>testing</category>
      <category>ci</category>
      <category>rerun</category>
    </item>
    <item>
      <title>Build custom workflows with Cypress Cloud Webhooks</title>
      <dc:creator>Mark Noonan</dc:creator>
      <pubDate>Tue, 08 Sep 2026 17:17:31 +0000</pubDate>
      <link>https://dev.to/cypress/build-custom-workflows-with-cypress-cloud-webhooks-4p6i</link>
      <guid>https://dev.to/cypress/build-custom-workflows-with-cypress-cloud-webhooks-4p6i</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb504ghmymlrvastsr6gp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb504ghmymlrvastsr6gp.png" alt="Push Results with Webhooks" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://on.cypress.io/webhooks?utm_source=devto" rel="noopener noreferrer"&gt;Webhooks&lt;/a&gt; are a new addition to Cypress Cloud, supporting more flexible automation workflows beyond what is built into the platform itself.&lt;/p&gt;

&lt;p&gt;Learn why we've added them and how to get started using them in your project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integration support in Cypress Cloud
&lt;/h2&gt;

&lt;p&gt;Cypress Cloud supports a wide range of integrations out of the box. If you need a &lt;a href="https://on.cypress.io/github?utm_source=devto&amp;amp;utm_medium=cypress-org&amp;amp;utm_content=cypress-cloud-webhooks#Status-checks" rel="noopener noreferrer"&gt;GitHub status check&lt;/a&gt; for every group of tests in your test run, we've got you covered. &lt;a href="https://docs.cypress.io/cloud/integrations/slack?utm_source=devto&amp;amp;utm_medium=cypress-org&amp;amp;utm_content=cypress-cloud-webhooks" rel="noopener noreferrer"&gt;Slack&lt;/a&gt; and &lt;a href="https://on.cypress.io/microsoft-teams?utm_source=devto&amp;amp;utm_medium=cypress.io&amp;amp;utm_content=cypress-cloud-webhooks" rel="noopener noreferrer"&gt;Teams&lt;/a&gt; messages when a run fails? That's there, along with &lt;a href="https://on.cypress.io/recorded-runs?utm_source=devto&amp;amp;utm_medium=cypress-org&amp;amp;utm_content=cypress-cloud-webhooks#Notifications" rel="noopener noreferrer"&gt;desktop notifications&lt;/a&gt;, &lt;a href="https://on.cypress.io/jira?utm_source=devto&amp;amp;utm_medium=cypress-org&amp;amp;utm_content=cypress-cloud-webhooks" rel="noopener noreferrer"&gt;Jira ticket creation&lt;/a&gt;, &lt;a href="https://on.cypress.io/enterprise-reporting?utm_source=devto&amp;amp;utm_medium=cypress-org&amp;amp;utm_content=cypress-cloud-webhooks" rel="noopener noreferrer"&gt;high-level trend reporting APIs&lt;/a&gt;, and more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why webhooks?
&lt;/h2&gt;

&lt;p&gt;Using webhooks is a great solution for &lt;strong&gt;custom integrations&lt;/strong&gt; where you want Cypress Cloud to &lt;strong&gt;push&lt;/strong&gt; run results and other data into another system.&lt;/p&gt;

&lt;p&gt;A webhook can be the starting point to kick off workflows based on inspecting the outcome of a run, or a way to customize the details you will include in a Slack or Teams message, or send messages to destinations Cypress doesn't support, such as Google Chat.&lt;/p&gt;

&lt;p&gt;Webhooks also help you leverage our other recently-released Cypress App and Cypress Cloud updates in ways that work for you. Rather than invoke the Cypress &lt;a href="https://on.cypress.io/cloud-cli?utm_source=devto&amp;amp;utm_medium=cypress-org&amp;amp;utm_content=cypress-cloud-webhooks" rel="noopener noreferrer"&gt;Cloud CLI&lt;/a&gt; or &lt;a href="https://on.cypress.io/cloud-mcp?utm_source=devto&amp;amp;utm_medium=cypress-org&amp;amp;utm_content=cypress-cloud-webhooks" rel="noopener noreferrer"&gt;MCP&lt;/a&gt; at the end of every run, you can set up a URL for Cypress to deliver run results, along with the tags, branch, and commit info that helps you decided if further action is needed.&lt;/p&gt;

&lt;p&gt;This lets you be more efficient with AI token usage and reduce rate-limited request you make for routine run analysis. Filter to just the runs you are most interested in based on the webhook payload, then you can dive deeper with scripts using the Cloud AI tools to get Test Replay snapshot data and command logs, or hand off to your LLM using the live Cypress App running locally with &lt;a href="https://on.cypress.io/cypress-tap?utm_source=devto&amp;amp;utm_medium=cypress-org&amp;amp;utm_content=cypress-cloud-webhooks" rel="noopener noreferrer"&gt;&lt;code&gt;cypress tap&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use a webhook
&lt;/h2&gt;

&lt;p&gt;In some cases, existing APIs may fully meet your needs, particularly if your runs are small and the existing integrations are well-suited to the content you already want in your messaging platforms.&lt;/p&gt;

&lt;p&gt;In other cases, webhooks remove multiple layers of handoffs from your CI environment to other services, removing glue code or even opening up new possibilities, like optionally triggering re-runs of your failed tests in CI based on the tags or commit message you send to Cypress Cloud.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get started
&lt;/h2&gt;

&lt;p&gt;Available on all paid Cypress Cloud plans, today webhooks can be attached to a &lt;code&gt;run.completed&lt;/code&gt; event, which fires when a run has resolved to a finalized state after all results have been recorded.&lt;/p&gt;

&lt;p&gt;Admins and owners can add webhooks in your project settings page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyvglgxe25qczotxevke6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyvglgxe25qczotxevke6.png" alt="webhook creation area of the settings page" width="800" height="107"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; the recently-released &lt;a href="https://on.cypress.io/parallelization?utm_source=devto&amp;amp;utm_medium=cypress-org&amp;amp;utm_content=cypress-cloud-webhooks#Run-Completion-API" rel="noopener noreferrer"&gt;Run Completion API&lt;/a&gt; may be useful if you have parallel or grouped runs, and you want your webhooks to fire as soon as all expected groups have finished recording.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;We'd love to know how you are using this and what types of events or payloads would be useful for your specific goals. We will continue developing and expanding in this area based on your feedback.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://on.cypress.iowebhooks?utm_source=devto&amp;amp;utm_medium=cypress-org&amp;amp;utm_content=cypress-cloud-webhooks" rel="noopener noreferrer"&gt;Read the webhooks docs&lt;/a&gt; to get your first webhook configured. Don't have a Cypress Cloud account yet? &lt;a href="https://cloud.cypress.io/signup?utm_source=devto&amp;amp;utm_medium=cypress-org&amp;amp;utm_content=cypress-cloud-webhooks" rel="noopener noreferrer"&gt;Start your free trial&lt;/a&gt; and try it on your own project.&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>webhooks</category>
      <category>slack</category>
      <category>jira</category>
    </item>
    <item>
      <title>cypress tap: Closing the Loop Between Your Agent and Your Suite</title>
      <dc:creator>David Ingraham</dc:creator>
      <pubDate>Fri, 28 Aug 2026 19:03:12 +0000</pubDate>
      <link>https://dev.to/cypress/cypress-tap-closing-the-loop-between-your-agent-and-your-suite-2m1e</link>
      <guid>https://dev.to/cypress/cypress-tap-closing-the-loop-between-your-agent-and-your-suite-2m1e</guid>
      <description>&lt;p&gt;&lt;em&gt;Your agent can write the test and run the test. Until now, it couldn't see the page.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Test failures will happen. Flake will happen. Writing new tests against a product deadline will happen. We've all been there and will be there again in the future.&lt;/p&gt;

&lt;p&gt;Software development is predictable that way, and completely fluid at the same time, which is what makes every day a slightly different problem than the one before it.&lt;/p&gt;

&lt;p&gt;As we've all started folding AI into that work, we've learned it's extremely good at producing a lot of code, fast. And one rule has held up better than any other: the better your inputs and the more context the tool has, the better the output. That's the whole reason we encode conventions as Skills, point agents at tickets through MCP, and hand them our patterns instead of hoping they guess right.&lt;/p&gt;

&lt;p&gt;But there's a ceiling, and it shows up the moment the agent has to work against a running app.&lt;/p&gt;

&lt;p&gt;When it's debugging a broken test or writing a new one, it can't see the DOM. It sees whatever I paste in. No matter how good the Skills are or how well-structured the repo is, the agent is reasoning about a page it has never actually looked at.&lt;/p&gt;

&lt;p&gt;That's the gap, and it sits in the middle of a loop we all run every day:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The agent writes the test.&lt;/li&gt;
&lt;li&gt;The suite runs it.&lt;/li&gt;
&lt;li&gt;Somebody reads what happened.&lt;/li&gt;
&lt;li&gt;The agent revises.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Steps 1, 2, and 4 have been handled for a while now.&lt;/p&gt;

&lt;p&gt;Step 3 was me, until now.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Read Interface, Not a Writer
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.cypress.io/app/references/command-line#cypress-tap" rel="noopener noreferrer"&gt;cypress tap&lt;/a&gt; is a set of CLI commands that read and drive an open-mode Cypress session from your terminal.&lt;/p&gt;

&lt;p&gt;Setup is two terminals. &lt;code&gt;cypress open&lt;/code&gt; in one, &lt;code&gt;cypress tap&lt;/code&gt; from the same project root in the other. No config, no server, no MCP.&lt;/p&gt;

&lt;p&gt;The subcommands split into three jobs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Find and run:&lt;/strong&gt; &lt;code&gt;sessions&lt;/code&gt;, &lt;code&gt;specs&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;, &lt;code&gt;run&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read results:&lt;/strong&gt; &lt;code&gt;reporter&lt;/code&gt;, &lt;code&gt;command&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interrogate the page:&lt;/strong&gt; &lt;code&gt;pin&lt;/code&gt;, &lt;code&gt;dom&lt;/code&gt;, &lt;code&gt;aria&lt;/code&gt;, &lt;code&gt;inspect&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything prints readable output by default and machine-readable JSON with &lt;code&gt;--json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The thing worth noticing is what isn't in that list.&lt;/p&gt;

&lt;p&gt;There's no &lt;code&gt;tap write&lt;/code&gt;. No &lt;code&gt;tap fix&lt;/code&gt;. No &lt;code&gt;tap generate&lt;/code&gt;. It takes the context the Cypress app was already showing you on screen and puts it somewhere a terminal, or an agent working in one, can actually reach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Breaking a Test Purposefully
&lt;/h2&gt;

&lt;p&gt;I have a fake pet adoption site I use for testing out new automation features and for creating workshops against. This exciting new feature was one I hooked up to that repo.&lt;/p&gt;

&lt;p&gt;To demo what &lt;code&gt;tap&lt;/code&gt; actually does, I needed something red to point at, so I broke it deliberately: I renamed an app-side &lt;code&gt;data-testid&lt;/code&gt; from &lt;code&gt;favorite-button-*&lt;/code&gt; to &lt;code&gt;favorite-toggle-*&lt;/code&gt; and left the spec untouched.&lt;/p&gt;

&lt;p&gt;Deliberately simple, too. The point here isn't the puzzle in the break, it's how the agent utilizes &lt;code&gt;cypress tap&lt;/code&gt; to get to the solution and how this new approach can be scaled to more complicated, real-life scenarios.&lt;/p&gt;

&lt;p&gt;Here's the whole diagnosis, terminal only.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Is there a session I can reach?&lt;/strong&gt; &lt;code&gt;cypress tap&lt;/code&gt; drives an already-open Cypress, so first prove one exists using &lt;code&gt;tap sessions&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npx cypress tap sessions
SESSIONS &lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;
  PID    PROJECT                                         TYPE  BROWSER
  29426  /Users/davidingraham/repos/pet-adoption-portal  e2e   Chrome
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. What can it run?&lt;/strong&gt; Using &lt;code&gt;tap specs&lt;/code&gt; it identifies the open spec from the runner.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npx cypress tap specs
SPECS &lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;
  cypress/e2e/favorites.cy.js  9 minutes ago
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Trigger the failure.&lt;/strong&gt; Next it runs the open spec so it can capture the current output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npx cypress tap run cypress/e2e/favorites.cy.js
▶ cypress/e2e/favorites.cy.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. The failure as text.&lt;/strong&gt; The bare &lt;code&gt;reporter&lt;/code&gt; command returns the suite summary. As you can see, I only ran one test and I knew it was going to fail. I imagine when you are actively working on building multiple tests at once, the &lt;code&gt;reporter&lt;/code&gt; command will be a great translator between what is happening in the runner and what the agent knows as changes are made.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npx cypress tap reporter
cypress/e2e/favorites.cy.js  &lt;span class="o"&gt;(&lt;/span&gt;started at 7:21:21 PM&lt;span class="o"&gt;)&lt;/span&gt;
✓ &lt;span class="nt"&gt;--&lt;/span&gt;  ✖ 1  ○ &lt;span class="nt"&gt;--&lt;/span&gt;  00:04

Favorites Feature
   r3  ✖ adds a pet to favorites  4.2s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. Expand the failing test.&lt;/strong&gt; The agent can pass in additional flags to expand on what happened at each step, all the way up until the captured failure. Note the test id, &lt;code&gt;r3&lt;/code&gt;, which every command from here on scopes to.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npx cypress tap reporter &lt;span class="nt"&gt;--test-id&lt;/span&gt; r3
✖ Favorites Feature &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; adds a pet to favorites  failed

ROUTES &lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;
  METHOD  MATCHER             STUBBED  ALIAS        &lt;span class="c"&gt;#&lt;/span&gt;
  POST    &lt;span class="k"&gt;**&lt;/span&gt;/api/favorites/&lt;span class="k"&gt;*&lt;/span&gt;  no       addFavorite  -

BEFORE EACH · h1
   1  request  ● POST 200 http://localhost:3000/api/auth/login
   2  request  ● GET 200 http://localhost:3000/api/favorites
   3  visit    /pets

TEST BODY · r3
   1  get      &lt;span class="o"&gt;[&lt;/span&gt;data-testid&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"pets-grid"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
  e1    &lt;span class="o"&gt;(&lt;/span&gt;xhr&lt;span class="o"&gt;)&lt;/span&gt; ● GET 200 http://localhost:3000/api/auth/me
  e2    &lt;span class="o"&gt;(&lt;/span&gt;xhr&lt;span class="o"&gt;)&lt;/span&gt; ● GET 200 http://localhost:3000/api/auth/me
  e3    &lt;span class="o"&gt;(&lt;/span&gt;xhr&lt;span class="o"&gt;)&lt;/span&gt; ● GET 200 http://localhost:3000/api/pets?species&lt;span class="o"&gt;=&lt;/span&gt;&amp;amp;age&lt;span class="o"&gt;=&lt;/span&gt;&amp;amp;size&lt;span class="o"&gt;=&lt;/span&gt;&amp;amp;gender&lt;span class="o"&gt;=&lt;/span&gt;&amp;amp;search&lt;span class="o"&gt;=&lt;/span&gt;&amp;amp;sortBy&lt;span class="o"&gt;=&lt;/span&gt;dateAdded&amp;amp;sortOrder&lt;span class="o"&gt;=&lt;/span&gt;desc&amp;amp;page&lt;span class="o"&gt;=&lt;/span&gt;1
  e4    &lt;span class="o"&gt;(&lt;/span&gt;xhr&lt;span class="o"&gt;)&lt;/span&gt; ● GET 200 http://localhost:3000/api/favorites
   2  &lt;span class="nt"&gt;-assert&lt;/span&gt;  expected &amp;lt;div.grid.md:grid-cols-2.gap-6&amp;gt; to be visible
   3  get      &lt;span class="o"&gt;[&lt;/span&gt;data-testid^&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"favorite-button-"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; ✖

✖ AssertionError
  Timed out retrying after 4000ms: Expected to find element: &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;data-testid^&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"favorite-button-"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;, but never found it.

cypress/e2e/favorites.cy.js:29:8
    27 |
    28 |     cy.intercept&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'POST'&lt;/span&gt;, &lt;span class="s1"&gt;'**/api/favorites/*'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;.as&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'addFavorite'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 29 |     cy.get&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'[data-testid^="favorite-button-"]'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;.first&lt;span class="o"&gt;()&lt;/span&gt;.as&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'favoriteButton'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;.click&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       |        ^
    30 |     cy.wait&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'@addFavorite'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;.its&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'response.statusCode'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;.should&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'eq'&lt;/span&gt;, 201&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    31 |
    32 |     cy.get&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'@favoriteButton'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;.should&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'contain'&lt;/span&gt;, &lt;span class="s1"&gt;'❤️'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6. Pull the failing command as data.&lt;/strong&gt; Command 3 is the one that failed, so the agent asks for it directly. This is the Command Log row as structured output rather than something you hover in the UI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npx cypress tap &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;--test-id&lt;/span&gt; r3 &lt;span class="nt"&gt;--command-id&lt;/span&gt; 3
TEST BODY · r3
✖  3  get  &lt;span class="o"&gt;[&lt;/span&gt;data-testid^&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"favorite-button-"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;  failed

CONSOLE PROPS
  Selector  &lt;span class="o"&gt;[&lt;/span&gt;data-testid^&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"favorite-button-"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
  Elements  0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;7. Put the at-failure DOM back on screen.&lt;/strong&gt; &lt;code&gt;pin&lt;/code&gt; pushes that command's snapshot into the live browser frame, so everything that follows is querying the page exactly as it looked when the test broke.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npx cypress tap pin &lt;span class="nt"&gt;--test-id&lt;/span&gt; r3 &lt;span class="nt"&gt;--command-id&lt;/span&gt; 3
⚲ PINNED - &lt;span class="o"&gt;(&lt;/span&gt;1/1&lt;span class="o"&gt;)&lt;/span&gt;
TEST BODY · r3
   3  get  &lt;span class="o"&gt;[&lt;/span&gt;data-testid^&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"favorite-button-"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; ✖
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;8. Ask the page what's actually there.&lt;/strong&gt; Next it can read the real ARIA tree from the DOM, which shows twelve favorite buttons rendered and healthy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npx cypress tap aria
navigation
  &lt;span class="nb"&gt;link&lt;/span&gt;  🐾 PawMatch
  ...
main
  heading  Adopt a Pet
  ...
  image  Scout
  button  🤍
  heading  Scout
  button  View Details →
  &lt;span class="o"&gt;[&lt;/span&gt;...eleven more pet cards...]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;9. Inspect the element itself.&lt;/strong&gt; Finally, &lt;code&gt;tap inspect&lt;/code&gt; returns the actual attributes on that button, which is where the answer is sitting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npx cypress tap inspect &lt;span class="nt"&gt;--selector&lt;/span&gt; &lt;span class="s1"&gt;'.grid button[data-testid]'&lt;/span&gt; &lt;span class="nt"&gt;--at&lt;/span&gt; 0
ATTRIBUTES &lt;span class="o"&gt;(&lt;/span&gt;2&lt;span class="o"&gt;)&lt;/span&gt;
  data-testid  favorite-toggle-18
  class        absolute top-3 right-3 w-10 h-10 bg-white/90 ...

ACCESSIBILITY
  role  button
  name  🤍

BOX
  x 189   y 564   width 40   height 40
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two lines carry the whole diagnosis.&lt;/p&gt;

&lt;p&gt;Step 6 says &lt;code&gt;Elements 0&lt;/code&gt;. Step 9 says &lt;code&gt;favorite-toggle-18&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The button was there the entire time. Visible, clickable, exactly where it should be. What changed was its contract with the test.&lt;/p&gt;

&lt;p&gt;That's a diagnosis I'd previously have assembled by clicking the red row in the Command Log, hovering the snapshot, opening DevTools on the runner, and squinting at the Elements panel to compare attribute names. Here it's text I can paste straight into the fix commit.&lt;/p&gt;

&lt;p&gt;The fix is a one-line selector update, and &lt;code&gt;tap run&lt;/code&gt; plus &lt;code&gt;tap reporter&lt;/code&gt; confirm it green, without ever touching the GUI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npx cypress tap reporter
cypress/e2e/favorites.cy.js  &lt;span class="o"&gt;(&lt;/span&gt;started at 7:22:21 PM&lt;span class="o"&gt;)&lt;/span&gt;
✓ 1  ✖ &lt;span class="nt"&gt;--&lt;/span&gt;  ○ &lt;span class="nt"&gt;--&lt;/span&gt;  310ms

Favorites Feature
   r3  ✓ adds a pet to favorites  302ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Scale This Up
&lt;/h2&gt;

&lt;p&gt;The demo above was a single renamed attribute. Sure I could have solved it by eye, but think about the front ends most of us are actually testing. Virtualized tables that only render what's in view. Nested modals and portals. Shadow DOM. Component libraries that regenerate class names on every build. Accessibility trees that don't match the visual order. Conditional rendering three states deep, where the element exists in one branch and not the other.&lt;/p&gt;

&lt;p&gt;When one of those fails, the hard question is almost never what the error was. The error is right there in the stack trace. The hard question is what was on the page at the moment it broke, and that has always meant a human clicking into the runner and looking.&lt;/p&gt;

&lt;p&gt;That's the context that's been missing. Not smarter models, not better prompts. Just a way to ask the page what it looked like and get an answer back as text. &lt;code&gt;tap aria&lt;/code&gt; and &lt;code&gt;tap inspect&lt;/code&gt; produce that on demand now, which is incredibly powerful and exciting.&lt;/p&gt;

&lt;p&gt;On a real application, it's the difference between an agent that can help with triage and one that can only guess at it. I'm looking forward to seeing how many wasted agent cycles disappear now that it has the product context it always needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Needs to Run
&lt;/h2&gt;

&lt;p&gt;It's a local development tool. &lt;code&gt;tap&lt;/code&gt; reads an open-mode session, and &lt;code&gt;cypress open&lt;/code&gt; has to be running on the same machine. Sessions register themselves in a shared cache, which is how &lt;code&gt;tap sessions&lt;/code&gt; finds them.&lt;/p&gt;

&lt;p&gt;There's no headless story, so you can't drop tap into a CI job or a nightly run. This isn't a knock on tap, it's just not how it is designed. CI has no open-mode session in a pipeline for it to read. If you were hoping to hand failure context to an agent triaging a 2am CI failure, that isn't this. At least not yet.&lt;/p&gt;

&lt;p&gt;It also means someone has to keep the runner open. Close the session and the agent goes blind mid-task. That's a non-issue when you're sitting there working, but worth knowing if you're multi-tasking or handing work off.&lt;/p&gt;

&lt;p&gt;Here's the flip side of that though: because &lt;code&gt;cypress open&lt;/code&gt; is up, you're watching. The browser is right there. The agent kicks off the spec and you see the run happen while the terminal narrates it. That's a very different experience from an agent working headless and reporting back a summary. You get the machine-readable context and your own eyes on the same run, at the same time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Back to the loop.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The agent writes the test.&lt;/li&gt;
&lt;li&gt;The suite runs it.&lt;/li&gt;
&lt;li&gt;Somebody reads what happened.&lt;/li&gt;
&lt;li&gt;The agent revises.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Steps 1, 2, and 4 were never the hard part and those solutions have been around for a while. &lt;code&gt;cypress tap&lt;/code&gt; hands step 3 over. The Command Log as text. The failing command as data. The DOM and the accessibility tree, on demand, exactly as they were when things broke.&lt;/p&gt;

&lt;p&gt;Failures are still going to happen. Flake is still going to happen. The deadlines are definitely not moving. What changes is how fast you find out why, and how much of that you have to type out yourself in the age of AI.&lt;/p&gt;

&lt;p&gt;Point your agent at it on the next red test and see if your triage gets shorter.&lt;/p&gt;

&lt;p&gt;I'm pretty excited about this one and can't wait to fully implement it.&lt;/p&gt;

&lt;p&gt;As always, happy testing.&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>automation</category>
      <category>ai</category>
    </item>
    <item>
      <title>Building Mocks-First Automated Testing with Cypress and AWS DynamoDB</title>
      <dc:creator>Sehani Chathurangi</dc:creator>
      <pubDate>Fri, 14 Aug 2026 10:10:37 +0000</pubDate>
      <link>https://dev.to/cypress/building-mocks-first-automated-testing-with-cypress-and-aws-dynamodb-3ngh</link>
      <guid>https://dev.to/cypress/building-mocks-first-automated-testing-with-cypress-and-aws-dynamodb-3ngh</guid>
      <description>&lt;p&gt;When testing applications that depend on cloud services, manually preparing database records for every test can quickly become time-consuming, repetitive, and error-prone.&lt;/p&gt;

&lt;p&gt;A better approach is to automate test-data creation using reusable fixtures, custom Cypress tasks, and the AWS SDK.&lt;/p&gt;

&lt;p&gt;In this article, I'll walk through how I built a &lt;strong&gt;mocks-first testing approach using Cypress and AWS DynamoDB&lt;/strong&gt;, where each test can create the data it needs, execute independently, and verify the resulting database state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Mocks-First Testing?
&lt;/h2&gt;

&lt;p&gt;A mocks-first approach allows tests to control their required data and dependencies instead of relying on manually prepared records.&lt;/p&gt;

&lt;p&gt;Rather than creating database records before running a test, the test itself prepares the required state during execution.&lt;/p&gt;

&lt;p&gt;This provides several benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Faster test execution&lt;/li&gt;
&lt;li&gt;Repeatable test scenarios&lt;/li&gt;
&lt;li&gt;Reusable test data&lt;/li&gt;
&lt;li&gt;Less manual setup&lt;/li&gt;
&lt;li&gt;Better test isolation&lt;/li&gt;
&lt;li&gt;Easier maintenance&lt;/li&gt;
&lt;li&gt;More reliable CI/CD execution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is not to mock every AWS service. Instead, the approach focuses on &lt;strong&gt;controlling test data and external dependencies so that tests remain predictable and repeatable&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Overall Approach
&lt;/h2&gt;

&lt;p&gt;The architecture can be summarized as:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cypress Test → &lt;code&gt;cy.task()&lt;/code&gt; → Node.js → AWS SDK → DynamoDB&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Cypress test is responsible for describing the scenario and assertions, while the Node.js layer handles AWS-specific operations.&lt;/p&gt;

&lt;p&gt;This separation keeps the test code clean and prevents AWS database logic from being duplicated across test cases.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Create Reusable Mock Data
&lt;/h2&gt;

&lt;p&gt;The first step is to create reusable JSON fixtures.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cypress/
└── fixtures/
    └── test-data.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fixture contains the information required to create the database record.&lt;/p&gt;

&lt;p&gt;Instead of hardcoding database values inside every test, the fixture becomes the base payload that can be reused across multiple scenarios.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"test-12345"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sample Test Data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TEST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ACTIVE"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Different fixtures can also be created for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Positive scenarios&lt;/li&gt;
&lt;li&gt;Negative scenarios&lt;/li&gt;
&lt;li&gt;Boundary conditions&lt;/li&gt;
&lt;li&gt;Error scenarios&lt;/li&gt;
&lt;li&gt;Different transaction states&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This keeps test data separate from test logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Separate Database Logic
&lt;/h2&gt;

&lt;p&gt;The next step is to keep DynamoDB operations outside the Cypress test itself.&lt;/p&gt;

&lt;p&gt;A dedicated helper can be responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating the DynamoDB client&lt;/li&gt;
&lt;li&gt;Converting fixture data into database records&lt;/li&gt;
&lt;li&gt;Generating partition keys and sort keys&lt;/li&gt;
&lt;li&gt;Calculating TTL values&lt;/li&gt;
&lt;li&gt;Inserting records&lt;/li&gt;
&lt;li&gt;Retrieving records&lt;/li&gt;
&lt;li&gt;Deleting test data when required&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, a helper might expose operations such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;insertMockData()
getMockData()
deleteMockData()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important principle is &lt;strong&gt;separation of concerns&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The test should describe &lt;em&gt;what data it needs&lt;/em&gt;, while the helper handles &lt;em&gt;how that data is stored in DynamoDB&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Expose Database Operations Through Cypress Tasks
&lt;/h2&gt;

&lt;p&gt;Cypress provides &lt;code&gt;cy.task()&lt;/code&gt; as a bridge between the Cypress test environment and Node.js.&lt;/p&gt;

&lt;p&gt;Instead of putting AWS SDK operations directly inside the browser-side test, the test can call a custom task:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;insertMockData&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mockData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The flow becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cypress Test
     │
     ▼
 cy.task()
     │
     ▼
 Node.js
     │
     ▼
 AWS SDK
     │
     ▼
 DynamoDB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Typical Cypress tasks can include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;insertMockData&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getMockData&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;deleteMockData&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach centralizes AWS operations and makes them reusable across the entire test suite.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Configure Environment Variables
&lt;/h2&gt;

&lt;p&gt;Cloud configuration should never be hardcoded inside test files.&lt;/p&gt;

&lt;p&gt;Environment-specific values can be provided through configuration such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS Region&lt;/li&gt;
&lt;li&gt;AWS credentials&lt;/li&gt;
&lt;li&gt;DynamoDB table name&lt;/li&gt;
&lt;li&gt;Local DynamoDB endpoint&lt;/li&gt;
&lt;li&gt;Other environment-specific settings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows the same test suite to run against different environments.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Local
  ↓
Development
  ↓
QA
  ↓
CI/CD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test logic remains unchanged while the configuration changes according to the execution environment.&lt;/p&gt;

&lt;p&gt;Credentials and secrets should always be managed securely and should never be committed to the repository.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Build Flexible Test Data
&lt;/h2&gt;

&lt;p&gt;Creating a separate fixture for every test scenario can eventually lead to duplicated test data.&lt;/p&gt;

&lt;p&gt;Instead, a base fixture can be loaded and overridden only where necessary.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;testData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;baseFixture&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`test-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;INACTIVE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The base fixture provides the common structure, while the test customizes only the values relevant to the scenario.&lt;/p&gt;

&lt;p&gt;This approach makes it easier to generate unique test records while keeping the fixture maintainable.&lt;/p&gt;

&lt;p&gt;It is especially useful when testing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple transactions&lt;/li&gt;
&lt;li&gt;Different statuses&lt;/li&gt;
&lt;li&gt;Different merchants&lt;/li&gt;
&lt;li&gt;Edge cases&lt;/li&gt;
&lt;li&gt;Concurrent processing&lt;/li&gt;
&lt;li&gt;Large datasets&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 6: Verify the Database State
&lt;/h2&gt;

&lt;p&gt;Creating test data is only part of the process.&lt;/p&gt;

&lt;p&gt;After the application processes the data, the test should retrieve the relevant DynamoDB record and validate the resulting state.&lt;/p&gt;

&lt;p&gt;Typical validations include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Record exists&lt;/li&gt;
&lt;li&gt;Partition key is correct&lt;/li&gt;
&lt;li&gt;Sort key is correct&lt;/li&gt;
&lt;li&gt;Required attributes are stored&lt;/li&gt;
&lt;li&gt;Status is updated correctly&lt;/li&gt;
&lt;li&gt;Configuration values match expectations&lt;/li&gt;
&lt;li&gt;TTL is generated correctly&lt;/li&gt;
&lt;li&gt;Expected processing results are persisted&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Insert test data
       ↓
Application processes data
       ↓
Retrieve DynamoDB record
       ↓
Validate final state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows the test to validate not only that data can be written, but also that the application's workflow produces the expected result.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Structure Works
&lt;/h2&gt;

&lt;p&gt;The approach separates the test suite into three main responsibilities:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fixtures
   ↓
Test Data

Database Helpers
   ↓
AWS / DynamoDB Operations

Cypress Tests
   ↓
Business Scenarios + Assertions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This separation provides a cleaner architecture and makes individual components easier to maintain.&lt;/p&gt;

&lt;p&gt;If the DynamoDB implementation changes, the test cases do not necessarily need to change.&lt;/p&gt;

&lt;p&gt;If the test scenario changes, the AWS helper does not need to change.&lt;/p&gt;




&lt;h2&gt;
  
  
  Moving Toward Event-Driven Testing
&lt;/h2&gt;

&lt;p&gt;Directly inserting records into DynamoDB is useful for preparing controlled test states, but it can also bypass parts of the application's normal workflow.&lt;/p&gt;

&lt;p&gt;This approach can therefore become the foundation for a more complete &lt;strong&gt;event-driven testing strategy&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The current approach might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fixture
   ↓
Cypress Task
   ↓
DynamoDB
   ↓
Application / Verification
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next evolution could be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fixture
   ↓
Event / SNS
   ↓
Application
   ↓
DynamoDB
   ↓
Cypress Verification
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of directly creating the database record, the same mock payload can be published as an event.&lt;/p&gt;

&lt;p&gt;The application then processes the event through its normal workflow, and Cypress verifies the final database state.&lt;/p&gt;

&lt;p&gt;The biggest advantage is that the &lt;strong&gt;test-data model can remain reusable&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The injection point changes, but the underlying fixture structure can remain largely the same.&lt;/p&gt;




&lt;h2&gt;
  
  
  When This Approach Is Useful
&lt;/h2&gt;

&lt;p&gt;This pattern works particularly well for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Integration testing&lt;/li&gt;
&lt;li&gt;Event-driven applications&lt;/li&gt;
&lt;li&gt;DynamoDB-backed services&lt;/li&gt;
&lt;li&gt;CI/CD pipelines&lt;/li&gt;
&lt;li&gt;Repeatable test scenarios&lt;/li&gt;
&lt;li&gt;Large-scale automated test suites&lt;/li&gt;
&lt;li&gt;Tests requiring controlled database state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, direct database seeding should be used carefully for end-to-end testing because it can bypass application logic that you may actually want to validate.&lt;/p&gt;

&lt;p&gt;A good testing strategy can use both approaches:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Database seeding&lt;/strong&gt; for controlled setup and targeted integration tests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Event/API-driven setup&lt;/strong&gt; for validating the application's complete workflow.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Benefits
&lt;/h2&gt;

&lt;p&gt;Using reusable fixtures together with Cypress tasks provides several advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cleaner test code&lt;/strong&gt; — AWS logic stays outside the test cases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reusable mock data&lt;/strong&gt; — Fixtures can support many scenarios.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduced duplication&lt;/strong&gt; — Base data can be overridden when needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Faster execution&lt;/strong&gt; — Tests do not depend on manual database preparation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better test isolation&lt;/strong&gt; — Each test can create its own required state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Centralized AWS logic&lt;/strong&gt; — DynamoDB operations are maintained in one place.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD friendly&lt;/strong&gt; — Test data can be generated automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy evolution&lt;/strong&gt; — The same data model can support future event-driven testing.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Automating database setup with reusable fixtures, Cypress tasks, and the AWS SDK can remove a significant amount of repetitive manual work from integration testing.&lt;/p&gt;

&lt;p&gt;The key is to keep &lt;strong&gt;test data, database operations, and test scenarios separate&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of manually preparing DynamoDB records before every test, the test suite becomes responsible for creating the state it needs and validating the resulting behavior.&lt;/p&gt;

&lt;p&gt;More importantly, this approach creates a foundation for moving toward event-driven testing. As the application architecture evolves, the same reusable test data can be injected through events and processed through the application's normal workflow.&lt;/p&gt;

&lt;p&gt;The result is a testing architecture that is &lt;strong&gt;repeatable, maintainable, scalable, and easier to integrate into CI/CD pipelines&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>aws</category>
      <category>testing</category>
      <category>automation</category>
    </item>
    <item>
      <title>Teaching an Agent to Triage Flaky Tests — and to Know When Not To</title>
      <dc:creator>Lyle Smart</dc:creator>
      <pubDate>Thu, 06 Aug 2026 18:32:22 +0000</pubDate>
      <link>https://dev.to/cypress/teaching-an-agent-to-triage-flaky-tests-and-to-know-when-not-to-jnh</link>
      <guid>https://dev.to/cypress/teaching-an-agent-to-triage-flaky-tests-and-to-know-when-not-to-jnh</guid>
      <description>&lt;p&gt;&lt;strong&gt;Teaching an Agent to Triage Flaky Tests — and to Know When Not To&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every team with a mature Cypress suite eventually meets the same quiet adversary: the flaky test. Not the broken test—the flaky one. It fails, then passes on retry, so nobody files it, so it lingers. Over time, "just re-run it" stops being a workaround and becomes a habit, and that habit costs you the one thing an end-to-end suite is supposed to give you: confidence.&lt;/p&gt;

&lt;p&gt;We decided to stop treating flake as background noise and start treating it as a signal worth acting on every single morning. So we built an autonomous agent to do the triage a human never quite finds time for. What we learned building it had less to do with automation than we expected, and a lot to do with judgment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Turning a Number Nobody Reads into a Decision&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The data was already there. Cypress Cloud records a flaky_test_count on every CI run and can tell you precisely which tests flaked—the error name, the message, the failing line of the stack, attempt by attempt. That is a rich signal. It was also, for us, a number that scrolled past unread.&lt;/p&gt;

&lt;p&gt;So we gave the agent a way to read it directly. We use the Cypress MCP server to dial into Cypress Cloud. It is the pipe through which the agent pulls recent runs and, for any run that flaked, the per-attempt failure detail behind each one. No scraping dashboards. No exporting CSVs. The agent simply asks Cypress Cloud what happened and gets back structured data, which is exactly what makes the rest of the loop possible.&lt;/p&gt;

&lt;p&gt;Each weekday it scans the last fifty completed CI runs, tallies flakiness per test, and applies a deliberately blunt rule: a test is only a candidate if it flaked in three or more distinct runs. One bad night is not a pattern. A test that wobbles across three separate runs is telling you something. That threshold is the difference between a useful beacon and an alarm everyone learns to mute.&lt;/p&gt;

&lt;p&gt;From there the loop is end to end, and this is where the second tool earns its place. We use Claude to review and fix the tests. It reads the failure detail surfaced through the Cypress MCP server, opens the actual spec file, groups failures into distinct modes, and writes a concrete, code-level diagnosis. Where a spec change is genuinely the right answer, it proposes the fix itself.&lt;/p&gt;

&lt;p&gt;The agent then deduplicates against Jira so the same flake is never filed twice, creates one ticket per flaky test on our QE board, @mentions the appropriate team, posts a summary to Slack, and updates a running tally in Confluence so the trend is visible to anyone who cares to look.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Most Important Thing It Does Is Refuse&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here is where the project stopped being about automation.&lt;/p&gt;

&lt;p&gt;The obvious way to build a test-fixing agent is to let it fix everything. Point it at a red test and let it turn the test green. But a flaky test is often not a test problem at all—it is a product telling you the truth in an inconvenient way.&lt;/p&gt;

&lt;p&gt;A backend might return a 500 during a read-after-write race. A page might render late because grading happens server-side. Increase the Cypress timeout and the test goes green, but you have not removed the flake. You have hidden a real bug and taught your suite to lie.&lt;/p&gt;

&lt;p&gt;So the single most important rule in the agent is one of restraint.&lt;/p&gt;

&lt;p&gt;It proposes a fix only when a spec change genuinely stabilizes the test. When the root cause is application-side or infrastructure-related, it files the ticket, classifies it clearly, and opens no pull request at all.&lt;/p&gt;

&lt;p&gt;It is not there to make tests pass.&lt;/p&gt;

&lt;p&gt;It is there to tell you why they don't.&lt;/p&gt;

&lt;p&gt;When it does propose a fix, that fix stays inside a narrow lane:&lt;/p&gt;

&lt;p&gt;Draft pull requests only&lt;br&gt;
Cypress spec files only&lt;br&gt;
Never application source code&lt;br&gt;
Clearly labeled as auto-generated and unverified&lt;/p&gt;

&lt;p&gt;The pull request's own CI run is the real verification. The agent's confidence is never mistaken for correctness.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Giving It a Memory—and a Rule About That Memory&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Diagnosis is the part of this an agent is most likely to get wrong, so we gave it something to lean on: a committed file of confirmed root-cause patterns.&lt;/p&gt;

&lt;p&gt;Each entry pairs a spec family and an error signature with:&lt;/p&gt;

&lt;p&gt;The confirmed root cause&lt;br&gt;
The correct classification&lt;br&gt;
The band-aid fix to avoid&lt;/p&gt;

&lt;p&gt;Before diagnosing anything, the agent reads this file and reuses what a human has already worked out rather than re-deriving a cause it has gotten wrong before.&lt;/p&gt;

&lt;p&gt;The rule governing that file matters just as much as the file itself.&lt;/p&gt;

&lt;p&gt;Confirmed causes only.&lt;/p&gt;

&lt;p&gt;Never a fresh guess.&lt;/p&gt;

&lt;p&gt;Codifying an unverified diagnosis would teach the agent to repeat exactly the mistake the file exists to prevent. A provisional hunch stays in the Jira ticket, where a human can weigh it. It never graduates to institutional memory until someone confirms it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Actually Changed&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Flaky tests now get triaged within minutes instead of never.&lt;/p&gt;

&lt;p&gt;The team wakes up to a Slack summary and a short stack of tickets, many with a draft fix already attached and the rest clearly marked as something a person needs to investigate.&lt;/p&gt;

&lt;p&gt;We can finally see, month over month, whether the suite is getting healthier.&lt;/p&gt;

&lt;p&gt;The outcome we did not anticipate is that a meaningful share of what we had lazily filed under "flaky" turned out to be genuine product race conditions—bugs that now receive a bug's attention instead of a timeout bump.&lt;/p&gt;

&lt;p&gt;If I could leave other Cypress teams with one reflection, it is this:&lt;/p&gt;

&lt;p&gt;The agent's most valuable trait is not how much it automates. It is that it knows the difference between a test problem and a product bug, and refuses to paper over the second.&lt;/p&gt;

&lt;p&gt;AI can accelerate the triage none of us had time for. But the judgment about what deserves a fix—and what deserves the truth—still has to be built in on purpose.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>automation</category>
      <category>testing</category>
    </item>
    <item>
      <title>Cypress Ambassador Spotlight: Boris Selivanov</title>
      <dc:creator>Melissa Milligan</dc:creator>
      <pubDate>Fri, 24 Jul 2026 13:43:32 +0000</pubDate>
      <link>https://dev.to/cypress/cypress-ambassador-spotlight-boris-selivanov-2lbp</link>
      <guid>https://dev.to/cypress/cypress-ambassador-spotlight-boris-selivanov-2lbp</guid>
      <description>&lt;p&gt;&lt;strong&gt;What motivates you to be a Cypress Ambassador?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I see being an Ambassador as the ultimate form of asynchronous collaboration. My goal is to share things that move the needle for other engineers. By comparing how different teams solve the same testing problems, we can turn isolated project victories into shared wins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do you currently help others that are either using Cypress or interested in using Cypress?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'm the person teammates ping when a test goes flaky or they're not sure how to approach a complex scenario. I always try to answer with a reusable pattern rather than a one-off fix so the knowledge scales past me. A lot of that is helping people pick selectors that survive a redesign, semantic and stable, over cosmetic and brittle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is your favorite Cypress feature?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;cy.intercept(). It's the ultimate tool for eliminating flake caused by unstable backend environments or slow APIs. Being able to intercept, modify, or stub network traffic means you can test frontend behavior under any condition imaginable, instantly. It guarantees your tests are deterministic, isolated, and entirely repeatable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is your favorite "Cypress Best Practice" and why?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Never using arbitrary cy.wait(ms). Hard waits are a confession that you don't trust your tooling. They either slow every run down or paper over a real race condition. Leaning on retry-ability and cy.intercept() aliases instead means the suite waits exactly as long as it needs to, no more, no less.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Outside of work, what are your favorite things to do (hobbies, passions, etc.)?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Catching FIFA World Cup matches with friends. It's the perfect way to unwind and watch a defensive line get tested for a change, instead of my own automation suite.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is one professional milestone you are proud of?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Building stable automation frameworks from scratch and successfully scaling that knowledge across large, cross-functional teams. Throughout my career, my favorite milestones haven't just been writing clean architectural patterns, but leading training sessions for 20+ engineers and clients to get them comfortable with test automation. Demystifying complex testing concepts and giving teams the leverage to confidently own their builds is exactly what drives me.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For new users - what is the best way to "get started" with Cypress?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Skip the boring configuration routines. Just run npm install cypress, open the runner, and write a real test against your actual app: visit a page, fill a form, submit, and assert the result. Letting the native retry-ability handle the async waiting while watching it interact with your live, real-world codebase is the absolute fastest way to make the framework click.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is your favorite quote or saying?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"It works on my machine."&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>ambassador</category>
      <category>testing</category>
      <category>development</category>
    </item>
    <item>
      <title>Connecting AWS Account with Cypress Automation: A Simple STS Connection Test</title>
      <dc:creator>Sehani Chathurangi</dc:creator>
      <pubDate>Fri, 17 Jul 2026 12:42:14 +0000</pubDate>
      <link>https://dev.to/cypress/connecting-aws-account-with-cypress-automation-a-simple-sts-connection-test-aea</link>
      <guid>https://dev.to/cypress/connecting-aws-account-with-cypress-automation-a-simple-sts-connection-test-aea</guid>
      <description>&lt;p&gt;When building Cypress automation that interacts with AWS services, the first step is verifying that your test framework can successfully authenticate and communicate with your AWS account.&lt;/p&gt;

&lt;p&gt;In this article, you'll learn how to connect Cypress to AWS and perform a simple authentication test using AWS Security Token Service (STS) and the &lt;code&gt;GetCallerIdentity&lt;/code&gt; API.&lt;/p&gt;

&lt;p&gt;This approach helps confirm that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS credentials are correctly configured.&lt;/li&gt;
&lt;li&gt;Cypress can invoke AWS SDK operations through Node.js tasks.&lt;/li&gt;
&lt;li&gt;The automation environment is connected to the expected AWS account.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Establishing this connection first provides a solid foundation before automating interactions with services such as AWS Lambda, Amazon S3, Amazon DynamoDB, Amazon SNS, or Amazon SQS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before getting started, ensure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js installed&lt;/li&gt;
&lt;li&gt;A Cypress project&lt;/li&gt;
&lt;li&gt;Valid AWS credentials:

&lt;ul&gt;
&lt;li&gt;AWS Access Key ID&lt;/li&gt;
&lt;li&gt;AWS Secret Access Key&lt;/li&gt;
&lt;li&gt;AWS Session Token (if using temporary credentials)&lt;/li&gt;
&lt;li&gt;AWS Region&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Note:If you're running Cypress in an AWS environment (such as AWS CodeBuild, an EC2 instance with an IAM role, or GitHub Actions using OpenID Connect), you may not need to provide credentials manually. The AWS SDK can automatically retrieve credentials from the execution environment.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Install the AWS SDK&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Install the AWS STS client package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @aws-sdk/client-sts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For this connectivity test, we only need the AWS Security Token Service (STS) client.&lt;/p&gt;

&lt;p&gt;The package provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;STSClient&lt;/code&gt; – Creates a client for communicating with AWS STS.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GetCallerIdentityCommand&lt;/code&gt; – Returns details about the authenticated AWS identity associated with the configured credentials.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Configure AWS Credentials&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For local development, create or update your &lt;code&gt;cypress.env.json&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"AWS_ACCESS_KEY_ID"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"AWS_SECRET_ACCESS_KEY"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"AWS_SESSION_TOKEN"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"AWS_REGION"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Populate the file with your AWS credentials.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"AWS_ACCESS_KEY_ID"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-access-key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"AWS_SECRET_ACCESS_KEY"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-secret-key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"AWS_SESSION_TOKEN"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-session-token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"AWS_REGION"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Never commit &lt;code&gt;cypress.env.json&lt;/code&gt; containing real AWS credentials to source control. Add it to your &lt;code&gt;.gitignore&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best Practice:&lt;/strong&gt; For CI/CD pipelines, prefer environment variables, IAM roles, or OpenID Connect (OIDC) instead of storing credentials in a file.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Configure Cypress to Use AWS Credentials&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Update your &lt;code&gt;cypress.config.ts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;First, import the required AWS SDK classes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cypress&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;STSClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;GetCallerIdentityCommand&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/client-sts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside &lt;code&gt;setupNodeEvents&lt;/code&gt;, map the Cypress environment variables to Node.js environment variables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;setupNodeEvents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;awsKeys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AWS_SESSION_TOKEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AWS_REGION&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;awsKeys&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cypress stores environment variables in &lt;code&gt;config.env&lt;/code&gt;, while the AWS SDK automatically reads credentials from the standard Node.js environment (&lt;code&gt;process.env&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;By copying the values into &lt;code&gt;process.env&lt;/code&gt;, the AWS SDK can authenticate automatically without requiring any additional credential configuration.&lt;/p&gt;

&lt;p&gt;The AWS SDK follows the AWS Credential Provider Chain, and environment variables are one of the first credential sources it checks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Create an AWS Connection Task&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create a Cypress task that authenticates with AWS using STS.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;task&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;awsConnectionTest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;STSClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_REGION&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;us-east-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GetCallerIdentityCommand&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;Arn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Arn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`AWS authentication failed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why use a Cypress task?
&lt;/h3&gt;

&lt;p&gt;AWS SDK calls must run in Cypress tasks because tasks execute in the Node.js process.&lt;/p&gt;

&lt;p&gt;Cypress test code runs in the browser context, where direct access to Node.js APIs is not available. Using &lt;code&gt;cy.task()&lt;/code&gt; allows your tests to securely execute backend operations such as invoking AWS services.&lt;/p&gt;

&lt;h3&gt;
  
  
  What happens during this task?
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Cypress calls the &lt;code&gt;awsConnectionTest&lt;/code&gt; task.&lt;/li&gt;
&lt;li&gt;The AWS SDK creates an STS client using the configured region.&lt;/li&gt;
&lt;li&gt;The SDK automatically resolves credentials from the environment.&lt;/li&gt;
&lt;li&gt;AWS validates the credentials.&lt;/li&gt;
&lt;li&gt;STS returns details about the authenticated IAM user or role.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A successful response looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Account"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"123456789012"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Arn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::123456789012:user/test-user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"UserId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AIDAxxxxxxxx"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 5: Create a Cypress Test&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create a new test file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;testing/awsConnection.cy.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AWS connection&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;connects to AWS using STS GetCallerIdentity&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;awsConnectionTest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;identity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

      &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;identity&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;identity&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;have&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Account&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;identity&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;have&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Arn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;identity&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;have&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UserId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 6: Run the Cypress Test&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Execute either of the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx cypress open
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx cypress run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If authentication is successful, Cypress logs the authenticated AWS identity, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS Account ID&lt;/li&gt;
&lt;li&gt;IAM User or Role ARN&lt;/li&gt;
&lt;li&gt;User ID&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This confirms that Cypress can successfully authenticate with your AWS account.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Start with GetCallerIdentity?
&lt;/h3&gt;

&lt;p&gt;Before interacting with AWS services such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS Lambda&lt;/li&gt;
&lt;li&gt;Amazon S3&lt;/li&gt;
&lt;li&gt;Amazon DynamoDB&lt;/li&gt;
&lt;li&gt;Amazon SNS&lt;/li&gt;
&lt;li&gt;Amazon SQS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;it's recommended to verify that authentication is working correctly.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;GetCallerIdentity&lt;/code&gt; is ideal because it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Confirms that authentication is successful.&lt;/li&gt;
&lt;li&gt;Identifies the AWS account and IAM identity being used.&lt;/li&gt;
&lt;li&gt;Helps diagnose credential-related issues early.&lt;/li&gt;
&lt;li&gt;Does not require permissions beyond the ability to authenticate.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Successfully calling &lt;code&gt;GetCallerIdentity&lt;/code&gt; confirms that Cypress can authenticate with AWS using the configured credentials.&lt;/p&gt;

&lt;p&gt;In this article, you learned how to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install the AWS STS SDK.&lt;/li&gt;
&lt;li&gt;Configure AWS credentials for local development.&lt;/li&gt;
&lt;li&gt;Map Cypress environment variables to the Node.js environment.&lt;/li&gt;
&lt;li&gt;Create a Cypress task that authenticates with AWS.&lt;/li&gt;
&lt;li&gt;Validate the connection using &lt;code&gt;GetCallerIdentity&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once this authentication step is working, you can extend the same approach to automate testing against other AWS services such as Lambda functions, S3 buckets, DynamoDB tables, SNS topics, SQS queues, and many more.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>aws</category>
      <category>testing</category>
      <category>cypress</category>
    </item>
    <item>
      <title>How I leveraged Claude Code skills to help me migrate Cypress 16 deprecations in time</title>
      <dc:creator>Marcelo C.</dc:creator>
      <pubDate>Fri, 03 Jul 2026 15:05:58 +0000</pubDate>
      <link>https://dev.to/cypress/how-i-leveraged-claude-code-skills-to-help-me-migrate-cypress-16-deprecations-in-time-37ff</link>
      <guid>https://dev.to/cypress/how-i-leveraged-claude-code-skills-to-help-me-migrate-cypress-16-deprecations-in-time-37ff</guid>
      <description>&lt;p&gt;Have you ever stared at a deprecation warning scrolling by in your terminal and thought &lt;em&gt;"eh, future me will deal with that"&lt;/em&gt;? We all have! But here's the thing about future you: he eventually shows up. And in my case, he showed up wearing a shirt that said &lt;strong&gt;Cypress v16&lt;/strong&gt;, holding a changelog, and pointing at the line that says &lt;code&gt;Cypress.env()&lt;/code&gt; will be &lt;strong&gt;fully removed&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxw1gx0ztg6645q543ozf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxw1gx0ztg6645q543ozf.png" alt="Cypress terminal warning before migration" width="799" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Warning in 15.10. Gone in 16. And sprinkled across every config, page object, and spec file in the massive E2E repository I maintain at my work monorepo. Configs, support files, fluent page-object chains — everywhere.&lt;/p&gt;

&lt;p&gt;So let's get to the grain here: even though Cypress has a &lt;a href="https://docs.cypress.io/app/references/migration-guide" rel="noopener noreferrer"&gt;great documentation about it&lt;/a&gt;, I didn't want to execute a find-and-replace job manually, and I also wanted to move fast. So instead of pasting a prompt into a chat window forty times, I did something better: I turned the migration playbook itself into a &lt;strong&gt;Claude Code Skill&lt;/strong&gt; — a reusable, self-triggering unit of expertise that lives in my machine and outlives this one migration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Cypress.env() had to die
&lt;/h2&gt;

&lt;p&gt;If you've written E2E tests, you know &lt;code&gt;Cypress.env()&lt;/code&gt;. Synchronous, available everywhere, dead simple.&lt;/p&gt;

&lt;p&gt;And that simplicity was exactly the problem: &lt;strong&gt;it hydrated the entire env object straight into the browser runtime&lt;/strong&gt;. Every value. Including the ones a given test never reads. Your admin password, your API tokens — all of it, sitting in browser-accessible memory, one &lt;code&gt;window&lt;/code&gt; inspection away, in every single spec.&lt;/p&gt;

&lt;p&gt;Easy to use? Absolutely. A credential-leaking surface baked into your test runner? Also absolutely.&lt;/p&gt;

&lt;p&gt;The v16 API splits the concern into two deliberately asymmetric halves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;cy.env(['key']).then(...)&lt;/code&gt;&lt;/strong&gt; — asynchronous, resolved &lt;strong&gt;Node-side&lt;/strong&gt;, strictly read-only. Values never touch the browser context until you explicitly consume them inside the callback. This is where credentials and tokens live now.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Cypress.expose('key')&lt;/code&gt;&lt;/strong&gt; — synchronous, a drop-in replacement, but only for values you've &lt;em&gt;explicitly opted in&lt;/em&gt; to exposing via a new top-level &lt;code&gt;expose:&lt;/code&gt; block in your config (a sibling to &lt;code&gt;env:&lt;/code&gt;, not nested inside it).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice the design: the safe path is ergonomic, the sensitive path is deliberately awkward. That asymmetry is the whole point.&lt;/p&gt;

&lt;h2&gt;
  
  
  The strategy: teaching the AI a classification heuristic
&lt;/h2&gt;

&lt;p&gt;Doing a mindless regex sweep across thousands of files would have been fast — and catastrophically wrong, because every single call site needed a &lt;strong&gt;classification decision&lt;/strong&gt; first. So instead of doing the work 3,000 times, I did it once: I encoded a strict heuristic and let Claude Code apply it mechanically.&lt;/p&gt;

&lt;p&gt;The heuristic:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Does the value contain a password, a username paired with a password, or any secret?&lt;/strong&gt; → It's sensitive. It &lt;strong&gt;stays&lt;/strong&gt; in the &lt;code&gt;env:&lt;/code&gt; block and every read site gets rewritten to the async &lt;code&gt;cy.env([...]).then(...)&lt;/code&gt; form.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is it a feature flag (our &lt;code&gt;idp_active&lt;/code&gt;, &lt;code&gt;xbrl_active&lt;/code&gt;), a locale, a timeout, a base URL fragment?&lt;/strong&gt; → It's public. It &lt;strong&gt;moves&lt;/strong&gt; to the &lt;code&gt;expose:&lt;/code&gt; block and the read becomes a one-line swap to &lt;code&gt;Cypress.expose()&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Rule 2 is boring. Rule 1 is where the migration earns its war stories.&lt;/p&gt;

&lt;h2&gt;
  
  
  From prompt to Skill: making the knowledge permanent
&lt;/h2&gt;

&lt;p&gt;The Cypress team actually ships an official starting point for this — the &lt;a href="https://github.com/cypress-io/ai-toolkit/blob/main/prompt-library/cypress-v16-migration/remove-cypress-env-usage.md" rel="noopener noreferrer"&gt;remove-cypress-env-usage prompt from the Cypress AI Toolkit&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fagmdeoz0yidhduh5xcrv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fagmdeoz0yidhduh5xcrv.png" alt="Real prompt from Cypress team" width="800" height="634"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's good. But a prompt in a library has two problems: &lt;strong&gt;you have to remember it exists&lt;/strong&gt;, and &lt;strong&gt;you have to re-paste it every session&lt;/strong&gt;. A Claude Code Skill solves both. A Skill is just a folder with a &lt;code&gt;SKILL.md&lt;/code&gt; — a markdown file with a small YAML frontmatter (name + description) followed by the instructions. Claude Code scans the descriptions on every task and &lt;strong&gt;auto-loads the skill when the context matches&lt;/strong&gt;, so the moment I say "migrate this spec off Cypress.env", the whole playbook is already in its head. No copy-paste, no drift between sessions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9pa2uml0cwyjoch7ber0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9pa2uml0cwyjoch7ber0.png" alt="Prompt set as Claude skill" width="800" height="194"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/
└── .claude/
    └── skills/
        └── cypress-v16-migration/
            └── SKILL.md    # frontmatter + the full migration playbook
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But here's the important part — I didn't just copy the official prompt into a file. I &lt;strong&gt;transformed&lt;/strong&gt; it, layering in everything the generic version can't know about &lt;em&gt;my&lt;/em&gt; repo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;classification heuristic&lt;/strong&gt; above, with our real flag names (&lt;code&gt;idp_active&lt;/code&gt;, &lt;code&gt;xbrl_active&lt;/code&gt;) as worked examples, so it never has to guess what "sensitive" means in our codebase.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;command-queue traps&lt;/strong&gt; you're about to read below, written as explicit "never do this / always do this" rules — because I hit them once and never wanted the AI to reproduce them.&lt;/li&gt;
&lt;li&gt;Our repo topology: &lt;strong&gt;all four active config files&lt;/strong&gt; that need the &lt;code&gt;expose:&lt;/code&gt; block and the final flag flip, so nothing gets a partial migration.&lt;/li&gt;
&lt;li&gt;The verification sequence (grep → flag → CI) as a mandatory checklist the skill must run before declaring victory.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The generic prompt is the textbook; the Skill is the textbook annotated by someone who took the exam. And since it's a personal skill (kept local, not committed to the repo), I can keep sharpening it without a PR review debate about my prompt-engineering style. 😄&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the plot thickens 🕵️
&lt;/h2&gt;

&lt;p&gt;You might be thinking: &lt;em&gt;"Ok, this is still just a glorified find-and-replace with two output templates. What's the big deal?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The big deal is &lt;strong&gt;the Cypress command queue&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Swapping a synchronous read for an asynchronous one &lt;em&gt;inside a fluent page-object chain&lt;/em&gt; changes the execution semantics of everything around it. Take our global &lt;code&gt;cy.login()&lt;/code&gt; command — the one every spec in the codebase depends on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// BEFORE — synchronous, credentials resolved at enqueue time&lt;/span&gt;
&lt;span class="nx"&gt;Cypress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Cypress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;username&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Cypress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;session&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;loginPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;typeUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;typePass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goToHome&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Naive migration attempt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// BROKEN — do you see it?&lt;/span&gt;
&lt;span class="nx"&gt;Cypress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;username&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;session&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;loginPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;typeUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;typePass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goToHome&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// 💥&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks reasonable. It's a time bomb. Here's why:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trap #1 — silent reordering.&lt;/strong&gt; Cypress enqueues commands synchronously as JavaScript executes, but runs them later. That trailing &lt;code&gt;cy.goToHome()&lt;/code&gt; gets &lt;strong&gt;enqueued before the &lt;code&gt;.then()&lt;/code&gt; callback ever fires&lt;/strong&gt; — and any &lt;em&gt;non-Cypress&lt;/em&gt; code after the block (variable assignments, page-object state mutations) executes immediately, before the credentials even exist. No error. No warning. Just your test quietly doing things in the wrong order. This is, by far, the easiest mistake to make in the entire migration, and grep will never find it for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trap #2 — &lt;code&gt;cy.session()&lt;/code&gt; needs a resolved ID.&lt;/strong&gt; The session ID argument is evaluated &lt;em&gt;at call time&lt;/em&gt;, not at run time. You can't hand it a promise or a pending value. So the entire &lt;code&gt;cy.session(...)&lt;/code&gt; invocation — not just its setup callback — has to move &lt;strong&gt;inside&lt;/strong&gt; the &lt;code&gt;cy.env().then()&lt;/code&gt; callback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// CORRECT — everything downstream of the credentials lives inside the callback&lt;/span&gt;
&lt;span class="nx"&gt;Cypress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;username&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;session&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;loginPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;typeUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;typePass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goToHome&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here's the payoff of years of discipline: because every login flow was registered through &lt;code&gt;Cypress.Commands.add&lt;/code&gt;, this entire architectural shift happened &lt;strong&gt;behind the command boundary&lt;/strong&gt;. Thousands of &lt;code&gt;cy.login()&lt;/code&gt; call sites across the specs didn't change by a single character. The abstraction held. This, kids, is why you wrap your login logic in a custom command instead of copy-pasting it into 5k tests (ask me how I know 😉).&lt;/p&gt;

&lt;h2&gt;
  
  
  The final lockdown 🔒
&lt;/h2&gt;

&lt;p&gt;A migration you can't &lt;em&gt;verify&lt;/em&gt; is just a migration you &lt;em&gt;hope&lt;/em&gt; worked. Cypress v16 gives you a kill switch for exactly this: &lt;code&gt;allowCypressEnv: false&lt;/code&gt;. With it set, any surviving &lt;code&gt;Cypress.env()&lt;/code&gt; call becomes a &lt;strong&gt;hard runtime failure&lt;/strong&gt; — not a warning, a crash.&lt;/p&gt;

&lt;p&gt;My verification sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;grep&lt;/code&gt; the branch for live &lt;code&gt;Cypress.env(&lt;/code&gt; calls → zero hits (comments and the migration doc don't count).&lt;/li&gt;
&lt;li&gt;Flip &lt;code&gt;allowCypressEnv: false&lt;/code&gt; across &lt;strong&gt;all four&lt;/strong&gt; active config files — no partial lockdowns, no "we'll enable it on that config later."&lt;/li&gt;
&lt;li&gt;Ship it to CI and let the full E2E stages be the judge.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Green across the board. And here's the subtle part that made it safe: we only changed the &lt;strong&gt;read API&lt;/strong&gt;, never the &lt;strong&gt;injection mechanism&lt;/strong&gt;. Every CI secret, every &lt;code&gt;--env&lt;/code&gt; CLI override, every pipeline variable kept flowing exactly as before — they just land in a place the browser can no longer see by default.&lt;/p&gt;

&lt;h2&gt;
  
  
  The commit log tells the real story 📜
&lt;/h2&gt;

&lt;p&gt;Want proof that the Skill approach actually moved fast? Read the git history like a detective:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;First PR:
Tue 11:27  Migration to v16
Tue 12:11  [E2E] Migration to Cypress v16 architecture
Tue 14:44  Updating Cypress env flag
Wed 16:04  Updating Typescript types

Fixing login issue:
Thu 10:23  [E2E] Fixing idp override with v16 Cy migration 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look at Tuesday morning: the &lt;strong&gt;bulk rewrite of the entire architecture landed in about 45 minutes&lt;/strong&gt;, between the first pass and the structured &lt;code&gt;[E2E]&lt;/code&gt; commit. That's thousands of call sites, classified and rewritten, in less time than a sprint planning meeting. Two hours later, &lt;code&gt;allowCypressEnv: false&lt;/code&gt; was already flipped — the kill switch armed on day one.&lt;/p&gt;

&lt;p&gt;Then comes the honest part, the part most migration posts conveniently forget: &lt;strong&gt;the long tail&lt;/strong&gt;. Wednesday was TypeScript's turn — &lt;code&gt;cy.env()&lt;/code&gt; returning a promise-like of a keyed object meant the old string-based typings had to be reshaped, so the compiler could catch a mistyped key at build time instead of CI catching it at run time.&lt;/p&gt;

&lt;p&gt;And Thursday? Thursday delivered the one trap the Skill didn't know about yet — &lt;strong&gt;Trap #3, the runtime override&lt;/strong&gt;. One special spec family runs against a different server with IDP/SSO enabled, and it used to flip that on with a runtime &lt;code&gt;Cypress.env('idp_active', true)&lt;/code&gt; in a &lt;code&gt;beforeEach&lt;/code&gt;. In v16, &lt;code&gt;idp_active&lt;/code&gt; now lives in the &lt;code&gt;expose:&lt;/code&gt; block — and exposed values are &lt;strong&gt;resolved from config, read-only at runtime&lt;/strong&gt;. The old setter didn't crash; it just silently did nothing, and the spec calmly logged into the wrong server. The fix was moving the override where v16 wants it: into the spec-level config override, resolved &lt;em&gt;before&lt;/em&gt; the test runs, not mutated &lt;em&gt;during&lt;/em&gt; it.&lt;/p&gt;

&lt;p&gt;Guess what happened next? That trap went straight into the &lt;code&gt;SKILL.md&lt;/code&gt;. The next migration Claude Code runs already knows about runtime overrides of exposed values. &lt;strong&gt;That's the whole point of a Skill over a prompt: it compounds.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;A breaking-change migration at this scale isn't a refactor problem, it's a &lt;strong&gt;classification + execution-semantics&lt;/strong&gt; problem. The AI didn't make the hard decisions — the heuristic did. The AI just made applying that heuristic across thousands of files not cost me a month of my life. And by encoding it as a Skill instead of a throwaway prompt, every trap I hit made the &lt;em&gt;next&lt;/em&gt; migration cheaper.&lt;/p&gt;

&lt;p&gt;What about you? Have you ever had to architect a massive, breaking-change migration across thousands of tests? What was the trap that almost got you — and did your abstractions hold when it mattered? Leave me a comment, I'd love to hear!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cypress</category>
      <category>e2etesting</category>
    </item>
    <item>
      <title>Meet the CypressConf 2026 Keynote: Bas Dijkstra</title>
      <dc:creator>Ronald Williams</dc:creator>
      <pubDate>Mon, 13 Apr 2026 16:32:16 +0000</pubDate>
      <link>https://dev.to/cypress/meet-the-cypressconf-2026-keynote-bas-dijkstra-4865</link>
      <guid>https://dev.to/cypress/meet-the-cypressconf-2026-keynote-bas-dijkstra-4865</guid>
      <description>&lt;p&gt;As CypressConf 2026 gets closer, the focus is simple.&lt;/p&gt;

&lt;p&gt;How do you move fast and still trust your test results?&lt;/p&gt;

&lt;p&gt;With more automation and AI in the mix, teams are shipping faster than ever. But speed has never been the hard part. Confidence is.&lt;/p&gt;

&lt;p&gt;Across teams, the same patterns keep showing up. Test suites grow, pipelines get faster, and more signals are generated at every stage of development. But when it comes time to make a decision, teams are still asking the same question.&lt;/p&gt;

&lt;p&gt;Can we trust this?&lt;/p&gt;

&lt;p&gt;Quality issues rarely belong to one person or one role anymore. Confidence in what ships depends on how well teams interpret and act on the signals coming from their tests, pipelines, and production systems. That is the shift happening across the industry, and it is what this year’s theme is built around.&lt;/p&gt;

&lt;p&gt;To kick things off, we are excited to announce our keynote speaker, &lt;a href="https://cypress.registration.goldcast.io/events/670deb6c-06ee-4ce8-858b-8a4db3a62eb1?utm_source=blog&amp;amp;utm_medium=dev_to&amp;amp;utm_campaign=cypressconf2026&amp;amp;utm_term=04-15-2026&amp;amp;utm_content=keynote_announcement" rel="noopener noreferrer"&gt;Bas Dijkstra&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Bas is a global test automation consultant, trainer, and speaker who has spent years working directly with engineering teams to improve how they approach quality. His work is grounded in real systems, real constraints, and the realities of modern development. He is known for helping teams cut through noise, reduce flake, and build testing strategies they can actually rely on.&lt;/p&gt;

&lt;p&gt;More importantly, he understands that testing is no longer a single function. It is a shared responsibility that spans developers, QA, and engineering leadership. The teams that succeed are the ones that align around clear signals and act on them with confidence.&lt;/p&gt;

&lt;p&gt;That perspective is exactly what makes this keynote the right fit for CypressConf this year.&lt;/p&gt;

&lt;p&gt;The industry is not short on tools. It is not short on automation. What teams are working through now is how to make sense of everything those systems produce. AI is increasing the volume of signals. Pipelines are accelerating feedback. But without clarity, more signal can just as easily create more noise.&lt;/p&gt;

&lt;p&gt;The challenge is no longer generating results. It is knowing what matters.&lt;/p&gt;

&lt;p&gt;At &lt;a href="https://cypress.registration.goldcast.io/events/670deb6c-06ee-4ce8-858b-8a4db3a62eb1?utm_source=blog&amp;amp;utm_medium=dev_to&amp;amp;utm_campaign=cypressconf2026&amp;amp;utm_term=04-15-2026&amp;amp;utm_content=keynote_announcement" rel="noopener noreferrer"&gt;CypressConf 2026&lt;/a&gt;, you will hear from teams who are working through this in real time. They are improving how they collaborate across roles, finding ways to respond earlier without introducing risk, and turning test results into decisions they can stand behind. They are not slowing down. They are getting better at understanding the systems they have already built.&lt;/p&gt;

&lt;p&gt;The keynote sets the tone for that conversation.&lt;/p&gt;

&lt;p&gt;With Bas Dijkstra, expect a session grounded in experience, focused on what teams are actually dealing with today, and clear about where things are going next.&lt;/p&gt;

&lt;p&gt;This is &lt;a href="https://cypress.registration.goldcast.io/events/670deb6c-06ee-4ce8-858b-8a4db3a62eb1?utm_source=blog&amp;amp;utm_medium=dev_to&amp;amp;utm_campaign=cypressconf2026&amp;amp;utm_term=04-15-2026&amp;amp;utm_content=keynote_announcement" rel="noopener noreferrer"&gt;the first announcement for CypressConf 2026&lt;/a&gt;. More speakers, sessions, and content are on the way.&lt;/p&gt;

&lt;p&gt;The signal is up.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>leadership</category>
    </item>
    <item>
      <title>Agent-Driven E2E Testing with Cypress: A Practical Guide to Harness Engineering with Cursor Subagents</title>
      <dc:creator>Darpan Shah</dc:creator>
      <pubDate>Tue, 07 Apr 2026 22:13:28 +0000</pubDate>
      <link>https://dev.to/cypress/agent-driven-e2e-testing-with-cypress-a-practical-guide-to-harness-engineering-with-cursor-5fob</link>
      <guid>https://dev.to/cypress/agent-driven-e2e-testing-with-cypress-a-practical-guide-to-harness-engineering-with-cursor-5fob</guid>
      <description>&lt;p&gt;Teams have done end-to-end testing deliberately for years: exploring the app, writing tests from what they see, fixing failures in focused sessions. That's skilled work, not guesswork.&lt;/p&gt;

&lt;p&gt;The hard part is usually organizational. Knowledge sits in people's heads or scattered across chat histories and tickets. What you see on a live screen is tough to describe clearly to whoever writes the automated test. Each new flow forces everyone to reload the same context from scratch.&lt;/p&gt;

&lt;p&gt;Agent-driven development doesn't replace that judgment. It packages skilled work into narrow roles (explore, implement, execute, repair) with clear inputs and outputs. Quality builds over time instead of starting from zero every sprint.&lt;/p&gt;

&lt;p&gt;This approach mirrors &lt;strong&gt;harness engineering&lt;/strong&gt;: the system around the agents that makes them reliable, not just capable.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Is a Harness, and Why Does It Matter?
&lt;/h3&gt;

&lt;p&gt;The term "harness" has emerged as shorthand for everything in an AI agent system except the model itself. Put simply: &lt;strong&gt;Agent = Model + Harness&lt;/strong&gt;. "The core challenge of long-running agents is that they must work in discrete sessions, and each new session begins with no memory of what came before." According to Anthropic's engineering research, imagine a software project staffed by engineers working in shifts, where each new engineer arrives with no memory of what happened on the previous shift. Without structure, agents drift, repeat work, or declare victory too early.&lt;/p&gt;

&lt;p&gt;Their solution? A two-fold approach: an initializer agent that sets up the environment on the first run, and a coding agent that makes incremental progress in every session, while leaving clear artifacts for the next session.&lt;/p&gt;

&lt;p&gt;When you talking about a coding agent. Martin Fowler's team breaks harness engineering into key components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Context engineering&lt;/strong&gt;: Provides us with the means to make guides and sensors available to the agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architectural constraints&lt;/strong&gt;: Rules that mechanically enforce quality (not just suggestions).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feedback loops&lt;/strong&gt;: The human's job is to steer the agent by iterating on the harness. Whenever an issue happens multiple times, the feedforward and feedback controls should be improved to make the issue less probable or even prevent it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Here's the counterintuitive insight: increasing trust and reliability in AI-generated code requires constraining the solution space rather than expanding it. Narrow roles, explicit handoffs, and clear boundaries make agents more productive, not less.&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  How This Applies to E2E Testing with Cypress
&lt;/h3&gt;

&lt;p&gt;This article describes four agents specialized for &lt;strong&gt;E2E testing&lt;/strong&gt; using Cypress and how they form a closed loop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;cypress-browser-explorer&lt;/strong&gt;: Maps UI flows with live browser tooling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cypress-builder&lt;/strong&gt;: Implements specs per team conventions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cypress-runner&lt;/strong&gt;: Executes tests consistently&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cypress-debugger&lt;/strong&gt;: Classifies failures and applies fixes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each agent produces a structured artifact (exploration report, spec file, run summary, debug notes) that becomes the input for the next agent. This is the harness in action: each step creates a plan that keeps the next agent on track.&lt;/p&gt;

&lt;p&gt;In Cursor, each of these agents maps directly to a custom &lt;a href="https://cursor.com/docs/subagents" rel="noopener noreferrer"&gt;subagent&lt;/a&gt; -- a markdown file in &lt;code&gt;.cursor/agents/&lt;/code&gt; with a name, description, and focused prompt. The explorer subagent leverages Cursor's built-in &lt;a href="https://cursor.com/docs/agent/tools/browser" rel="noopener noreferrer"&gt;browser tool&lt;/a&gt; to navigate your app, take snapshots, read the live DOM, and capture network activity without leaving the IDE. That means the exploration report isn't hand-written -- it's generated from real page state.&lt;/p&gt;

&lt;p&gt;It seems reasonable that specialized agents like a testing agent, a quality assurance agent, or a code cleanup agent could do an even better job at sub-tasks across the software development lifecycle. That's exactly what this workflow does for E2E automation when Cypress is your tool.&lt;/p&gt;

&lt;p&gt;Evidence from the real UI flows into code. Code gets verified by a standard test run. Failures get handled with clear escalation rules instead of improvisation.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Feedback Loop
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fka7lzijupyz59ydgsmay.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fka7lzijupyz59ydgsmay.png" alt="The feedback loop: Explore → build → run; on failure, debug and re-run; if the UI changed, explore again and rebuild" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The loop in one sentence: Explore → build → run; on failure, debug and re-run; if the UI changed, explore again and rebuild.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This closed loop is where the efficiency gains come from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Less rework&lt;/strong&gt;: Selectors and URLs come from live exploration, not memory&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Faster green builds&lt;/strong&gt;: Runner standardizes execution; debugger applies evidence-based fixes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clear escalation&lt;/strong&gt;: Stale DOM leads to re-explore; flaky patterns get documented&lt;/li&gt;
&lt;li&gt;Single-test discipline: Fix one failure, re-run, then move on&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Four Agents at a Glance
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Primary Inputs&lt;/th&gt;
&lt;th&gt;Primary Outputs&lt;/th&gt;
&lt;th&gt;Must Not&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;cypress-browser-explorer&lt;/td&gt;
&lt;td&gt;Map scoped UI flows using Cursor's browser tool&lt;/td&gt;
&lt;td&gt;URL, steps, ticket scope&lt;/td&gt;
&lt;td&gt;Exploration report with selectors, network map&lt;/td&gt;
&lt;td&gt;Wander outside scope; invent selectors without proof&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cypress-builder&lt;/td&gt;
&lt;td&gt;Implement specs per team rules&lt;/td&gt;
&lt;td&gt;Exploration report&lt;/td&gt;
&lt;td&gt;Spec and support code; handoff to runner&lt;/td&gt;
&lt;td&gt;Skip exploration for unfamiliar pages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cypress-runner&lt;/td&gt;
&lt;td&gt;Execute tests consistently&lt;/td&gt;
&lt;td&gt;Spec path, tags/env&lt;/td&gt;
&lt;td&gt;Pass/fail summary with failure context&lt;/td&gt;
&lt;td&gt;Fix failing tests (send to debugger)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cypress-debugger&lt;/td&gt;
&lt;td&gt;Classify failures, apply fixes&lt;/td&gt;
&lt;td&gt;Failure output, artifacts&lt;/td&gt;
&lt;td&gt;Code changes; handoff to runner or explorer&lt;/td&gt;
&lt;td&gt;Invent selectors when DOM has changed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;: These agents are blueprints, not universal standards. Your stack, auth flow, and naming conventions will differ. Expect to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Edit agent instructions to reference your scripts and config&lt;/li&gt;
&lt;li&gt;Pair agents with project rules (lint, selector policy, test ID format)&lt;/li&gt;
&lt;li&gt;Add or trim steps where your org needs tighter guardrails&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The value is the shape of the workflow and clean handoffs, not a one-size-fits-all prompt.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handoff Templates: Structured Artifacts That Bridge Context
&lt;/h3&gt;

&lt;p&gt;The key insight here was finding a way for agents to quickly understand the state of work when starting with a fresh context window. Structured handoffs are what prevent "context amnesia" between agents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Explorer → Builder&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Handoff to cypress-builder&lt;/span&gt;

Prompt: "Create cypress/e2e/[feature].cy.js using this exploration report:
&lt;span class="p"&gt;-&lt;/span&gt; Scope source: [quote from ticket/steps]
&lt;span class="p"&gt;-&lt;/span&gt; URL map: [ordered list]
&lt;span class="p"&gt;-&lt;/span&gt; Selector inventory: [element, purpose, selector, stability]
&lt;span class="p"&gt;-&lt;/span&gt; Network map: [method, pattern, suggested alias]"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Builder → Runner&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Handoff to cypress-runner&lt;/span&gt;

Prompt: "Run &lt;span class="nt"&gt;&amp;lt;spec&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; to verify the new/updated spec."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Runner → Debugger (on failure)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Handoff to cypress-debugger&lt;/span&gt;

Prompt: "Triage these E2E test failures (Cypress):

&lt;span class="gs"&gt;**Failing specs:**&lt;/span&gt; cypress/e2e/&lt;span class="nt"&gt;&amp;lt;spec&amp;gt;&lt;/span&gt;.cy.js

&lt;span class="gs"&gt;**Failures:**&lt;/span&gt;
&lt;span class="p"&gt;1.&lt;/span&gt; [TEST-ID] &lt;span class="nt"&gt;&amp;lt;describe&amp;gt;&lt;/span&gt; &amp;gt; &lt;span class="nt"&gt;&amp;lt;it&amp;gt;&lt;/span&gt;
   Error: &lt;span class="nt"&gt;&amp;lt;message&amp;gt;&lt;/span&gt;
   Screenshot: cypress/screenshots/&lt;span class="nt"&gt;&amp;lt;path&amp;gt;&lt;/span&gt;

&lt;span class="gs"&gt;**Notes:**&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;auth&lt;/span&gt; &lt;span class="na"&gt;errors&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;timeouts&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;etc.&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Debugger → Runner (after fix)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Handoff to cypress-runner&lt;/span&gt;

Prompt: "Re-run &lt;span class="nt"&gt;&amp;lt;spec&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; to verify the fix for [TEST-ID]."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Debugger → Explorer (stale DOM)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Handoff to cypress-browser-explorer&lt;/span&gt;

Prompt: "Re-explore &lt;span class="nt"&gt;&amp;lt;URL&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; because selectors are stale for &lt;span class="nt"&gt;&amp;lt;spec&amp;gt;&lt;/span&gt;. Return updated report to builder."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explorer Report Checklist
&lt;/h3&gt;

&lt;p&gt;When using the explorer agent, require a report that includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scope source&lt;/strong&gt;: Ticket, pasted steps, or URL/feature&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flow summary&lt;/strong&gt;: Scoped path, completion or blocked state&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;URL map&lt;/strong&gt;: Ordered URLs visited&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Selector inventory&lt;/strong&gt;: Element, purpose, selector, stability rating&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network map&lt;/strong&gt;: Method, pattern, suggested intercept alias&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test strategy&lt;/strong&gt;: E2E vs shift-left rationale per scenario&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notes&lt;/strong&gt;: Gaps, fragile selectors, missing test hooks&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Steering the Harness: How to Keep Agents Aligned
&lt;/h3&gt;

&lt;p&gt;Rather than personally inspecting what the agents produce, we can make them better at producing it. The collection of specifications, quality checks, and workflow guidance that control different levels of loops inside the how loop is the agent's harness. The emerging practice of building and maintaining these harnesses, Harness Engineering, is how humans work on the loop.&lt;/p&gt;

&lt;p&gt;This is working "on the loop" rather than just "in the loop." You're not micromanaging every output. You're improving the harness so agents naturally produce better results.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Practical steps:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scope every request&lt;/strong&gt;: URL, role, numbered steps, or ticket excerpt. The explorer especially needs to know what path to follow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encode standards in the repo&lt;/strong&gt;: Lint rules, skills files, and agent instructions should match. Otherwise the model follows whatever file it read most recently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use explicit handoffs&lt;/strong&gt;: Paste the structured blocks so the next agent gets data, not a summary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review diffs like any PR&lt;/strong&gt;: Generated specs need scrutiny, especially auth, network mocks, and assertions on money or permissions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep secrets out of chat&lt;/strong&gt;: Credentials belong in .env or your secret manager.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Turn fixes into constraints&lt;/strong&gt;: When an agent makes a mistake, you take the time to engineer a solution such that the agent never makes that mistake again. Add a lint rule, update the instructions, or create a check.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Review Gates: Keeping Humans on the Loop
&lt;/h3&gt;

&lt;p&gt;Agents execute evaluations automatically, but human oversight remains important for initial calibration and quality validation. Keep humans at judgment points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;After build&lt;/strong&gt;: Review spec structure, selector quality, and assertion coverage before treating a run as final.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After green&lt;/strong&gt;: Quick coverage and risk check before merge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After repeated debug failure&lt;/strong&gt;: If the same failure persists after three fix attempts, escalate to a person.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The agents handle the repetitive cycle. Engineers keep the judgment calls.&lt;/p&gt;




&lt;h3&gt;
  
  
  Team-Owned Content
&lt;/h3&gt;

&lt;p&gt;The harness above doesn't define these items. Your team documents them in skills, rules, or extended agent files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authentication flows, secrets file layout, and commands patterns&lt;/li&gt;
&lt;li&gt;Exact run commands (local vs Docker), CI script names&lt;/li&gt;
&lt;li&gt;Tag/grep filters, base URLs per environment&lt;/li&gt;
&lt;li&gt;Selector policy beyond "prefer stable hooks" (&lt;code&gt;data-*&lt;/code&gt;, roles, aria)&lt;/li&gt;
&lt;li&gt;Test ID formats, coverage scripts, Lint/Cypress config conventions&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Why This Approach Works
&lt;/h3&gt;

&lt;p&gt;The principles from Anthropic and Martin Fowler's research explain why the four-agent pattern is effective:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Constraints as multipliers:&lt;/strong&gt; Paradoxically, constraining the solution space makes agents more productive, not less. When an agent can generate anything, it wastes tokens exploring dead ends.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structured artifacts bridge context&lt;/strong&gt;: Structured progress files and feature lists let a new agent quickly understand the state of work, analogous to a shift handoff between engineers who've never met.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feedback loops catch issues early&lt;/strong&gt;: Run follows build. Debug follows failure. Re-explore only when needed. This order cuts rework.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clean escalation prevents endless retries&lt;/strong&gt;: If the DOM is wrong, hand to explorer. If three fixes fail, hand to a human. No guessing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The harness evolves&lt;/strong&gt;: Coding agents make it much cheaper to build more custom controls and more custom static analysis. Agents can help write structural tests, generate draft rules from observed patterns, scaffold custom linters, or create how-to guides from codebase archaeology.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Implementing This in Cursor with Subagents and Browser
&lt;/h3&gt;

&lt;p&gt;The four-agent workflow maps to four Cursor subagents: one markdown file per role under &lt;code&gt;.cursor/agents/&lt;/code&gt;, each with YAML frontmatter (name, description, model and any optional fields you need) plus a focused instructions and prompt body. How you create them is always the same—only the name, description, and instructions change to match explorer, builder, runner, or debugger.&lt;/p&gt;

&lt;p&gt;Below is one example (the browser explorer). The other three files use the identical shape; plug in the responsibilities from the agent table and handoff templates earlier in this article instead of pasting four full prompts here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cypress-browser-explorer&lt;/span&gt;
&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;inherit&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Explores the application UI using browser tools to discover selectors, network calls, and page flows for Cypress test development. Use when exploring a new feature, finding selectors, mapping user flows, building new tests, or when the user says to explore a page. ALWAYS launch the browser - never assume selectors without navigating and snapshotting.&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

You are a browser exploration specialist for E2E tests using Cypress.

When invoked:
&lt;span class="p"&gt;
1.&lt;/span&gt; &lt;span class="gs"&gt;**Authenticate**&lt;/span&gt; if the target page requires login (see above)
&lt;span class="p"&gt;2.&lt;/span&gt; &lt;span class="gs"&gt;**Navigate**&lt;/span&gt; to the target URL or flow entry point
&lt;span class="p"&gt;3.&lt;/span&gt; &lt;span class="gs"&gt;**Take a snapshot**&lt;/span&gt; to capture the page structure
&lt;span class="p"&gt;4.&lt;/span&gt; &lt;span class="gs"&gt;**Follow the exploration checklist**&lt;/span&gt; below for every flow

&lt;span class="gu"&gt;## Exploration Checklist&lt;/span&gt;

&lt;span class="gu"&gt;### Page URLs&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Record the entry page URL
&lt;span class="p"&gt;-&lt;/span&gt; Navigate through each step of the flow, recording intermediate URLs
&lt;span class="p"&gt;-&lt;/span&gt; Record the confirmation/success page URL

&lt;span class="gu"&gt;### Selectors (capture in priority order)&lt;/span&gt;
Priority order:
&lt;span class="p"&gt;1.&lt;/span&gt; &lt;span class="sb"&gt;`[data-cy]`&lt;/span&gt;, &lt;span class="sb"&gt;`[data-test]`&lt;/span&gt;, &lt;span class="sb"&gt;`[data-testid]`&lt;/span&gt; -- purpose-built for testing
&lt;span class="p"&gt;2.&lt;/span&gt; Any other &lt;span class="sb"&gt;`[data-*]`&lt;/span&gt; attribute -- stable, not styling-dependent
&lt;span class="p"&gt;3.&lt;/span&gt; Any &lt;span class="sb"&gt;`[test-*]`&lt;/span&gt; attribute (e.g. &lt;span class="sb"&gt;`test-auto`&lt;/span&gt;, &lt;span class="sb"&gt;`test-id`&lt;/span&gt;) -- also for testing
&lt;span class="p"&gt;4.&lt;/span&gt; &lt;span class="sb"&gt;`[role="..."]`&lt;/span&gt;, &lt;span class="sb"&gt;`[aria-label="..."]`&lt;/span&gt;, &lt;span class="sb"&gt;`[aria-labelledby]`&lt;/span&gt; -- semantic/accessible
&lt;span class="p"&gt;5.&lt;/span&gt; &lt;span class="sb"&gt;`label[for="..."]`&lt;/span&gt; + associated input -- form elements
&lt;span class="p"&gt;6.&lt;/span&gt; Stable visible text via &lt;span class="sb"&gt;`cy.contains()`&lt;/span&gt; -- only when text itself is the assertion
&lt;span class="p"&gt;7.&lt;/span&gt; Tag + attribute combos (e.g. &lt;span class="sb"&gt;`input[name="email"]`&lt;/span&gt;) -- last resort

&lt;span class="gs"&gt;**Never use**&lt;/span&gt;: CSS classes, generated IDs, tag names alone, XPath, positional selectors

&lt;span class="gu"&gt;### Network Calls&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Monitor network requests during the flow using browser tools
&lt;span class="p"&gt;-&lt;/span&gt; For each significant API call, record:
&lt;span class="p"&gt;  -&lt;/span&gt; HTTP method and URL pattern
&lt;span class="p"&gt;  -&lt;/span&gt; Suggested intercept alias (e.g., &lt;span class="sb"&gt;`get:cart-items`&lt;/span&gt;, &lt;span class="sb"&gt;`post:place-order`&lt;/span&gt;)
&lt;span class="p"&gt;  -&lt;/span&gt; Whether the response contains data needed for assertions
&lt;span class="p"&gt;-&lt;/span&gt; Pay attention to: auth calls, data fetching, form submissions, redirects

&lt;span class="gu"&gt;## Authentication&lt;/span&gt;

When the target page requires login (e.g. &lt;span class="sb"&gt;`/dashboard`&lt;/span&gt;, &lt;span class="sb"&gt;`/account`&lt;/span&gt;, any page that
redirects to &lt;span class="sb"&gt;`/login`&lt;/span&gt;), authenticate &lt;span class="gs"&gt;**before**&lt;/span&gt; exploring. Never ask the user
for credentials -- resolve them from project files.

&lt;span class="gu"&gt;### Credential Resolution (priority order)&lt;/span&gt;
&lt;span class="p"&gt;
1.&lt;/span&gt; &lt;span class="gs"&gt;**`.env`**&lt;/span&gt; file in the project root -- parse &lt;span class="sb"&gt;`KEY=VALUE`&lt;/span&gt; lines.
&lt;span class="p"&gt;2.&lt;/span&gt; &lt;span class="gs"&gt;**`cypress.env.json`**&lt;/span&gt; in the project root -- parse JSON object.

&lt;span class="gu"&gt;## Handoff to cypress-builder&lt;/span&gt;

Prompt: "Create cypress/e2e/[feature].cy.js using this exploration report:
&lt;span class="p"&gt;-&lt;/span&gt; Scope source: [quote from ticket/steps]
&lt;span class="p"&gt;-&lt;/span&gt; URL map: [ordered list]
&lt;span class="p"&gt;-&lt;/span&gt; Selector inventory: [element, purpose, selector, stability]
&lt;span class="p"&gt;-&lt;/span&gt; Network map: [method, pattern, suggested alias]
&lt;span class="p"&gt;-&lt;/span&gt; Draft spec: [snippet if applicable]
"

&lt;span class="gu"&gt;## Output Format&lt;/span&gt;

Return a structured report:
&lt;span class="p"&gt;1.&lt;/span&gt; &lt;span class="gs"&gt;**Scope source:**&lt;/span&gt; Ticket, pasted steps, or URL/feature 
&lt;span class="p"&gt;2.&lt;/span&gt; &lt;span class="gs"&gt;**Flow summary**&lt;/span&gt;: Scoped path, completion or blocked state
&lt;span class="p"&gt;3.&lt;/span&gt; &lt;span class="gs"&gt;**URL map:**&lt;/span&gt; Ordered URLs visited
&lt;span class="p"&gt;4.&lt;/span&gt; &lt;span class="gs"&gt;**Selector inventory:**&lt;/span&gt; Element, purpose, selector, stability rating
&lt;span class="p"&gt;5.&lt;/span&gt; &lt;span class="gs"&gt;**Network map:**&lt;/span&gt; Method, pattern, suggested intercept alias
&lt;span class="p"&gt;6.&lt;/span&gt; &lt;span class="gs"&gt;**Test strategy:**&lt;/span&gt; E2E vs shift-left rationale per scenario
&lt;span class="p"&gt;7.&lt;/span&gt; &lt;span class="gs"&gt;**Notes:**&lt;/span&gt; Gaps, fragile selectors, missing test hooks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save as &lt;code&gt;.cursor/agents/cypress-browser-explorer.md&lt;/code&gt;. Add &lt;code&gt;cypress-builder.md&lt;/code&gt;, &lt;code&gt;cypress-runner.md&lt;/code&gt;, and &lt;code&gt;cypress-debugger.md&lt;/code&gt; the same way, then invoke with &lt;code&gt;/cypress-browser-explorer&lt;/code&gt; (and so on) or let the parent agent delegate from each file’s description.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cursor's browser tool powers the explorer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The explorer subagent is where Cursor's built-in browser tool becomes essential. Rather than asking an engineer to describe what's on screen, the agent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Navigates&lt;/strong&gt; directly to URLs and follows multi-step flows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Takes snapshots&lt;/strong&gt; of live DOM state, capturing element structure, attributes, and text&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reads selectors&lt;/strong&gt; from the actual page -- &lt;code&gt;data-testid&lt;/code&gt;, ARIA roles, form labels -- instead of guessing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Captures network activity&lt;/strong&gt; to identify API calls that need &lt;code&gt;cy.intercept()&lt;/code&gt; aliases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means the exploration report is evidence-based from the start. Selectors come from the real DOM, not from memory or a other sources that may be out of date. When the debugger detects stale selectors and hands back to the explorer, the browser tool re-navigates and captures the current state -- closing the feedback loop with live data.&lt;/p&gt;




&lt;h3&gt;
  
  
  Why subagents fit this workflow
&lt;/h3&gt;

&lt;p&gt;Cursor subagents provide three properties that align with the harness model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Context isolation&lt;/strong&gt;: Each subagent gets its own context window. The explorer's noisy DOM snapshots and network logs don't pollute the builder's context. The debugger's stack traces don't crowd the runner. This is the same isolation principle the harness pattern demands.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parallel execution&lt;/strong&gt;: Multiple subagents run simultaneously, cutting wall-clock time on multi-spec work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structured handoffs&lt;/strong&gt;: A subagent returns a final message to the parent agent. That message is the handoff artifact -- the exploration report, the run summary, the debug notes. The templates in this article become the return format each subagent follows.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  The Orchestration Pattern
&lt;/h3&gt;

&lt;p&gt;The parent agent acts as an orchestrator, coordinating the four subagents in sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Invoke &lt;code&gt;/cypress-browser-explorer&lt;/code&gt; with URL and steps -- get exploration report&lt;/li&gt;
&lt;li&gt;Pass the report to &lt;code&gt;/cypress-builder&lt;/code&gt; -- get spec files&lt;/li&gt;
&lt;li&gt;Hand spec paths to &lt;code&gt;/cypress-runner&lt;/code&gt; -- get pass/fail summary&lt;/li&gt;
&lt;li&gt;On failure, send details to &lt;code&gt;/cypress-debugger&lt;/code&gt; -- get fixes, then back to step 3&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each handoff uses the structured templates from earlier in this article. The parent agent doesn't need deep knowledge of Cypress APIs—it routes data between specialists. This is the same orchestrator pattern Cursor's documentation recommends for complex workflows.&lt;/p&gt;

&lt;p&gt;If you use &lt;a href="https://docs.cypress.io/cloud/integrations/cloud-mcp" rel="noopener noreferrer"&gt;Cypress MCP&lt;/a&gt;, you can also point &lt;code&gt;/cypress-debugger&lt;/code&gt; at MCP tools &lt;strong&gt;to fetch failures from Cypress Cloud&lt;/strong&gt;. The debugger triages, patches the spec or support code, then uses the &lt;strong&gt;Debugger → Runner handoff&lt;/strong&gt; to re-run and stays in that loop until failures are addressed. That keeps run, fail, fetch, fix, re-run inside one workflow.&lt;/p&gt;




&lt;h3&gt;
  
  
  Closing
&lt;/h3&gt;

&lt;p&gt;Treating exploration, implementation, execution, and repair as separate agent roles mirrors how strong teams already work. The harness makes this pattern repeatable and easy to hand off inside the IDE.&lt;/p&gt;

&lt;p&gt;The largest efficiency win is the closed loop: run follows build, debug follows failure, re-explore only when the page structure actually changed.&lt;/p&gt;

&lt;p&gt;The most effective harnesses don't just constrain the agent. They create an environment where the agent naturally produces better output with less correction needed. This is a critical insight. The best harnesses aren't restrictive. They're enabling.&lt;/p&gt;

&lt;p&gt;Since shipping these specialized Cypress agents, I have hardly written tests by hand. The agents produce specs; I review them, merge when they are right, and when something drifts or misfires I adjust the agent definitions, skills, or prompts so the next run is better. The work shifts from typing cy.* to curating the harness -- continuous improvement on the automation itself, not just on individual tests.&lt;/p&gt;

&lt;p&gt;The loop is sequential, but each step stays small: one subagent, one job, less noise in context than doing it all in a single chat.&lt;/p&gt;

&lt;p&gt;Agent-driven development pays off when agents are blueprints you maintain. With Cursor subagents, those blueprints live in your repo as markdown files -- versioned, reviewable, and shared across the team. The browser tool gives the explorer agent direct access to your running app, so the entire loop from live UI to green test stays inside the IDE. Tighten instructions as your app and pipeline evolve. Keep guidance in the loop so automation stays trustworthy, not just clever.&lt;/p&gt;




&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Anthropic: &lt;a href="https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents" rel="noopener noreferrer"&gt;Effective Harnesses for Long-Running Agents&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Martin Fowler: &lt;a href="https://martinfowler.com/articles/harness-engineering.html" rel="noopener noreferrer"&gt;Harness engineering for coding agent users&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Cursor: &lt;a href="https://cursor.com/docs/subagents" rel="noopener noreferrer"&gt;Subagents&lt;/a&gt; and &lt;a href="https://cursor.com/docs/agent/tools/browser" rel="noopener noreferrer"&gt;Browser Tool&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>cursor</category>
      <category>cypress</category>
    </item>
  </channel>
</rss>
