From dedb05a0b5dc7f78116c894ed5affb7d79e09fb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barth=C3=A9l=C3=A9my=20Ledoux?= Date: Thu, 23 Sep 2021 09:06:02 -0500 Subject: [PATCH 01/31] feat: allow use of javascript in config file (#18061) --- packages/server/lib/errors.js | 7 +- packages/server/lib/project-base.ts | 13 ++- packages/server/lib/util/require_async.ts | 93 +++++++++++++++++++ .../server/lib/util/require_async_child.js | 71 ++++++++++++++ packages/server/lib/util/settings.js | 81 ++++++++++++---- packages/server/package.json | 2 +- packages/server/test/e2e/3_config_spec.js | 7 ++ .../server/test/integration/cypress_spec.js | 35 +++---- .../cypress.config.custom.js | 7 ++ .../cypress/integration/app_spec.js | 8 ++ packages/server/test/unit/project_spec.js | 10 +- packages/server/test/unit/settings_spec.js | 14 +++ yarn.lock | 15 --- 13 files changed, 305 insertions(+), 58 deletions(-) create mode 100644 packages/server/lib/util/require_async.ts create mode 100644 packages/server/lib/util/require_async_child.js create mode 100644 packages/server/test/support/fixtures/projects/config-with-custom-file-js/cypress.config.custom.js create mode 100644 packages/server/test/support/fixtures/projects/config-with-custom-file-js/cypress/integration/app_spec.js diff --git a/packages/server/lib/errors.js b/packages/server/lib/errors.js index 24ea562a0cbc..e33a20dab4c1 100644 --- a/packages/server/lib/errors.js +++ b/packages/server/lib/errors.js @@ -490,7 +490,7 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { ${chalk.yellow('Assign a different port with the \'--port \' argument or shut down the other running process.')}` case 'ERROR_READING_FILE': filePath = `\`${arg1}\`` - err = `\`${arg2}\`` + err = `\`${arg2.type || arg2.code || arg2.name}: ${arg2.message}\`` return stripIndent`\ Error reading from: ${chalk.blue(filePath)} @@ -1049,6 +1049,11 @@ const clone = function (err, options = {}) { if (options.html) { obj.message = ansi_up.ansi_to_html(err.message) + // revert back the distorted characters + // in case there is an error in a child_process + // that contains quotes + .replace(/\&\#x27;/g, '\'') + .replace(/\"\;/g, '"') } else { obj.message = err.message } diff --git a/packages/server/lib/project-base.ts b/packages/server/lib/project-base.ts index 51f593fd8f12..3bb01fa130e0 100644 --- a/packages/server/lib/project-base.ts +++ b/packages/server/lib/project-base.ts @@ -83,6 +83,7 @@ export class ProjectBase extends EE { protected _server?: TServer protected _automation?: Automation private _recordTests?: any = null + private _isServerOpen: boolean = false public browser: any public options: OpenProjectLaunchOptions @@ -220,6 +221,8 @@ export class ProjectBase extends EE { specsStore, }) + this._isServerOpen = true + // if we didnt have a cfg.port // then get the port once we // open the server @@ -340,6 +343,10 @@ export class ProjectBase extends EE { this.spec = null this.browser = null + if (!this._isServerOpen) { + return + } + const closePreprocessor = (this.testingType === 'e2e' && preprocessor.close) ?? undefined await Promise.all([ @@ -348,6 +355,8 @@ export class ProjectBase extends EE { closePreprocessor?.(), ]) + this._isServerOpen = false + process.chdir(localCwd) const config = this.getConfig() @@ -534,7 +543,7 @@ export class ProjectBase extends EE { } if (configFile !== false) { - this.watchers.watch(settings.pathToConfigFile(projectRoot, { configFile }), obj) + this.watchers.watchTree(settings.pathToConfigFile(projectRoot, { configFile }), obj) } return this.watchers.watch(settings.pathToCypressEnvJson(projectRoot), obj) @@ -552,7 +561,7 @@ export class ProjectBase extends EE { try { Reporter.loadReporter(reporter, projectRoot) - } catch (err) { + } catch (err: any) { const paths = Reporter.getSearchPathsForReporter(reporter, projectRoot) // only include the message if this is the standard MODULE_NOT_FOUND diff --git a/packages/server/lib/util/require_async.ts b/packages/server/lib/util/require_async.ts new file mode 100644 index 000000000000..dc11b7c86c4b --- /dev/null +++ b/packages/server/lib/util/require_async.ts @@ -0,0 +1,93 @@ +import _ from 'lodash' +import * as path from 'path' +import * as cp from 'child_process' +import * as inspector from 'inspector' +import * as util from '../plugins/util' +import * as errors from '../errors' +import { fs } from '../util/fs' +import Debug from 'debug' + +const debug = Debug('cypress:server:require_async') + +let requireProcess: cp.ChildProcess | null + +interface RequireAsyncOptions{ + projectRoot: string + loadErrorCode: string + /** + * members of the object returned that are functions and will need to be wrapped + */ + functionNames: string[] +} + +interface ChildOptions{ + stdio: 'inherit' + execArgv?: string[] +} + +const killChildProcess = () => { + requireProcess && requireProcess.kill() + requireProcess = null +} + +export async function requireAsync (filePath: string, options: RequireAsyncOptions): Promise { + return new Promise((resolve, reject) => { + if (requireProcess) { + debug('kill existing config process') + killChildProcess() + } + + if (/\.json$/.test(filePath)) { + fs.readJson(path.resolve(options.projectRoot, filePath)).then((result) => resolve(result)).catch(reject) + } + + const childOptions: ChildOptions = { + stdio: 'inherit', + } + + if (inspector.url()) { + childOptions.execArgv = _.chain(process.execArgv.slice(0)) + .remove('--inspect-brk') + .push(`--inspect=${process.debugPort + 1}`) + .value() + } + + const childArguments = ['--projectRoot', options.projectRoot, '--file', filePath] + + debug('fork child process', path.join(__dirname, 'require_async_child.js'), childArguments, childOptions) + requireProcess = cp.fork(path.join(__dirname, 'require_async_child.js'), childArguments, childOptions) + const ipc = util.wrapIpc(requireProcess) + + if (requireProcess.stdout && requireProcess.stderr) { + // manually pipe plugin stdout and stderr for dashboard capture + // @see https://github.com/cypress-io/cypress/issues/7434 + requireProcess.stdout.on('data', (data) => process.stdout.write(data)) + requireProcess.stderr.on('data', (data) => process.stderr.write(data)) + } + + ipc.on('loaded', (result) => { + debug('resolving with result %o', result) + resolve(result) + }) + + ipc.on('load:error', (type, ...args) => { + debug('load:error %s, rejecting', type) + killChildProcess() + + const err = errors.get(type, ...args) + + // if it's a non-cypress error, restore the initial error + if (!(err.message?.length)) { + err.isCypressErr = false + err.message = args[1] + err.code = type + err.name = type + } + + reject(err) + }) + + debug('trigger the load of the file') + ipc.send('load') + }) +} diff --git a/packages/server/lib/util/require_async_child.js b/packages/server/lib/util/require_async_child.js new file mode 100644 index 000000000000..179fda770144 --- /dev/null +++ b/packages/server/lib/util/require_async_child.js @@ -0,0 +1,71 @@ +require('graceful-fs').gracefulify(require('fs')) +const stripAnsi = require('strip-ansi') +const debug = require('debug')('cypress:server:require_async:child') +const util = require('../plugins/util') +const ipc = util.wrapIpc(process) + +require('./suppress_warnings').suppress() + +const { file, projectRoot } = require('minimist')(process.argv.slice(2)) + +run(ipc, file, projectRoot) + +/** + * runs and returns the passed `requiredFile` file in the ipc `load` event + * @param {*} ipc Inter Process Comunication protocol + * @param {*} requiredFile the file we are trying to load + * @param {*} projectRoot the root of the typescript project (useful mainly for tsnode) + * @returns + */ +function run (ipc, requiredFile, projectRoot) { + debug('requiredFile:', requiredFile) + debug('projectRoot:', projectRoot) + if (!projectRoot) { + throw new Error('Unexpected: projectRoot should be a string') + } + + process.on('uncaughtException', (err) => { + debug('uncaught exception:', util.serializeError(err)) + ipc.send('error', util.serializeError(err)) + + return false + }) + + process.on('unhandledRejection', (event) => { + const err = (event && event.reason) || event + + debug('unhandled rejection:', util.serializeError(err)) + ipc.send('error', util.serializeError(err)) + + return false + }) + + ipc.on('load', () => { + try { + debug('try loading', requiredFile) + const exp = require(requiredFile) + + const result = exp.default || exp + + ipc.send('loaded', result) + + debug('config %o', result) + } catch (err) { + if (err.name === 'TSError') { + // beause of this https://github.com/TypeStrong/ts-node/issues/1418 + // we have to do this https://stackoverflow.com/questions/25245716/remove-all-ansi-colors-styles-from-strings/29497680 + const cleanMessage = stripAnsi(err.message) + // replace the first line with better text (remove potentially misleading word TypeScript for example) + .replace(/^.*\n/g, 'Error compiling file\n') + + ipc.send('load:error', err.name, requiredFile, cleanMessage) + } else { + const realErrorCode = err.code || err.name + + debug('failed to load file:%s\n%s: %s', requiredFile, realErrorCode, err.message) + + ipc.send('load:error', realErrorCode, requiredFile, err.message) + } + } + }) +} diff --git a/packages/server/lib/util/settings.js b/packages/server/lib/util/settings.js index c1d03d1f4d7b..d1e13a14c667 100644 --- a/packages/server/lib/util/settings.js +++ b/packages/server/lib/util/settings.js @@ -4,6 +4,19 @@ const path = require('path') const errors = require('../errors') const log = require('../log') const { fs } = require('../util/fs') +const { requireAsync } = require('./require_async') +const debug = require('debug')('cypress:server:settings') + +function jsCode (obj) { + const objJSON = obj && !_.isEmpty(obj) + ? JSON.stringify(_.omit(obj, 'configFile'), null, 2) + : `{ + +}` + + return `module.exports = ${objJSON} +` +} // TODO: // think about adding another PSemaphore @@ -78,7 +91,19 @@ module.exports = { }, _write (file, obj = {}) { - return fs.outputJsonAsync(file, obj, { spaces: 2 }) + if (/\.json$/.test(file)) { + debug('writing json file') + + return fs.outputJsonAsync(file, obj, { spaces: 2 }) + .return(obj) + .catch((err) => { + return this._logWriteErr(file, err) + }) + } + + debug('writing javascript file') + + return fs.writeFileAsync(file, jsCode(obj)) .return(obj) .catch((err) => { return this._logWriteErr(file, err) @@ -110,7 +135,13 @@ module.exports = { return null }) }, - + /** + * Ensures the project at this root has a config file + * that is readable and writable by the node process + * @param {string} projectRoot root of the project + * @param {object} options + * @returns + */ exists (projectRoot, options = {}) { const file = this.pathToConfigFile(projectRoot, options) @@ -121,7 +152,7 @@ module.exports = { // directory is writable return fs.accessAsync(projectRoot, fs.W_OK) }).catch({ code: 'ENOENT' }, () => { - // cypress.json does not exist, we missing project + // cypress.json does not exist, completely new project log('cannot find file %s', file) return this._err('CONFIG_FILE_NOT_FOUND', this.configFile(options), projectRoot) @@ -144,29 +175,45 @@ module.exports = { const file = this.pathToConfigFile(projectRoot, options) - return fs.readJsonAsync(file) - .catch({ code: 'ENOENT' }, () => { - return this._write(file, {}) - }).then((json = {}) => { - if (this.isComponentTesting(options) && 'component' in json) { - json = { ...json, ...json.component } + return requireAsync(file, + { + projectRoot, + loadErrorCode: 'CONFIG_FILE_ERROR', + }) + .catch((err) => { + if (err.type === 'MODULE_NOT_FOUND' || err.code === 'ENOENT') { + debug('file not found', file) + + return this._write(file, {}) + } + + return Promise.reject(err) + }) + .then((configObject = {}) => { + if (this.isComponentTesting(options) && 'component' in configObject) { + configObject = { ...configObject, ...configObject.component } } - if (!this.isComponentTesting(options) && 'e2e' in json) { - json = { ...json, ...json.e2e } + if (!this.isComponentTesting(options) && 'e2e' in configObject) { + configObject = { ...configObject, ...configObject.e2e } } - const changed = this._applyRewriteRules(json) + debug('resolved configObject', configObject) + const changed = this._applyRewriteRules(configObject) // if our object is unchanged // then just return it - if (_.isEqual(json, changed)) { - return json + if (_.isEqual(configObject, changed)) { + return configObject } // else write the new reduced obj return this._write(file, changed) + .then((config) => { + return config + }) }).catch((err) => { + debug('an error occured when reading config', err) if (errors.isCypressErr(err)) { throw err } @@ -196,7 +243,7 @@ module.exports = { return Promise.resolve({}) } - return this.read(projectRoot) + return this.read(projectRoot, options) .then((settings) => { _.extend(settings, obj) @@ -206,10 +253,6 @@ module.exports = { }) }, - remove (projectRoot, options = {}) { - return fs.unlinkSync(this.pathToConfigFile(projectRoot, options)) - }, - pathToConfigFile (projectRoot, options = {}) { const configFile = this.configFile(options) diff --git a/packages/server/package.json b/packages/server/package.json index 1501a4afcf1b..9988d819ff7b 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -38,7 +38,7 @@ "chai": "1.10.0", "chalk": "2.4.2", "check-more-types": "2.24.0", - "chokidar": "3.2.2", + "chokidar": "3.5.1", "chrome-remote-interface": "0.28.2", "cli-table3": "0.5.1", "coffeescript": "1.12.7", diff --git a/packages/server/test/e2e/3_config_spec.js b/packages/server/test/e2e/3_config_spec.js index efc901ce4690..e0f89be3de6a 100644 --- a/packages/server/test/e2e/3_config_spec.js +++ b/packages/server/test/e2e/3_config_spec.js @@ -47,4 +47,11 @@ describe('e2e config', () => { project: Fixtures.projectPath('shadow-dom-global-inclusion'), }) }) + + it('supports custom configFile in JavaScript', function () { + return e2e.exec(this, { + project: Fixtures.projectPath('config-with-custom-file-js'), + configFile: 'cypress.config.custom.js', + }) + }) }) diff --git a/packages/server/test/integration/cypress_spec.js b/packages/server/test/integration/cypress_spec.js index 356f8b5dc348..969258aa9652 100644 --- a/packages/server/test/integration/cypress_spec.js +++ b/packages/server/test/integration/cypress_spec.js @@ -511,7 +511,11 @@ describe('lib/cypress', () => { return fs.statAsync(path.join(this.pristinePath, 'cypress', 'integration')) }).then(() => { throw new Error('integration folder should not exist!') - }).catch({ code: 'ENOENT' }, () => {}) + }).catch((err) => { + if (err.code !== 'ENOENT') { + throw err + } + }) }) it('scaffolds out fixtures + files if they do not exist', function () { @@ -1795,29 +1799,26 @@ describe('lib/cypress', () => { }) it('reads config from a custom config file', function () { - sinon.stub(fs, 'readJsonAsync') - fs.readJsonAsync.withArgs(path.join(this.pristinePath, this.filename)).resolves({ + return fs.writeJson(path.join(this.pristinePath, this.filename), { env: { foo: 'bar' }, port: 2020, - }) - - fs.readJsonAsync.callThrough() - - return cypress.start([ + }).then(() => { + cypress.start([ `--config-file=${this.filename}`, - ]) - .then(() => { - const options = Events.start.firstCall.args[0] + ]) + .then(() => { + const options = Events.start.firstCall.args[0] - return Events.handleEvent(options, {}, {}, 123, 'open:project', this.pristinePath) - }).then(() => { - expect(this.open).to.be.called + return Events.handleEvent(options, {}, {}, 123, 'open:project', this.pristinePath) + }).then(() => { + expect(this.open).to.be.called - const cfg = this.open.getCall(0).args[0] + const cfg = this.open.getCall(0).args[0] - expect(cfg.env.foo).to.equal('bar') + expect(cfg.env.foo).to.equal('bar') - expect(cfg.port).to.equal(2020) + expect(cfg.port).to.equal(2020) + }) }) }) diff --git a/packages/server/test/support/fixtures/projects/config-with-custom-file-js/cypress.config.custom.js b/packages/server/test/support/fixtures/projects/config-with-custom-file-js/cypress.config.custom.js new file mode 100644 index 000000000000..d95722d5dddc --- /dev/null +++ b/packages/server/test/support/fixtures/projects/config-with-custom-file-js/cypress.config.custom.js @@ -0,0 +1,7 @@ +module.exports = { + pageLoadTimeout: 10000, + e2e: { + defaultCommandTimeout: 500, + videoCompression: 20, + }, +} diff --git a/packages/server/test/support/fixtures/projects/config-with-custom-file-js/cypress/integration/app_spec.js b/packages/server/test/support/fixtures/projects/config-with-custom-file-js/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-js/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/project_spec.js b/packages/server/test/unit/project_spec.js index c19c8f5d88e8..2fd5e5244d84 100644 --- a/packages/server/test/unit/project_spec.js +++ b/packages/server/test/unit/project_spec.js @@ -13,7 +13,7 @@ const cache = require(`${root}lib/cache`) const config = require(`${root}lib/config`) const scaffold = require(`${root}lib/scaffold`) const { ServerE2E } = require(`${root}lib/server-e2e`) -const ProjectBase = require(`${root}lib/project-base`).ProjectBase +const { ProjectBase } = require(`${root}lib/project-base`) const { getOrgs, paths, @@ -532,8 +532,10 @@ This option will not have an effect in Some-other-name. Tests that rely on web s this.project = new ProjectBase({ projectRoot: '/_test-output/path/to/project-e2e', testingType: 'e2e' }) this.project._server = { close () {} } + this.project._isServerOpen = true sinon.stub(this.project, 'getConfig').returns(this.config) + sinon.stub(user, 'ensureAuthToken').resolves('auth-token-123') }) @@ -713,12 +715,14 @@ This option will not have an effect in Some-other-name. Tests that rely on web s sinon.stub(settings, 'pathToConfigFile').returns('/path/to/cypress.json') sinon.stub(settings, 'pathToCypressEnvJson').returns('/path/to/cypress.env.json') this.watch = sinon.stub(this.project.watchers, 'watch') + this.watchTree = sinon.stub(this.project.watchers, 'watchTree') }) it('watches cypress.json and cypress.env.json', function () { this.project.watchSettings({ onSettingsChanged () {} }, {}) - expect(this.watch).to.be.calledTwice - expect(this.watch).to.be.calledWith('/path/to/cypress.json') + expect(this.watch).to.be.calledOnce + expect(this.watchTree).to.be.calledOnce + expect(this.watchTree).to.be.calledWith('/path/to/cypress.json') expect(this.watch).to.be.calledWith('/path/to/cypress.env.json') }) diff --git a/packages/server/test/unit/settings_spec.js b/packages/server/test/unit/settings_spec.js index e1c9df1339bc..ca1e4cef8622 100644 --- a/packages/server/test/unit/settings_spec.js +++ b/packages/server/test/unit/settings_spec.js @@ -222,6 +222,10 @@ describe('lib/settings', () => { this.options = { configFile: 'my-test-config-file.json', } + + this.optionsJs = { + configFile: 'my-test-config-file.js', + } }) afterEach(function () { @@ -254,5 +258,15 @@ describe('lib/settings', () => { }) }) }) + + it('.read returns from configFile when its a JavaScript file', function () { + return fs.writeFile(path.join(this.projectRoot, this.optionsJs.configFile), `module.exports = { baz: 'lurman' }`) + .then(() => { + return settings.read(this.projectRoot, this.optionsJs) + .then((settings) => { + expect(settings).to.deep.equal({ baz: 'lurman' }) + }) + }) + }) }) }) diff --git a/yarn.lock b/yarn.lock index 3dc3d87ef31e..1467ce7af1f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13682,21 +13682,6 @@ chokidar-cli@2.1.0: lodash.throttle "^4.1.1" yargs "^13.3.0" -chokidar@3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.2.2.tgz#a433973350021e09f2b853a2287781022c0dc935" - integrity sha512-bw3pm7kZ2Wa6+jQWYP/c7bAZy3i4GwiIiMO2EeRjrE48l8vBqC/WvFhSF0xyM8fQiPEGvwMY/5bqDG7sSEOuhg== - dependencies: - anymatch "~3.1.1" - braces "~3.0.2" - glob-parent "~5.1.0" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.2.0" - optionalDependencies: - fsevents "~2.1.1" - chokidar@3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.0.tgz#12c0714668c55800f659e262d4962a97faf554a6" From 6e0c2c1af81be750a74bad0528d52de45746a453 Mon Sep 17 00:00:00 2001 From: Zander Martineau Date: Fri, 24 Sep 2021 03:06:32 +0100 Subject: [PATCH 02/31] fix(vite-dev-server): replace UserConfig with InlineConfig to allow correct `configFile` types (#18167) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Barthélémy Ledoux Co-authored-by: Lachlan Miller --- npm/vite-dev-server/src/startServer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/npm/vite-dev-server/src/startServer.ts b/npm/vite-dev-server/src/startServer.ts index 167daaffd073..5417add3d95b 100644 --- a/npm/vite-dev-server/src/startServer.ts +++ b/npm/vite-dev-server/src/startServer.ts @@ -1,5 +1,5 @@ import Debug from 'debug' -import { createServer, ViteDevServer, InlineConfig, UserConfig } from 'vite' +import { createServer, ViteDevServer, InlineConfig } from 'vite' import { dirname, resolve } from 'path' import getPort from 'get-port' import { makeCypressPlugin } from './makeCypressPlugin' @@ -17,7 +17,7 @@ export interface StartDevServerOptions { * to override some options, you can do so using this. * @optional */ - viteConfig?: Omit + viteConfig?: Omit } const resolveServerConfig = async ({ viteConfig, options }: StartDevServerOptions): Promise => { From db6f9096bd6668db1937d0e38d3928866f6cd5df Mon Sep 17 00:00:00 2001 From: Zachary Williams Date: Fri, 24 Sep 2021 11:53:29 -0500 Subject: [PATCH 03/31] fix: next trace error (#18189) --- npm/react/plugins/next/findNextWebpackConfig.js | 3 +++ npm/react/plugins/next/getRunWebpackSpan.ts | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 npm/react/plugins/next/getRunWebpackSpan.ts diff --git a/npm/react/plugins/next/findNextWebpackConfig.js b/npm/react/plugins/next/findNextWebpackConfig.js index b988d6e52e9a..e2609de57fd3 100644 --- a/npm/react/plugins/next/findNextWebpackConfig.js +++ b/npm/react/plugins/next/findNextWebpackConfig.js @@ -3,6 +3,7 @@ const debug = require('debug')('@cypress/react') const getNextJsBaseWebpackConfig = require('next/dist/build/webpack-config').default const { findPagesDir } = require('../../dist/next/findPagesDir') +const { getRunWebpackSpan } = require('../../dist/next/getRunWebpackSpan') async function getNextWebpackConfig (config) { let loadConfig @@ -20,6 +21,7 @@ async function getNextWebpackConfig (config) { } } const nextConfig = await loadConfig('development', config.projectRoot) + const runWebpackSpan = await getRunWebpackSpan() const nextWebpackConfig = await getNextJsBaseWebpackConfig( config.projectRoot, { @@ -30,6 +32,7 @@ async function getNextWebpackConfig (config) { pagesDir: findPagesDir(config.projectRoot), entrypoints: {}, rewrites: { fallback: [], afterFiles: [], beforeFiles: [] }, + ...runWebpackSpan, }, ) diff --git a/npm/react/plugins/next/getRunWebpackSpan.ts b/npm/react/plugins/next/getRunWebpackSpan.ts new file mode 100644 index 000000000000..ead3dcdbe227 --- /dev/null +++ b/npm/react/plugins/next/getRunWebpackSpan.ts @@ -0,0 +1,16 @@ +import type { Span } from 'next/dist/telemetry/trace/trace' + +// Starting with v11.1.1, a trace is required. +// 'next/dist/telemetry/trace/trace' only exists since v10.0.9 +// and our peerDeps support back to v8 so try-catch this import +export async function getRunWebpackSpan (): Promise<{ runWebpackSpan?: Span }> { + let trace: (name: string) => Span + + try { + trace = await import('next/dist/telemetry/trace/trace').then((m) => m.trace) + + return { runWebpackSpan: trace('cypress') } + } catch (_) { + return {} + } +} From a5356b8f117c5b2ebd607c896b5170ec3e07c3e9 Mon Sep 17 00:00:00 2001 From: David Munechika Date: Fri, 24 Sep 2021 16:13:22 -0400 Subject: [PATCH 04/31] feat(driver): add select by index (#18201) Co-authored-by: Jennifer Shehane Co-authored-by: Zach Bloomquist --- cli/types/cypress.d.ts | 180 +++++++++--------- .../commands/actions/select_spec.js | 55 +++++- .../driver/src/cy/commands/actions/select.ts | 22 ++- packages/driver/src/cypress/error_messages.ts | 38 ++-- 4 files changed, 174 insertions(+), 121 deletions(-) diff --git a/cli/types/cypress.d.ts b/cli/types/cypress.d.ts index ebf03c52900c..8b4842d02dd3 100644 --- a/cli/types/cypress.d.ts +++ b/cli/types/cypress.d.ts @@ -168,7 +168,7 @@ declare namespace Cypress { /** * The interface for user-defined properties in Window object under test. */ - interface ApplicationWindow {} // tslint:disable-line + interface ApplicationWindow { } // tslint:disable-line /** * Several libraries are bundled with Cypress by default. @@ -521,7 +521,7 @@ declare namespace Cypress { /** * @see https://on.cypress.io/keyboard-api */ - Keyboard: { + Keyboard: { defaults(options: Partial): void } @@ -579,7 +579,7 @@ declare namespace Cypress { } interface SessionOptions { - validate?: () => false|void + validate?: () => false | void } type CanReturnChainable = void | Chainable | Promise @@ -717,36 +717,36 @@ declare namespace Cypress { ``` */ clearLocalStorage(re: RegExp): Chainable - /** - * Clear data in local storage. - * Cypress automatically runs this command before each test to prevent state from being - * shared across tests. You shouldn’t need to use this command unless you’re using it - * to clear localStorage inside a single test. Yields `localStorage` object. - * - * @see https://on.cypress.io/clearlocalstorage - * @param {options} [object] - options object - * @example - ``` - // Removes all local storage items, without logging - cy.clearLocalStorage({ log: false }) - ``` - */ + /** + * Clear data in local storage. + * Cypress automatically runs this command before each test to prevent state from being + * shared across tests. You shouldn’t need to use this command unless you’re using it + * to clear localStorage inside a single test. Yields `localStorage` object. + * + * @see https://on.cypress.io/clearlocalstorage + * @param {options} [object] - options object + * @example + ``` + // Removes all local storage items, without logging + cy.clearLocalStorage({ log: false }) + ``` + */ clearLocalStorage(options: Partial): Chainable - /** - * Clear data in local storage. - * Cypress automatically runs this command before each test to prevent state from being - * shared across tests. You shouldn’t need to use this command unless you’re using it - * to clear localStorage inside a single test. Yields `localStorage` object. - * - * @see https://on.cypress.io/clearlocalstorage - * @param {string} [key] - name of a particular item to remove (optional). - * @param {options} [object] - options object - * @example - ``` - // Removes item "todos" without logging - cy.clearLocalStorage("todos", { log: false }) - ``` - */ + /** + * Clear data in local storage. + * Cypress automatically runs this command before each test to prevent state from being + * shared across tests. You shouldn’t need to use this command unless you’re using it + * to clear localStorage inside a single test. Yields `localStorage` object. + * + * @see https://on.cypress.io/clearlocalstorage + * @param {string} [key] - name of a particular item to remove (optional). + * @param {options} [object] - options object + * @example + ``` + // Removes item "todos" without logging + cy.clearLocalStorage("todos", { log: false }) + ``` + */ clearLocalStorage(key: string, options: Partial): Chainable /** @@ -834,7 +834,7 @@ declare namespace Cypress { * // or use this shortcut * cy.clock().invoke('restore') */ - clock(now: number|Date, options?: Loggable): Chainable + clock(now: number | Date, options?: Loggable): Chainable /** * Mocks global clock but only overrides specific functions. * @@ -843,7 +843,7 @@ declare namespace Cypress { * // keep current date but override "setTimeout" and "clearTimeout" * cy.clock(null, ['setTimeout', 'clearTimeout']) */ - clock(now: number|Date, functions?: Array<'setTimeout' | 'clearTimeout' | 'setInterval' | 'clearInterval' | 'Date'>, options?: Loggable): Chainable + clock(now: number | Date, functions?: Array<'setTimeout' | 'clearTimeout' | 'setInterval' | 'clearInterval' | 'Date'>, options?: Loggable): Chainable /** * Mocks global clock and all functions. * @@ -977,14 +977,14 @@ declare namespace Cypress { */ debug(options?: Partial): Chainable - /** - * Save/Restore browser Cookies, LocalStorage, and SessionStorage data resulting from the supplied `setup` function. - * - * Only available if the `experimentalSessionSupport` config option is enabled. - * - * @see https://on.cypress.io/session - */ - session(id: string|object, setup?: SessionOptions['validate'], options?: SessionOptions): Chainable + /** + * Save/Restore browser Cookies, LocalStorage, and SessionStorage data resulting from the supplied `setup` function. + * + * Only available if the `experimentalSessionSupport` config option is enabled. + * + * @see https://on.cypress.io/session + */ + session(id: string | object, setup?: SessionOptions['validate'], options?: SessionOptions): Chainable /** * Get the window.document of the page that is currently active. @@ -1648,17 +1648,11 @@ declare namespace Cypress { scrollTo(x: number | string, y: number | string, options?: Partial): Chainable /** - * Select an `