Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Use Excel to define Cypress scenarios by parsing the workbook in Node.js while Cypress loads its configuration, then expose the resulting rows with config.expose. In the spec, read them with Cypress.expose() and synchronously create one it() block per row. This timing is essential: Cypress builds the suite when the spec is evaluated, so asynchronous commands such as cy.fixture() and cy.task() cannot create tests after loading.

The architecture that works

A reliable Excel-driven suite has three stages:

  1. Node configuration: read the .xlsx file with Node’s filesystem API and parse it with SheetJS.
  2. Configuration handoff: place only the normalized scenario data your browser code needs in config.expose.
  3. Spec evaluation: call Cypress.expose('scenarios') and synchronously loop over the rows to define the tests.

The browser then executes ordinary Cypress commands inside each generated test. Excel is a source of test definitions, not a runtime replacement for Cypress’s command queue.

Install the parser and arrange the workbook

Install SheetJS

Install the xlsx package in the project that runs Cypress:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
npm install --save-dev xlsx

Confirm the package’s current installation guidance and compatibility with the Node.js and Cypress versions used by your project. Keep the workbook at a predictable path such as cypress/fixtures/scenarios.xlsx.

#1 Best Overall
Sale
The Microsoft Office 365 Bible: The Most Updated and Complete Guide to Excel, Word, PowerPoint, Outlook, OneNote, OneDrive, Teams, Access, and Publisher from Beginners to Advanced
  • The Microsoft Office 365 Bible: The Most Updated and Complete Guide to Excel, Word, PowerPoint, Outlook, OneNote, OneDrive, Teams, Access, and Publisher from Beginners to Advanced
  • ABIS BOOK

Design a stable worksheet

Put column headers in the first row. For example:

title username password expectedMessage
Valid login [email protected] correct-password Welcome, Alice
Unknown user [email protected] correct-password Invalid credentials

Use a unique, descriptive title for every row. Treat headers as an interface: normalize them or reject the workbook when a required column is missing. Do not place production credentials or other secrets in this file, especially if it is committed to source control.

Parse Excel in cypress.config.js

This configuration reads the first worksheet, converts rows to objects, validates the shape, and exposes the safe values to the spec. The validation is deliberately explicit so an empty row or misspelled header fails before a confusing test is generated.

const { defineConfig } = require('cypress')
const XLSX = require('xlsx')
const { readFileSync } = require('fs')

const requiredColumns = ['title', 'username', 'password', 'expectedMessage']

function readScenarios() {
  const workbook = XLSX.read(
    readFileSync('cypress/fixtures/scenarios.xlsx'),
    { type: 'buffer' }
  )

  if (!workbook.SheetNames.length) {
    throw new Error('scenarios.xlsx contains no worksheets')
  }

  const firstSheet = workbook.Sheets[workbook.SheetNames[0]]
  const rawRows = XLSX.utils.sheet_to_json(firstSheet, {
    defval: '',
    raw: false,
  })

  if (!rawRows.length) {
    throw new Error('The first worksheet contains no scenario rows')
  }

  const rows = rawRows.map((row, index) => {
    const normalized = Object.fromEntries(
      Object.entries(row).map(([key, value]) => [
        key.trim(),
        typeof value === 'string' ? value.trim() : value,
      ])
    )

    for (const column of requiredColumns) {
      if (!normalized[column]) {
        throw new Error(`Row ${index + 2} is missing ${column}`)
      }
    }

    return {
      title: String(normalized.title),
      username: String(normalized.username),
      password: String(normalized.password),
      expectedMessage: String(normalized.expectedMessage),
    }
  })

  const titles = new Set()
  for (const row of rows) {
    if (titles.has(row.title)) {
      throw new Error(`Duplicate scenario title: ${row.title}`)
    }
    titles.add(row.title)
  }

  return rows
}

module.exports = defineConfig({
  e2e: {
    setupNodeEvents(on, config) {
      const scenarios = readScenarios()
      config.expose = {
        ...config.expose,
        scenarios,
      }
      return config
    },
  },
})

XLSX.read parses the workbook buffer, while sheet_to_json turns the selected worksheet into JavaScript objects. Selecting the sheet by name instead of position is also reasonable when a workbook has multiple tabs; fail clearly if the expected name is absent.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Generate one Cypress test per Excel row

Because Cypress.expose() returns an ordinary value during spec evaluation, the loop below runs synchronously and creates the complete suite before Cypress starts executing commands.

