Cypress plugins are not just extensions; they’re the fundamental mechanism by which Cypress interacts with the outside world, allowing it to do things beyond just driving a browser.

Let’s watch a plugin in action. We’ll use cypress-grep to selectively run tests based on tags.

First, install it:

npm install --save-dev cypress-grep

Then, configure it in your cypress.config.js (or cypress.config.ts):

const { defineConfig } = require('cypress')

module.exports = defineConfig({
  e2e: {
    setupNodeEvents(on, config) {
      require('cypress-grep')(on, config)
      // IMPORTANT: return the config object
      return config
    },
  },
})

Now, in your test files, you can add tags:

describe('Login Feature', () => {
  it('should log in successfully', { tags: ['smoke', 'login'] }, () => {
    cy.visit('/login')
    cy.get('input[name="username"]').type('user')
    cy.get('input[name="password"]').type('password')
    cy.get('button[type="submit"]').click()
    cy.url().should('include', '/dashboard')
  })

  it('should show an error on invalid credentials', { tags: ['login'] }, () => {
    cy.visit('/login')
    cy.get('input[name="username"]').type('wronguser')
    cy.get('input[name="password"]').type('wrongpassword')
    cy.get('button[type="submit"]').click()
    cy.get('.error-message').should('be.visible')
  })
})

describe('Dashboard Feature', () => {
  it('should display user profile', { tags: ['smoke', 'dashboard'] }, () => {
    // Assume logged in for this test
    cy.visit('/dashboard')
    cy.get('.profile-section').should('be.visible')
  })
})

To run only the tests tagged with smoke, you’d use the command:

npx cypress run --env grep='smoke'

Or, in interactive mode:

npx cypress open --env grep='smoke'

Cypress plugins operate within the setupNodeEvents function in your cypress.config.js. This function is executed once when Cypress starts up. The on object is an event emitter, and you use on('event-name', handler) to hook into Cypress’s internal events. The config object is the Cypress configuration, which you can modify and must return. This setupNodeEvents function is the gateway for plugins to modify Cypress’s behavior, inject custom commands, or interact with the Node.js environment.

The primary problem plugins solve is extending Cypress’s capabilities beyond what’s built-in. This includes:

  • Custom Commands: Adding reusable steps like cy.login('user', 'password') that encapsulate complex sequences.
  • Environment Variables: Loading .env files or fetching secrets securely.
  • Data Generation: Creating realistic test data on the fly.
  • Reporting: Generating custom test reports in various formats (HTML, JSON, etc.).
  • Third-Party Integrations: Connecting with CI/CD pipelines, API mocking tools, or other services.
  • Test Filtering/Selection: As seen with cypress-grep, controlling which tests run.

The setupNodeEvents function is crucial because it allows plugins to register their functionality before any tests begin execution. When Cypress encounters a command or event that a plugin has registered to handle, it delegates that task to the plugin’s implementation. For instance, cypress-grep listens for the spec event, which fires for each test file Cypress is about to load. It inspects the test file’s metadata (like tags) and decides whether Cypress should proceed with loading that file or skip it entirely. This happens in the Node.js process, not the browser, which is why it’s so efficient for filtering and setup tasks.

Many people think of plugins as just adding new cy. commands, but their power is much broader. They can intercept network requests, modify the test runner’s UI, or even interact with the file system. The on object provides access to a range of events, such as before:run, after:run, before:spec, after:spec, task, and file:preprocessor. By listening to these, plugins can influence the entire test execution lifecycle. For example, a plugin could use on('before:run', ...) to set up a database before any tests start and on('after:run', ...) to tear it down afterward, ensuring a clean state for each test run. The task event is particularly useful for running arbitrary Node.js code from within your test specs using cy.task('yourTaskName', { arg1: 'value' }).

The most surprising thing about Cypress plugins is that they are not limited to extending the cy object. You can define custom commands that don’t start with cy., as long as they are registered correctly within setupNodeEvents and you know how to invoke them. For instance, a plugin could register a function under a custom event name, and you could trigger it from your test using cy.wrap(null).then(() => cy.task('myCustomTask')) or by directly calling the function if it’s exposed in a specific way. This flexibility allows for deeply integrated custom workflows that might not fit the typical command pattern.

The next step after mastering plugins is understanding how to write your own custom Cypress commands that are discoverable and usable across your entire project.

Want structured learning?

Take the full Cypress course →