diff --git a/packages/server/__snapshots__/3_config_spec.js b/packages/server/__snapshots__/3_config_spec.js index 9b4b71c67077..7b09652976ff 100644 --- a/packages/server/__snapshots__/3_config_spec.js +++ b/packages/server/__snapshots__/3_config_spec.js @@ -151,4 +151,13 @@ We found an invalid value in the file: \`cypress.json\` Found an error while validating the \`browsers\` list. Expected \`family\` to be either chromium or firefox. Instead the value was: \`{"name":"bad browser","family":"unknown family","displayName":"Bad browser","version":"no version","path":"/path/to","majorVersion":123}\` +` + +exports['e2e config throws error when multiple default config file are found in project 1'] = ` +There is both a \`cypress.config.js\` and a \`cypress.config.ts\` at the location below: +/foo/bar/.projects/pristine + +Cypress does not know which one to read for config. Please remove one of the two and try again. + + ` diff --git a/packages/server/lib/configFiles.ts b/packages/server/lib/configFiles.ts index 938003e636ec..62cbff7327cd 100644 --- a/packages/server/lib/configFiles.ts +++ b/packages/server/lib/configFiles.ts @@ -1,2 +1 @@ -// the first file is the default created file -export const CYPRESS_CONFIG_FILES = ['cypress.json', 'cypress.config.js'] +export const CYPRESS_CONFIG_FILES = ['cypress.json', 'cypress.config.js', 'cypress.config.ts'] diff --git a/packages/server/lib/plugins/child/run_plugins.js b/packages/server/lib/plugins/child/run_plugins.js index 5b0d68abd28f..cca85f69341b 100644 --- a/packages/server/lib/plugins/child/run_plugins.js +++ b/packages/server/lib/plugins/child/run_plugins.js @@ -7,11 +7,11 @@ const Promise = require('bluebird') const preprocessor = require('./preprocessor') const devServer = require('./dev-server') const resolve = require('../../util/resolve') +const tsNodeUtil = require('../../util/ts_node') const browserLaunch = require('./browser_launch') const task = require('./task') const util = require('../util') const validateEvent = require('./validate_event') -const tsNodeUtil = require('./ts_node') let registeredEventsById = {} let registeredEventsByName = {} diff --git a/packages/server/lib/util/require_async_child.js b/packages/server/lib/util/require_async_child.js index 179fda770144..bc03dd07e114 100644 --- a/packages/server/lib/util/require_async_child.js +++ b/packages/server/lib/util/require_async_child.js @@ -1,6 +1,7 @@ require('graceful-fs').gracefulify(require('fs')) const stripAnsi = require('strip-ansi') const debug = require('debug')('cypress:server:require_async:child') +const tsNodeUtil = require('./ts_node') const util = require('../plugins/util') const ipc = util.wrapIpc(process) @@ -8,6 +9,8 @@ require('./suppress_warnings').suppress() const { file, projectRoot } = require('minimist')(process.argv.slice(2)) +let tsRegistered = false + run(ipc, file, projectRoot) /** @@ -24,6 +27,14 @@ function run (ipc, requiredFile, projectRoot) { throw new Error('Unexpected: projectRoot should be a string') } + if (!tsRegistered && requiredFile.endsWith('.ts')) { + debug('register typescript for required file') + tsNodeUtil.register(projectRoot, requiredFile) + + // ensure typescript is only registered once + tsRegistered = true + } + process.on('uncaughtException', (err) => { debug('uncaught exception:', util.serializeError(err)) ipc.send('error', util.serializeError(err)) diff --git a/packages/server/lib/plugins/child/ts_node.js b/packages/server/lib/util/ts_node.js similarity index 71% rename from packages/server/lib/plugins/child/ts_node.js rename to packages/server/lib/util/ts_node.js index 49992c8a2856..cc9306dce336 100644 --- a/packages/server/lib/plugins/child/ts_node.js +++ b/packages/server/lib/util/ts_node.js @@ -1,9 +1,9 @@ const debug = require('debug')('cypress:server:ts-node') const path = require('path') const tsnode = require('ts-node') -const resolve = require('../../util/resolve') +const resolve = require('./resolve') -const getTsNodeOptions = (tsPath, pluginsFile) => { +const getTsNodeOptions = (tsPath, registeredFile) => { return { compiler: tsPath, // use the user's installed typescript compilerOptions: { @@ -11,20 +11,23 @@ const getTsNodeOptions = (tsPath, pluginsFile) => { }, // resolves tsconfig.json starting from the plugins directory // instead of the cwd (the project root) - dir: path.dirname(pluginsFile), + dir: path.dirname(registeredFile), transpileOnly: true, // transpile only (no type-check) for speed } } -const register = (projectRoot, pluginsFile) => { +const register = (projectRoot, registeredFile) => { try { + debug('projectRoot path: %s', projectRoot) + debug('registeredFile: %s', registeredFile) const tsPath = resolve.typescript(projectRoot) if (!tsPath) return - const tsOptions = getTsNodeOptions(tsPath, pluginsFile) - debug('typescript path: %s', tsPath) + + const tsOptions = getTsNodeOptions(tsPath, registeredFile) + debug('registering project TS with options %o', tsOptions) require('tsconfig-paths/register') diff --git a/packages/server/test/e2e/3_config_spec.js b/packages/server/test/e2e/3_config_spec.js index 183a8734fb5a..0a5d80d10ff5 100644 --- a/packages/server/test/e2e/3_config_spec.js +++ b/packages/server/test/e2e/3_config_spec.js @@ -1,4 +1,6 @@ const e2e = require('../support/helpers/e2e').default +const { fs } = require('../../lib/util/fs') +const path = require('path') const Fixtures = require('../support/helpers/fixtures') describe('e2e config', () => { @@ -55,9 +57,37 @@ describe('e2e config', () => { }) }) + it('supports custom configFile in TypeScript', function () { + return e2e.exec(this, { + project: Fixtures.projectPath('config-with-custom-file-ts'), + configFile: 'cypress.config.custom.ts', + }) + }) + it('supports custom configFile in a default JavaScript file', function () { return e2e.exec(this, { project: Fixtures.projectPath('config-with-js'), }) }) + + it('supports custom configFile in a default TypeScript file', function () { + return e2e.exec(this, { + project: Fixtures.projectPath('config-with-ts'), + }) + }) + + it('throws error when multiple default config file are found in project', function () { + const projectRoot = Fixtures.projectPath('pristine') + + return Promise.all([ + fs.writeFile(path.join(projectRoot, 'cypress.config.js'), 'module.exports = {}'), + fs.writeFile(path.join(projectRoot, 'cypress.config.ts'), 'export default {}'), + ]).then(() => { + return e2e.exec(this, { + project: projectRoot, + expectedExitCode: 1, + snapshot: true, + }) + }) + }) }) diff --git a/packages/server/test/support/fixtures/projects/config-with-custom-file-ts/cypress.config.custom.ts b/packages/server/test/support/fixtures/projects/config-with-custom-file-ts/cypress.config.custom.ts new file mode 100644 index 000000000000..fafadfaf9fb3 --- /dev/null +++ b/packages/server/test/support/fixtures/projects/config-with-custom-file-ts/cypress.config.custom.ts @@ -0,0 +1,9 @@ +const config: Record = { + pageLoadTimeout: 10000, + e2e: { + defaultCommandTimeout: 500, + videoCompression: 20, + }, +} + +export default config diff --git a/packages/server/test/support/fixtures/projects/config-with-custom-file-ts/cypress/integration/app_spec.js b/packages/server/test/support/fixtures/projects/config-with-custom-file-ts/cypress/integration/app_spec.js new file mode 100644 index 000000000000..011ffed68906 --- /dev/null +++ b/packages/server/test/support/fixtures/projects/config-with-custom-file-ts/cypress/integration/app_spec.js @@ -0,0 +1,8 @@ +it('overrides config', () => { + // overrides come from plugins + expect(Cypress.config('defaultCommandTimeout')).to.eq(500) + expect(Cypress.config('videoCompression')).to.eq(20) + + // overrides come from CLI + expect(Cypress.config('pageLoadTimeout')).to.eq(10000) +}) diff --git a/packages/server/test/support/fixtures/projects/config-with-ts/cypress.config.ts b/packages/server/test/support/fixtures/projects/config-with-ts/cypress.config.ts new file mode 100644 index 000000000000..ec331922c7b8 --- /dev/null +++ b/packages/server/test/support/fixtures/projects/config-with-ts/cypress.config.ts @@ -0,0 +1,7 @@ +export default { + pageLoadTimeout: 10000, + e2e: { + defaultCommandTimeout: 500, + videoCompression: 20, + }, +} diff --git a/packages/server/test/support/fixtures/projects/config-with-ts/cypress/integration/app_spec.js b/packages/server/test/support/fixtures/projects/config-with-ts/cypress/integration/app_spec.js new file mode 100644 index 000000000000..011ffed68906 --- /dev/null +++ b/packages/server/test/support/fixtures/projects/config-with-ts/cypress/integration/app_spec.js @@ -0,0 +1,8 @@ +it('overrides config', () => { + // overrides come from plugins + expect(Cypress.config('defaultCommandTimeout')).to.eq(500) + expect(Cypress.config('videoCompression')).to.eq(20) + + // overrides come from CLI + expect(Cypress.config('pageLoadTimeout')).to.eq(10000) +}) diff --git a/packages/server/test/unit/plugins/child/run_plugins_spec.js b/packages/server/test/unit/plugins/child/run_plugins_spec.js index f4b088cbc1b8..6323777f78ba 100644 --- a/packages/server/test/unit/plugins/child/run_plugins_spec.js +++ b/packages/server/test/unit/plugins/child/run_plugins_spec.js @@ -10,7 +10,7 @@ const util = require(`${root}../../lib/plugins/util`) const resolve = require(`${root}../../lib/util/resolve`) const browserUtils = require(`${root}../../lib/browsers/utils`) const Fixtures = require(`${root}../../test/support/helpers/fixtures`) -const tsNodeUtil = require(`${root}../../lib/plugins/child/ts_node`) +const tsNodeUtil = require(`${root}../../lib/util/ts_node`) const runPlugins = require(`${root}../../lib/plugins/child/run_plugins`) diff --git a/packages/server/test/unit/plugins/child/ts_node_spec.js b/packages/server/test/unit/plugins/child/ts_node_spec.js index e5a5ef7c6c12..c06f5701851b 100644 --- a/packages/server/test/unit/plugins/child/ts_node_spec.js +++ b/packages/server/test/unit/plugins/child/ts_node_spec.js @@ -4,9 +4,9 @@ const tsnode = require('ts-node') const resolve = require(`${root}../../lib/util/resolve`) -const tsNodeUtil = require(`${root}../../lib/plugins/child/ts_node`) +const tsNodeUtil = require(`${root}../../lib/util/ts_node`) -describe('lib/plugins/child/ts_node', () => { +describe('lib/util/ts_node', () => { beforeEach(() => { sinon.stub(tsnode, 'register') sinon.stub(resolve, 'typescript').returns('/path/to/typescript.js')