const scenarios = Cypress.expose('scenarios') || []

describe('Excel-driven login scenarios', () => {
  scenarios.forEach((scenario) => {
    it(scenario.title, () => {
      cy.visit('/login')
      cy.get('[data-testid="username"]').clear().type(scenario.username)
      cy.get('[data-testid="password"]').clear().type(scenario.password)
      cy.get('[data-testid="submit"]').click()
      cy.contains(scenario.expectedMessage).should('be.visible')
    })
  })
})

Use selectors that are stable in your application, preferably dedicated data-testid attributes. Keep each row independently understandable: the title should identify the case, and the expected result should be explicit rather than inferred from row order.

Why cy.fixture() cannot create the tests

Cypress evaluates describe() and it() while loading the spec. Cypress commands are queued and resolve later. Therefore this pattern does not work:

cy.fixture('scenarios.xlsx').then((rows) => {
  rows.forEach((row) => {
    it(row.title, () => {})
  })
})

Fixtures are useful for fixed input consumed inside an already-defined test. Cypress recognizes CSV as a fixture extension and returns it as text by default; an .xlsx fixture is not automatically parsed into workbook rows. For Excel-driven test generation, parse in Node configuration and expose the result.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choose the right data-loading method

Situation Recommended method Reason and limitation
Checked-in, stable data used during a test cy.fixture() Intended for fixed test inputs and cached after the first read; it does not create new test blocks asynchronously.
Data determines which tests exist Node parsing in setupNodeEvents plus Cypress.expose() Rows are available while the spec is loaded, allowing synchronous it() definitions.
File changes during a run or is produced by the application cy.readFile() Reads during test execution and retries while assertions are pending; it is too late to define the suite.
Large or Node-only processing cy.task() Keeps filesystem and parsing work in Node and returns only needed results; asynchronous tasks cannot generate test blocks.
Testing an upload control Workbook fixture plus .selectFile() Tests the application’s upload behavior. It is separate from using workbook rows to generate scenarios.

Security, size and determinism safeguards

  • Expose the minimum: values in config.expose are accessible in the browser context. Never expose passwords, API keys, tokens or customer secrets. Prefer opaque test identifiers and retrieve secrets through a protected server-side mechanism when necessary.
  • Filter in Node: for a large workbook, select the worksheet, columns and rows in configuration. Passing the entire workbook to the browser increases memory use and exposes unnecessary data.
  • Validate before generation: reject missing headers, blank required cells, duplicate titles, unexpected worksheet shape and an empty result. A configuration error is easier to diagnose than dozens of skipped or malformed tests.
  • Normalize deliberately: trim headers and string fields, but preserve values such as leading-zero IDs when they are meaningful. Decide whether numbers should be read as numbers or formatted text before parsing.
  • Do not depend on row order: give every case a stable key or title. If order matters for a business workflow, state that dependency explicitly and isolate the ordering logic.
  • Version the input: review workbook changes like code. A changed column name or formula result can change the suite even when the spec is untouched.

Handling multiple worksheets and richer scenarios

Select a named worksheet

When the workbook contains tabs such as Login, Checkout and Search, select deliberately:

const sheetName = 'Login'
const sheet = workbook.Sheets[sheetName]
if (!sheet) throw new Error(`Worksheet not found: ${sheetName}`)
const rows = XLSX.utils.sheet_to_json(sheet, { defval: '', raw: false })

Map columns to an internal contract

Keep spreadsheet labels separate from test code by mapping them once:

return {
  title: String(row['Case name']).trim(),
  path: String(row['Path']).trim(),
  method: String(row['Method']).trim().toUpperCase(),
  expectedStatus: Number(row['Expected status']),
}

Then the spec consumes scenario.path and scenario.expectedStatus without knowing how the worksheet is labeled. For optional columns, apply defaults in Node and document them.

Keep scenario logic readable

When rows represent several workflows, dispatch to small functions rather than placing a large conditional in every it() block:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
function runScenario(scenario) {
  if (scenario.kind === 'login') return runLogin(scenario)
  if (scenario.kind === 'checkout') return runCheckout(scenario)
  throw new Error(`Unknown scenario kind: ${scenario.kind}`)
}

describe('Excel scenarios', () => {
  scenarios.forEach((scenario) => {
    it(scenario.title, () => runScenario(scenario))
  })
})

Troubleshooting common failures

