Skip to main content

Command Palette

Search for a command to run...

Breaking the Browser Sandbox: A Complete Guide to Cypress Custom Tasks with TypeScript

Published
4 min read

ON THIS PAGE

  • Why Do We Need cy.task?

  • When Should You Use It?

  • The Critical Difference: cy.task vs. Helper Functions

  • Step-by-Step Implementation Guide

  • Step 1: cypress.config.ts (The Backend Logic)

  • Step 2: cypress/support/commands.ts (The Command Wrapper)

  • Step 3: cypress/support/index.d.ts (The TypeScript Definition)

  • How to Use It

  • Summary


As SDETs, we love Cypress for its speed and stability. However, because Cypress runs directly inside the browser, it follows strict security rules. It is "sandboxed," meaning it cannot reach outside the browser to touch your operating system, file system, or database directly.

This is where cy.task() comes in. It acts as a bridge between the Browser (where your tests run) and the Node.js process (where Cypress is controlled).

Why Do We Need cy.task?

Imagine you are testing a "Export to PDF" feature.

  1. Browser: You click the "Download" button.

  2. Browser: Cypress sees the button click.

  3. Reality: The file is saved to your computer's Downloads folder.

  4. Problem: The browser cannot look into your computer's hard drive to confirm the file is there.

cy.task allows you to execute Node.js code to check that file, seed a database, or even query a mail server, and return the result to the browser.

When Should You Use It?

You should use cy.task whenever you need to perform an action that a browser is security-restricted from doing:

  • Database Seeding: Inserting test data directly into SQL/NoSQL databases before a test (e.g., creating a user).

  • File System (fs): Checking if a file exists, reading a file's content, or deleting downloaded files.

  • Server-Side Validation: checking if an email was actually sent (e.g., checking Mailosaur or a local SMTP server).

  • OS Operations: Running a shell command.

The Critical Difference: cy.task vs. Helper Functions

A common question is: "Why can't I just write a normal TypeScript function in utils.ts?"

Feature

Helper Function (utils.ts)

Cypress Task (cy.task)

Where it runs

Runs in the Browser

Runs in Node.js (Backend)

Capabilities

strict JavaScript (Math, formatting strings, API calls via fetch)

Full OS access (File System, Database connections, Shell commands)

Example

formatDate(), generateRandomEmail()

queryDatabase(), readFileFromDisk()

Can it use fs?

No (Browser crashes)

Yes

If your logic is pure JavaScript (like calculating a sum), use a Helper. If your logic needs to touch the system (like reading a file), use a Task.


Step-by-Step Implementation Guide

To implement a task in TypeScript properly, we need to touch three files. Let's create a task that checks if a file exists.

Step 1: cypress.config.ts (The Backend Logic)

This is where the actual Node.js code lives. We use the setupNodeEvents function.

import { defineConfig } from "cypress";
import * as fs from "fs"; // Import Node.js File System module

export default defineConfig({
  e2e: {
    setupNodeEvents(on, config) {
      // Register the task here
      on("task", {
        checkFileExists(filePath: string) {
          // This code runs in Node.js
          if (fs.existsSync(filePath)) {
            return true;
          }
          return null; // Tasks must return a value or null (not undefined)
        },
      });
    },
  },
});

Step 2: cypress/support/commands.ts (The Command Wrapper)

Calling cy.task('checkFileExists', path) every time is verbose. Let's wrap it in a custom command for cleaner code.

Cypress.Commands.add("verifyFileExists", (filePath: string) => {
  cy.task("checkFileExists", filePath).then((exists) => {
    // We can add an assertion here to make the test cleaner
    expect(exists).to.be.true;
  });
});

Step 3: cypress/support/index.d.ts (The TypeScript Definition)

Since we are using TypeScript, we must tell the compiler that our new command verifyFileExists exists. Without this, your IDE will show a red error line.

/// <reference types="cypress" />

declare namespace Cypress {
  interface Chainable {
    /**
     * Custom command to check if a file exists on the disk.
     * @example cy.verifyFileExists('downloads/invoice.pdf')
     */
    verifyFileExists(filePath: string): Chainable<void>;
  }
}

How to Use It

Now, you can use your new command in any spec file just like a native Cypress command.

File: cypress/e2e/download-test.cy.ts

describe("File Download Verification", () => {
  it("should download the invoice successfully", () => {
    // 1. Perform the action in the browser
    cy.get(".btn-download-invoice").click();

    // 2. Wait briefly for the download to complete
    cy.wait(2000);

    // 3. Verify the file exists on the OS level
    const filePath = "cypress/downloads/invoice.pdf";
    cy.verifyFileExists(filePath); 
  });
});

Summary

  1. Define the Node.js logic in cypress.config.ts (using on('task')).

  2. Wrap the task in cypress/support/commands.ts for ease of use.

  3. Type the command in cypress/support/index.d.ts to keep TypeScript happy.

By mastering cy.task, you unlock the ability to test the entire application lifecycle, not just the frontend UI.

Happy Testing!

#cypress #typescript #sdet #testing #automation