Q31 of 48 · Cypress
What is cy.task and when is it the right tool?
Short answer
Short answer: `cy.task(name, args)` runs a Node-side function defined in `setupNodeEvents`. Use it for things the browser can't do: seeding the database, reading files outside Cypress's reach, hitting internal Node APIs, sending external notifications. The browser-side test stays fast; Node handles the rest.
Detail
Cypress runs in two processes: the browser (your test code, with cy commands) and Node (the runner, plugins, and setupNodeEvents). cy.task is the bridge — it sends a message from the browser to Node, runs your registered Node function, and returns the result back to the browser test.
Register tasks in setupNodeEvents:
// cypress.config.ts
setupNodeEvents(on, config) {
on('task', {
seedUser({ email, role }) {
// Hit your database directly via a Node client
return db.users.insert({ email, role });
},
clearDownloads() {
fs.rmSync(path.join(__dirname, 'cypress/downloads'), { recursive: true, force: true });
fs.mkdirSync(path.join(__dirname, 'cypress/downloads'));
return null; // tasks must return non-undefined
},
log(message) {
console.log(message);
return null;
},
});
return config;
}
Use it in tests:
beforeEach(() => {
cy.task('seedUser', { email: 'alice@x.com', role: 'admin' });
});
When cy.task is the right tool:
- Database seeding — far faster than seeding via API endpoints, especially for setup that doesn't represent the test's intent.
- File system ops — clearing
downloads/, reading large fixtures, writing test artefacts. - Stateful operations across specs — coordinating shared state, reading env-specific secrets.
- Calls to internal services — message brokers, third-party SDK calls that don't belong in the browser.
When not to use it:
- Anything you could do in the browser with
cy.requestagainst your own API. Tasks are heavier and break the "test the contract" principle. - Logic that should be tested. Tasks are setup, not the system under test.
Tasks must return a non-undefined value (return null if you have nothing to report). They serialize args/returns as JSON, so don't pass functions or class instances.
// EXAMPLE
cypress.config.ts + spec
// cypress.config.ts
import { defineConfig } from 'cypress';
import { Pool } from 'pg';
const pool = new Pool({ connectionString: process.env.TEST_DB_URL });
export default defineConfig({
e2e: {
setupNodeEvents(on, config) {
on('task', {
async resetDb() {
await pool.query('TRUNCATE users, orders RESTART IDENTITY CASCADE');
return null;
},
async seedUser({ email, role }: { email: string; role: string }) {
const { rows } = await pool.query(
'INSERT INTO users(email, role) VALUES($1, $2) RETURNING id',
[email, role],
);
return rows[0].id;
},
});
},
},
});
// In a spec
beforeEach(() => {
cy.task('resetDb');
cy.task('seedUser', { email: 'admin@x.com', role: 'admin' }).then((userId) => {
cy.wrap(userId).as('adminId');
});
});