“Cannot find module xlsx”

Install xlsx in the package where Cypress runs, then verify the lockfile and workspace resolution. In monorepos, run the install from the Cypress project’s package rather than an unrelated parent directory.

The suite has zero tests

Check that the workbook path is relative to the project working directory, the first sheet contains a header row and at least one data row, and config.expose.scenarios is returned from setupNodeEvents. Add a configuration-time error for an empty array instead of silently producing an empty suite.

“Cypress.expose is undefined”

Use a Cypress version that supports the documented expose handoff and follow that version’s configuration format. Confirm the value is assigned to config.expose, not a similarly named custom property, and that the returned config is the one Cypress uses.

Excel dates or numbers look wrong

Excel stores dates and numeric cells in ways that may not match their displayed formatting. Decide whether the application needs a serial number, an ISO date or the formatted text, then normalize the value in Node. Use raw: false when formatted strings are preferable, and validate the resulting representation.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Special characters break test titles or inputs

Keep titles as strings and avoid constructing selectors from untrusted cell text. For passwords or punctuation-heavy values, pass the string directly to .type() and ensure the application accepts the characters. Escape only when building a selector or regular expression.

One bad row prevents all tests

That is usually preferable to running an incomplete suite. Report the worksheet row number in the validation error, fix the source data, and rerun. If your process requires partial execution, filter rows explicitly in Node and record which rows were excluded.

Tests are slow or memory-heavy

Parse once during configuration, expose only the selected fields and avoid passing the workbook object itself. Split very large datasets into suites or use a CI matrix. Do not call the parser separately inside every test.

Performance and reliability considerations

Workbook parsing happens once per Cypress configuration load, while browser commands run per scenario. The dominant cost is normally the application interaction, not converting a worksheet to objects. Keep the input deterministic so retries rerun the same cases. If the workbook is generated immediately before Cypress starts, ensure the generation step completes and the file is closed before configuration reads it.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For changing data, do not pretend a mutable file is a static fixture. Read it at the point the test needs it with cy.readFile(), or use a task for Node-side processing. If the changing data determines the number of cases, generate or update the workbook before launching Cypress so it is available during spec loading.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your workflow also needs screenshots of the pages under test, ScreenshotNeo can capture a URL through one HTTP request instead of maintaining browser automation for that artifact. It removes cookie-consent banners, newsletter popups and chat widgets before capture; bot checks, blank pages, timeouts, failed loads and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server provides take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients.

For a direct capture, see the ScreenshotNeo API documentation:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

The same request in Python:

import requests
r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

And Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const fs = require('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

ScreenshotNeo includes full-page and element capture, device presets, custom viewport and retina scale, PDF output, custom CSS and JavaScript, clicks, waits, request blocking, headers, cookies, user-agent, timezone, geolocation, transparent backgrounds, resizing, selectable caching TTLs, signed links, asynchronous jobs, webhooks, bulk capture of up to 100 URLs per call, usage reporting and an OpenAPI specification. Its parameter names are compatible with those used by other screenshot APIs, which can simplify migration.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; yearly billing provides two months free. Every feature is available on every plan. Create a free ScreenshotNeo account to get started.

FAQ

Can I generate tests from an Excel file created during the Cypress run?

Not in the same spec load. Generate the file before Cypress starts, or use runtime file-reading methods for data consumed by tests rather than for creating new test blocks.

Should I expose the workbook itself?

No. Parse and filter it in Node, then expose only the small, non-sensitive scenario objects required by the spec.

Is this approach suitable for testing an Excel upload feature?

Use a representative workbook fixture and .selectFile() to test upload behavior. That is a separate concern from using workbook rows to define the suite.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Frequently Asked Questions

Can I generate tests from an Excel file created during the Cypress run?

Not in the same spec load. Generate the file before Cypress starts, or use runtime file-reading methods for data consumed by tests rather than for creating new test blocks.

Should I expose the workbook itself?

No. Parse and filter it in Node, then expose only the small, non-sensitive scenario objects required by the spec.

Is this approach suitable for testing an Excel upload feature?

Use a representative workbook fixture and .selectFile() to test upload behavior. That is separate from using workbook rows to define the suite.

The Bottom Line

Parse Excel in Node during Cypress configuration, validate and minimize the rows, expose them with config.expose, and synchronously define the tests with Cypress.expose(). Use fixtures, cy.readFile() or cy.task() when the data’s job is different.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.