cypress: After Spec API TypeScript Code breaks the config.ts file - [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts"

Current behavior

If I use the code from the snippet here for TypeScript it breaks the project:

TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for /Users/george/Documents/core-cypress/cypress.config.ts
    at new NodeError (node:internal/errors:372:5)
    at Object.getFileProtocolModuleFormat [as file:] (node:internal/modules/esm/get_format:76:11)
    at defaultGetFormat (node:internal/modules/esm/get_format:118:38)
    at defaultLoad (node:internal/modules/esm/load:21:20)
    at ESMLoader.load (node:internal/modules/esm/loader:407:26)
    at ESMLoader.moduleProvider (node:internal/modules/esm/loader:326:22)
    at new ModuleJob (node:internal/modules/esm/module_job:66:26)
    at ESMLoader.#createModuleJob (node:internal/modules/esm/loader:345:17)
    at ESMLoader.getModuleJob (node:internal/modules/esm/loader:304:34)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:385:24)
    at async importModuleDynamicallyWrapper (node:internal/vm/module:437:15)
    at async loadFile (/Users/george/Library/Caches/Cypress/10.3.0/Cypress.app/Contents/Resources/app/packages/server/lib/plugins/child/run_require_async_child.js:106:14)
    at async EventEmitter.<anonymous> (/Users/george/Library/Caches/Cypress/10.3.0/Cypress.app/Contents/Resources/app/packages/server/lib/plugins/child/run_require_async_child.js:116:32)

Even just this line breaks the project: const del = require('del')

Desired behavior

I can run my tests and the videos are deleted if test passed.

Test code to reproduce

/* eslint @typescript-eslint/no-var-requires: "off" */
import { defineConfig } from "cypress";
import { checkGmail, CheckGmailParam } from "../core-cypress/cypress/plugins/checkGmail";
import * as path from "path";
const del = require('del')

export default defineConfig({
  e2e: {
    async setupNodeEvents(on, config) {
      const version = config.env.version || 'development'
      const configFile = await import(path.join(
        config.projectRoot,
        'cypress/config',
        `${version}.json`
      ));
      config.projectId = "5jgpns"
      config.baseUrl = configFile.baseUrl
      config.env = configFile.env
      config.defaultCommandTimeout = 10000
      config.chromeWebSecurity = false
      on("task", {
        async checkGmail(args: CheckGmailParam) {
          return await checkGmail(args);
        },
      });
      on('after:spec', (spec, results) => {
        if (results && results.stats.failures === 0 && results.video) {
          // `del()` returns a promise, so it's important to return it to ensure
          // deleting the video is finished before moving on
          return del(results.video)
        }
      })
      return config
    },
    reporter: 'mochawesome'
  },
});

Cypress Version

10.1.0

Other

Current snippet also isn’t 100% TypeScript/Eslint friendly because uses require instead of import and has unused parameter. Something like this should work right?

import { deleteAsync } from 'del'

on('after:spec', async (_spec, results) => {
        if (results && results.stats.failures === 0 && results.video) {
          // `del()` returns a promise, so it's important to return it to ensure
          // deleting the video is finished before moving on
          await deleteAsync(results.video)
        }
})

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Reactions: 3
  • Comments: 53 (37 by maintainers)

Most upvoted comments

Run any test with the above config file or just the module import in config file, super easy to reproduce.

  1. Yeah, esbuild works pretty well for our projects, although to support require() and friends, you need a custom banner configuration
  2. Right, this may be simple and maybe also good enough for fixing most configurations
  3. Agreed that config flag should be last resort, if the other options do not work

Oh, I’m sorry, I think you are right - I didn’t realize this was the issue with the mts discussion. I prematurely closed it. I agree, we should make sure we are in a good place with the whole ESM / CJS / TS loading.

We can track specific progress for cypress.config.mts here: https://github.com/cypress-io/cypress/issues/24962

@karlhorky Sorry, this issue seems to have gotten incorrectly marked and not routed to the correct team and thus looked abandoned. I will reopen and route.

Thanks for the detailed reply. Let me spend a bit of time reading and researching this.

My main concern with adding any kind of Cypress-specific flag/hint is that the same flag/hint should be possible with ts-node (and whatever toolchain the user has in their project). As long as said hint is present (eg, the mts in cypress.config.mts) Cypress will run all the code through ts-node with the configuration/hints presented by the user and work as it would without Cypress.

I think there are at least 3 things to try out …

  • try out using configuration to always transform to ESM
  • .mts extension
  • esm flag

I think the first two are the most viable. I am not sure if we need to modify Cypress to do the first, do we? This should be doable in ts-node/userland. Or, we might need an additional hint here.

For mts, we should definitely do this, I created: https://github.com/cypress-io/cypress/issues/24962. We will look for mts, use ts-node/esm, and let ts-node do the rest.

As for a new command line flag, I think we need to consider carefully what it would actually do - would we ultimately we would just do some additional conditions here (similar to your first option) https://github.com/cypress-io/cypress/blob/e3435b6fba05480765769974d829e69cf1e42ca0/packages/data-context/src/data/ProjectConfigIpc.ts#L273-L310?

I can try to setup a small reproduction repo.

I have tried 16.15.0, 16.17.1 and 16.18.1. All have the same error. Even an update to cypress 10.11 doesn’t change anything.

Hi @GeorgeXCV, I was able to recreate and fix this issue in this repo: cypress-del-ts

The ticket for my project was adding "type": "module" to package.json.

I also made some slight changes in comparison to what the guide describes. import {deleteSync} from 'del'; and deleteSync(results.video) It seems to work, but it would be awesome if you could confirm the changes work for you.

I get this error:

Your configFile is invalid: /Users/georgeashton/Documents/core/cypress.config.ts: —record

It threw an error when required, check the stack trace below:

ReferenceError: exports is not defined in ES module scope
    at file:///Users/georgeashton/Documents/core/cypress.config.ts:25:23
    at ModuleJob.run (node:internal/modules/esm/module_job:198:25)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:385:24)
    at async importModuleDynamicallyWrapper (node:internal/vm/module:437:15)
    at async loadFile (/Users/georgeashton/Library/Caches/Cypress/10.3.0/Cypress.app/Contents/Resources/app/packages/server/lib/plugins/child/run_require_async_child.js:106:14)
    at async EventEmitter.<anonymous> (/Users/georgeashton/Library/Caches/Cypress/10.3.0/Cypress.app/Contents/Resources/app/packages/server/lib/plugins/child/run_require_async_child.js:116:32)

Line 25 starts here:

on('after:spec', async (_spec, results) => {
        if (results && results.stats.failures === 0 && results.video) {
           // `del()` returns a promise, so it's important to return it to ensure
           // deleting the video is finished before moving on
           deleteSync(results.video)
        }
      })

Also why does Cypress recommend del module? Wouldn’t built in fs module work?

import * as fs from "fs"

on('after:spec', async (_spec, results) => {
        if (results && results.stats.failures === 0 && results.video) {
            fs.unlink(results.video, (err) => {
               if (err) throw err
               return
          })
        }
      })