skip to content

In Cypress, which languages can specs be written in, and why is that permanent?

level: juniorimportance: must knowfreq 72%

answer

  1. The runtime decides the language
  2. Same event loop as the app
  3. Listed under permanent trade-offs
  4. Types compile away before the browser
  5. cy.task() and cy.request() lead outward

basics

~20 s

JavaScript and TypeScript, and nothing else. Cypress evaluates spec code inside the browser alongside the application, so the only language it can ever run is the language of the web; the docs list this as a permanent trade-off.

solid answer

~40 s

Cypress specs are **JavaScript or TypeScript only**. Test code is evaluated inside the browser, in the same event loop as the application under test, so the only language available is the one the browser executes. Cypress's trade-offs guide files this under *permanent* trade-offs, next to the one-browser and single-superdomain rules — it is not a roadmap gap waiting on a Java or Python client. TypeScript is not a second language here: Cypress ships official type declarations, and the types are erased before the spec loads. The default `e2e.specPattern` reflects the ceiling, matching only `cypress/e2e/**/*.cy.{js,jsx,ts,tsx}`, and Cypress 16 narrowed it further by removing built-in CoffeeScript support. Code that genuinely cannot be JavaScript never enters the spec: it stays in Node behind `cy.task()`, or behind an HTTP endpoint you call with `cy.request()`.

go deeper

for a junior

Be ready to name the two languages in one sentence and give the reason: Cypress evaluates spec code inside the browser, so it can only run what a browser runs.

for a middle

Explain that TypeScript is erased before the spec loads, and that the default spec glob and the bundled preprocessor resolve only JavaScript and TypeScript file extensions.

for a senior

An interviewer wants the consequence: helper code written in another language cannot be imported into a spec, so decide where that logic is re-homed before a migration starts.

for a principal

Frame it as a permanent product boundary rather than a version gap, and be able to say what it costs an organisation whose test engineers do not write JavaScript.

## The runtime decides the language Most browser-test tools sit **outside** the browser. The test process speaks a wire protocol to a driver, the driver speaks to the browser, and because that protocol is language-neutral, a client for it can be written in any language at all. Cypress is not built that way. A Cypress spec is loaded **into the browser** and evaluated there, in the same event loop as the application under test. There is no serialization boundary and no protocol sitting between a command and an element, which is exactly where time-travel debugging and native access to application state come from. It also fixes the language: whatever the browser can execute is the entire set of options, and browsers execute JavaScript. Cypress's own trade-offs guide states it without hedging — the only language it will ever support is the language of the web. That sentence sits under the heading **Permanent trade-offs**, beside the one-browser-at-a-time rule and the single-superdomain rule, and it is deliberately kept apart from a shorter list of *temporary* restrictions the project hopes to address. ## Permanent versus temporary, as the project itself defines them Reading that split correctly is what separates a confident answer from a guess: - **Permanent** — commands run inside the browser, one browser at a time, one superdomain per test, and one language. - **Temporary** — there is no `cy.hover()` command, no native or mobile event support, and iframe switching is limited. A team weighing adoption should price the permanent list as a property of the tool and treat the temporary list as risk that may retire on its own. Waiting for a Java or Python client is waiting for something that was never on a roadmap. ## TypeScript does not add a second language "JavaScript and TypeScript" reads like two languages and is really one. Cypress ships **official type declarations** in its npm package, so `cy` and `Cypress` type-check without a community types package, and TypeScript 5.x, 6.x or 7.x is supported as of Cypress 16. Types are then erased before the spec ever reaches the browser. What TypeScript changes: - Mistakes surface as an editor error rather than as a failing run minutes later. - Editor completion covers every `cy.*` command with its parameters and its documentation. - Specs can import the application's own domain types, so an app-side change propagates into tests. What it does not change: the runtime, the command queue, retry-ability, or the ceiling itself. ## What Cypress 16 changed about accepted files Cypress 16 narrowed the accepted surface rather than widening it. Built-in **CoffeeScript support was removed**: the default `@cypress/webpack-batteries-included-preprocessor` no longer bundles `coffee-loader` or `coffeescript`, and `.coffee` is no longer resolved for spec files, support files, or `cy.fixture()`. A project that must keep it registers Cypress's own `@cypress/webpack-preprocessor` on the `file:preprocessor` node event with its own loader rule, and owns that toolchain from then on. | What Cypress loads | Where the default states it | |---|---| | `.js`, `.jsx`, `.ts`, `.tsx` specs | the default `e2e.specPattern`, `cypress/e2e/**/*.cy.{js,jsx,ts,tsx}` | | JavaScript and TypeScript with no setup | compiled by the bundled preprocessor out of the box | | `.coffee` | nothing — removed in 16, reachable only through your own webpack rule | ## Where non-JavaScript work actually goes The ceiling governs what runs *in the spec*, not what a test can cause to happen. Two commands lead out of the browser: 1. `cy.task()` hands an event name and a JSON-serialisable argument to a handler registered on the `task` node event, which runs in Node — the door to a database seed, a file, or a child process. 2. `cy.request()` makes an HTTP call that Cypress issues from Node rather than from the browser, so it bypasses CORS and never shows up in the browser's network panel — the door to anything that already lives behind an API. Neither makes another language available inside the spec. They move the work to where that language already lives and hand a value back. That is the shape of every honest answer here: the spec stays JavaScript or TypeScript permanently, and the rest of the stack is reached through a door rather than through an import.

  • Can a Cypress 16 spec still be written in CoffeeScript?
    Not out of the box. Cypress 16 removed built-in CoffeeScript support: the default `@cypress/webpack-batteries-included-preprocessor` no longer bundles `coffee-loader`, and `.coffee` is no longer resolved for specs, support files or `cy.fixture()`. You can register Cypress's `@cypress/webpack-preprocessor` on the `file:preprocessor` node event with your own loader rule, but you then own that toolchain. Converting the files to JavaScript or TypeScript is the recommended path.
  • Does Cypress shipping type declarations make TypeScript a first-class language?
    It makes TypeScript a first-class *authoring* experience. Cypress publishes official declarations with its npm package, so `cy` and `Cypress` type-check with no community types package, and TypeScript 5.x, 6.x or 7.x is supported. At runtime it is still JavaScript: types are erased before the spec reaches the browser, so TypeScript widens what the editor can tell you, not what the ceiling allows.

An out-of-process runner is a driver radioing instructions to a car it follows. Cypress is a passenger already sitting inside it, and a passenger can only speak whatever language is spoken in the car.

saying these in an interview costs you the question

  • Says a Java or Python Cypress client is planned
  • Treats TypeScript as a separate supported runtime
  • Calls the JavaScript-only limit a temporary gap
  • Believes spec code runs in Node beside the browser
  • Thinks a preprocessor could bundle any language