How to Set Up Create React App v3 with Cypress and TypeScript
Set up TypeScript end-to-end tests in a legacy Create React App v3 project, understand the old Cypress helper workflow, and keep its config separate from current guidance.
Direct answer: For the historical Create React App (CRA) v3 workflow, create or open a TypeScript CRA project, install Cypress, scaffold its legacy E2E folders, add the 2019 TypeScript preprocessor helper, rename the spec to .ts, and give Cypress a separate TypeScript config that overrides CRA’s root noEmit: true. This is a legacy recipe, not a current starter recommendation: Cypress says CRA is no longer actively maintained or supported. Existing applications can still be maintained, but pin versions and check the documentation for the Cypress version you install. The original CRA v3 tutorial documents the historical setup; Cypress’s migration guide provides the support caveat.
1. Choose the workflow before installing packages
This guide is for Cypress end-to-end (E2E) tests: the test opens the running React app in a browser using cy.visit() and checks the rendered experience. It is not Cypress component testing, which mounts individual components through a configured dev server. Keep those setups separate. See Cypress component framework configuration for the separate workflow.
| Situation | Recommended path in this guide |
|---|---|
| You maintain a project created with CRA v3 and need to reproduce its historic setup | Use the legacy section below, matching the Cypress and helper versions already in the project. |
| You have a working CRA project and are adding TypeScript E2E tests with a newer Cypress release | Use the current TypeScript guidance section; do not install the old helper by default. |
| You are starting a new app | Choose a currently supported framework, then follow Cypress documentation for that framework and your installed Cypress version. |
The 2019 tutorial uses cypress.json, cypress/integration, cypress/plugins, and cypress/support. Modern Cypress releases use different configuration and spec conventions. Do not assume a folder layout or configuration file from one generation applies unchanged to another.
2. Create or verify the CRA v3 TypeScript app
For historical reproduction, use the CRA v3 version and package manager appropriate to the project. The 2019 tutorial created the app with the TypeScript option. CRA’s old global installation command is not needed for most existing projects; use the repository’s locked dependencies whenever possible.
# Historical CRA v3 creation command from the 2019 tutorial
npx create-react-app@3 cra-cypress --typescript
cd cra-cypress
npm start
Confirm the app renders at http://localhost:3000 before introducing Cypress. Keep this development server running while using Cypress interactively. If maintaining an existing app, start there rather than regenerating it.
3. Install Cypress and scaffold the historical E2E structure
The 2019 tutorial installed Cypress as a development dependency and used a scaffolding utility to create the Cypress folder structure:
npm install --save-dev cypress
npx @bahmutov/cly init
The tutorial’s resulting legacy layout looked like this:
cypress.json
cypress/
fixtures/
integration/
spec.js
plugins/
index.js
support/
index.js
These names are historical. If the installed Cypress command opens a setup wizard or generates a different layout, use that release’s config conventions rather than forcing the old layout onto it. The old tutorial expected cypress.json and integration specs under cypress/integration.
4. Add TypeScript to the legacy Cypress specs
In the 2019 workflow, Cypress did not yet handle TypeScript specs in the same built-in way described by current documentation. The tutorial added @bahmutov/add-typescript-to-cypress and its Webpack peer dependency, then renamed the spec to TypeScript.
npm install --save-dev @bahmutov/add-typescript-to-cypress webpack
mv cypress/integration/spec.js cypress/integration/spec.ts
The helper-based approach is specifically tied to that historical Cypress generation. Do not add it automatically to a newer Cypress installation. Current Cypress ships its TypeScript declarations and describes a Cypress-specific tsconfig.json; consult the next section and the current TypeScript documentation for the version actually installed.
Write a first E2E spec
The tutorial’s basic test visits the running app and checks the default CRA link. This illustrates the structure; adjust the selector and expected text to match your app.
// cypress/integration/spec.ts
describe('CRA app', () => {
it('shows the Learn React link', () => {
cy.visit('http://localhost:3000');
cy.get('.App-link')
.should('be.visible')
.and('have.text', 'Learn React');
});
});
The test requires the CRA server to be running at the URL it visits. If a different port is used, change the URL. A more maintainable project can set a shared base URL in the configuration supported by its Cypress version.
5. Resolve CRA’s noEmit TypeScript configuration
CRA v3 generated a root tsconfig.json with "noEmit": true. In the legacy helper workflow, Cypress needed a separate config under the Cypress folder to define the test files and override that setting. The helper could create the file; inspect the result and make sure it covers your actual folder layout.
// cypress/tsconfig.json — historical helper-based configuration
{
"extends": "../tsconfig.json",
"include": [
"../node_modules/cypress",
"**/*.ts"
],
"compilerOptions": {
"noEmit": false
}
}
The key detail is the local override of noEmit. The root CRA setting is intended for the app build and should not be edited casually to solve a Cypress-only issue. If the helper generated a different include path, retain the Cypress declarations and include the Cypress TypeScript spec and support files you actually use. Restart the editor’s TypeScript service after changing project configs.
6. Current Cypress TypeScript setup in an existing CRA repository
For a newer Cypress version, start with Cypress’s current TypeScript instructions rather than the 2019 transpilation helper. The current docs state TypeScript 5.x, 6.x, or 7.x is required by the current release documentation and recommend a Cypress-specific config containing Cypress and Node types. Requirements change across Cypress releases, so verify the minimum TypeScript version for the exact version in your lockfile.
// cypress/tsconfig.json — current-style isolated Cypress types
{
"compilerOptions": {
"target": "es6",
"lib": ["es6", "dom"],
"sourceMap": true,
"types": ["cypress", "node"]
},
"include": ["**/*.ts"]
}
In projects that also use Jest, Cypress globals can conflict with Jest globals. Cypress’s documentation recommends isolating the Cypress config and excluding Cypress files and the Cypress config file from the root TypeScript project where appropriate:
// root tsconfig.json: preserve existing CRA options and merge these exclusions
{
"exclude": ["cypress.config.ts", "cypress", "node_modules"]
}
Do not replace the entire CRA root config with this abbreviated fragment; merge the exclusions with its existing compiler options and includes. Current Cypress configuration files can be TypeScript, but module format and extension have rules of their own. Follow the installed release’s config processing guidance if adding cypress.config.ts, .mts, or .cts.
7. Run the test and add project scripts
For the legacy Cypress command flow, run the app in one terminal and open the Cypress runner in another:
# Terminal 1
npm start
# Terminal 2
npx cypress open
For a headless run in CI or a terminal, use:
npx cypress run
Optionally record convenient scripts in package.json. These commands invoke Cypress but do not start CRA; your app server must be running unless your CI job starts it separately.
{
"scripts": {
"start": "react-scripts start",
"cypress:open": "cypress open",
"cypress:run": "cypress run"
}
}
Do not add a modern cypress.config example to a project still using the old cypress.json setup without first migrating its Cypress configuration. Conversely, a current Cypress project should follow current config conventions and not copy a legacy plugin index blindly.
8. Useful configuration choices and test design
Keep configuration limited to what your tests need and use the option names supported by the installed Cypress generation.
- Base URL: Set it once in the Cypress configuration for the installed version, then visit app-relative paths such as
/. This avoids repeating host and port strings. - Test location: Legacy projects may use
cypress/integration; modern projects commonly use a different default spec pattern. Match the configured pattern. - Support files: Put reusable commands and setup in the support file recognized by your Cypress version.
- Environment-specific URLs: Keep local, preview, and CI origins configurable; do not bake credentials or private tokens into specs.
- Selectors: Prefer stable application selectors such as dedicated test attributes when available. Avoid relying on styling classes that may change as the interface is restyled.
- Assertions: Assert user-visible results and wait through Cypress’s retrying query/assertion behavior rather than adding arbitrary sleeps for ordinary rendering.
- Network-dependent flows: Stub responses when the test’s purpose is UI behavior; use a controlled test backend when the purpose includes the real integration.
These are test-design practices, not additional CRA-specific configuration requirements. For any config option, use the reference matching the installed Cypress version: Cypress configuration.
9. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| TypeScript spec is not discovered | The filename is outside the configured spec pattern, or the folder belongs to a different Cypress generation. | Check the installed Cypress version and its spec pattern. For the historical recipe, confirm the file is cypress/integration/spec.ts. |
cy, describe, or Cypress commands have missing types |
The editor is loading the root CRA project rather than cypress/tsconfig.json, or Cypress types are absent. |
Check the Cypress config’s include/types, open the file under the Cypress folder, then restart the IDE TypeScript server. |
| Jest and Cypress types conflict | Both test runners contribute global declarations to one TypeScript project. | Use separate TypeScript configs; scope Cypress types to its folder and exclude Cypress files/config from the root project where appropriate. |
| Historical helper fails under a newer Cypress | The tutorial’s preprocessor, directory layout, or configuration API belongs to an older Cypress generation. | Do not combine generations. Either pin a compatible legacy toolchain for maintenance or remove the helper and follow the installed Cypress version’s TypeScript setup. |
| Browser opens but the test cannot connect | The app server is stopped, Cypress is visiting the wrong port, or a CI job has not started the server. | Start CRA first, verify its actual local URL, and configure the same origin for the test run. |
| CRA says “There might be a problem with the project dependency tree” | The 2019 article observed a babel-loader version mismatch after installing its helper dependencies. |
Inspect the dependency tree and resolve the actual version conflict first. The historical tutorial suggested SKIP_PREFLIGHT_CHECK=true in a root .env, but that is not established as a universal or current fix; do not suppress the check without understanding the mismatch. |
| Config file fails to load after switching to TypeScript | The config extension, package type, module option, or Cypress version do not agree. |
Use the current Cypress docs for the installed version and align the extension and module format; E2E test TypeScript support and config-file loading are separate concerns. |
| Old plugin file is ignored or rejected | Plugin/config conventions changed after the historical plugins/index.js layout. |
Use the configuration lifecycle documented for the installed Cypress version and migrate old hooks deliberately. |
10. Performance, reliability, and cost notes
TypeScript itself does not make E2E runs faster. Keep the suite reliable by testing a small number of meaningful user journeys, using stable selectors, controlling external dependencies, and avoiding fixed delays where Cypress can retry an assertion. Starting the app server once per job and reusing a deterministic test environment reduces setup variability. The provided research does not establish comparative runtime benchmarks for CRA v3 and other frameworks, so performance should be measured in your own CI environment.
Cypress and TypeScript can be installed as development dependencies; the exact package versions and their compatibility affect maintenance cost. The historical helper may add extra build-tool dependencies and coupling. For a maintained legacy app, lock the dependency graph and upgrade one toolchain layer at a time. For a new app, the ongoing support status of the framework should be part of the choice.
11. Frequently asked questions
Can I still use Cypress with an existing CRA v3 app?
Yes, the recipe is useful for maintaining or reproducing an existing project, but CRA is no longer actively maintained or supported according to Cypress’s migration guide.
Do current Cypress releases still require @bahmutov/add-typescript-to-cypress?
The current Cypress TypeScript docs describe Cypress’s own declarations and a Cypress-specific tsconfig. The helper belongs to the 2019 workflow; check your installed Cypress version before adding any preprocessor.
Is this a component testing setup?
No. It visits a running CRA site in a browser, so it is E2E testing. Component testing mounts components with a dev-server configuration.
Should I set noEmit to false in the root CRA config?
The historic workaround overrides the option in the Cypress-specific config. Keep app and test TypeScript concerns scoped separately unless your build tool documentation requires otherwise.
Or skip the browser setup
If the task is to capture what the running app or a public page looks like, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF; it does not replace Cypress assertions or E2E coverage.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo API documentation for parameters and response handling. Cookie banners, popups, and chat widgets are removed before the shot; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The Free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000. ScreenshotNeo also supports full-page captures, element selection, PDF, custom CSS and JavaScript, and async and bulk capture.


