diff --git a/packages/app/src/runner/event-manager.ts b/packages/app/src/runner/event-manager.ts
index 28f3324fc512..223cf6310e73 100644
--- a/packages/app/src/runner/event-manager.ts
+++ b/packages/app/src/runner/event-manager.ts
@@ -38,7 +38,6 @@ interface AddGlobalListenerOptions {
const driverToLocalAndReporterEvents = 'run:start run:end'.split(' ')
const driverToSocketEvents = 'backend:request automation:request mocha recorder:frame'.split(' ')
-const driverTestEvents = 'test:before:run:async test:after:run'.split(' ')
const driverToLocalEvents = 'viewport:changed config stop url:changed page:loading visit:failed visit:blank cypress:in:cypress:runner:event'.split(' ')
const socketRerunEvents = 'runner:restart watched:file:changed'.split(' ')
const socketToDriverEvents = 'net:stubbing:event request:event script:error cross:origin:cookies'.split(' ')
@@ -536,12 +535,6 @@ export class EventManager {
Cypress.on('after:screenshot', handleAfterScreenshot)
- driverTestEvents.forEach((event) => {
- Cypress.on(event, (test, cb) => {
- this.reporterBus.emit(event, test, cb)
- })
- })
-
driverToLocalAndReporterEvents.forEach((event) => {
Cypress.on(event, (...args) => {
this.localBus.emit(event, ...args)
@@ -549,6 +542,14 @@ export class EventManager {
})
})
+ Cypress.on('test:before:run:async', (test, _runnable) => {
+ this.reporterBus.emit('test:before:run:async', test)
+ })
+
+ Cypress.on('test:after:run', (test, _runnable) => {
+ this.reporterBus.emit('test:after:run', test, Cypress.config('isInteractive'))
+ })
+
Cypress.on('run:start', async () => {
if (Cypress.config('experimentalMemoryManagement') && Cypress.isBrowser({ family: 'chromium' })) {
await Cypress.backend('start:memory:profiling', Cypress.config('spec'))
diff --git a/packages/app/src/specs/SpecsListRowItem.cy.tsx b/packages/app/src/specs/SpecsListRowItem.cy.tsx
index 00ba82a74cda..a5b62a9b681e 100644
--- a/packages/app/src/specs/SpecsListRowItem.cy.tsx
+++ b/packages/app/src/specs/SpecsListRowItem.cy.tsx
@@ -68,12 +68,12 @@ describe('SpecItem', () => {
}}
/>))
- cy.findByTestId('latest').trigger('mouseenter').wait(300)
- cy.findByTestId('button').contains('Specs Latest Runs Empty State')
- cy.findByTestId('button').trigger('mouseleave')
+ cy.findByTestId('latest').trigger('mouseenter')
+ cy.findByTestId('button').as('button').contains('Specs Latest Runs Empty State')
+ cy.get('@button').trigger('mouseleave')
- cy.findByTestId('duration').trigger('mouseenter').wait(300)
- cy.findByTestId('button').contains('Specs Average Duration Empty State')
- cy.findByTestId('button').trigger('mouseleave')
+ cy.findByTestId('duration').trigger('mouseenter')
+ cy.get('@button').contains('Specs Average Duration Empty State')
+ cy.get('@button').trigger('mouseleave')
})
})
diff --git a/packages/config/package.json b/packages/config/package.json
index d9f723ff7b7b..25e54eb73390 100644
--- a/packages/config/package.json
+++ b/packages/config/package.json
@@ -25,7 +25,7 @@
"@babel/types": "^7",
"check-more-types": "2.24.0",
"common-tags": "1.8.0",
- "debug": "^4.3.2",
+ "debug": "^4.3.4",
"fs-extra": "^9.1.0",
"lodash": "^4.17.21",
"recast": "0.20.4",
diff --git a/packages/data-context/package.json b/packages/data-context/package.json
index 86a7616eaa4c..7bf141d7e895 100644
--- a/packages/data-context/package.json
+++ b/packages/data-context/package.json
@@ -49,7 +49,7 @@
"randomstring": "1.1.5",
"react-docgen": "6.0.0-alpha.3",
"semver": "7.3.2",
- "simple-git": "3.15.0",
+ "simple-git": "3.16.0",
"stringify-object": "^3.0.0",
"underscore.string": "^3.3.6",
"wonka": "^4.0.15"
diff --git a/packages/data-context/src/actions/MigrationActions.ts b/packages/data-context/src/actions/MigrationActions.ts
index 58dadd515f34..c154784b96c6 100644
--- a/packages/data-context/src/actions/MigrationActions.ts
+++ b/packages/data-context/src/actions/MigrationActions.ts
@@ -170,8 +170,7 @@ export class MigrationActions {
async initialize (config: LegacyCypressConfigJson) {
const legacyConfigForMigration = await this.setLegacyConfigForMigration(config)
- // for testing mainly, we want to ensure the flags are reset each test
- this.resetFlags()
+ this.reset(legacyConfigForMigration)
if (!this.ctx.currentProject || !legacyConfigForMigration) {
throw Error('cannot do migration without currentProject!')
@@ -435,11 +434,9 @@ export class MigrationActions {
}
}
- resetFlags () {
+ reset (config?: LegacyCypressConfigJson) {
this.ctx.update((coreData) => {
- const defaultFlags = makeCoreData().migration.flags
-
- coreData.migration.flags = defaultFlags
+ coreData.migration = { ...makeCoreData().migration, legacyConfigForMigration: config }
})
}
}
diff --git a/packages/data-context/src/actions/ProjectActions.ts b/packages/data-context/src/actions/ProjectActions.ts
index 0c3be8c52555..a0d0e887dd61 100644
--- a/packages/data-context/src/actions/ProjectActions.ts
+++ b/packages/data-context/src/actions/ProjectActions.ts
@@ -98,6 +98,7 @@ export class ProjectActions {
d.app.browserStatus = 'closed'
})
+ this.ctx.actions.migration.reset()
await this.ctx.lifecycleManager.clearCurrentProject()
resetIssuedWarnings()
await this.api.closeActiveProject()
diff --git a/packages/data-context/src/data/ProjectConfigManager.ts b/packages/data-context/src/data/ProjectConfigManager.ts
index 753d4c5de16e..30508f02ff49 100644
--- a/packages/data-context/src/data/ProjectConfigManager.ts
+++ b/packages/data-context/src/data/ProjectConfigManager.ts
@@ -135,6 +135,12 @@ export class ProjectConfigManager {
...loadConfigReply.requires,
this.configFilePath,
])
+
+ // Only call "to{App,Launchpad}" once the config is done loading.
+ // Calling this in a "finally" would trigger this emission for every
+ // call to get the config (which we do a lot)
+ this.options.ctx.emitter.toLaunchpad()
+ this.options.ctx.emitter.toApp()
}
return loadConfigReply.initialConfig
@@ -147,10 +153,10 @@ export class ProjectConfigManager {
this._state = 'errored'
await this.closeWatchers()
- throw error
- } finally {
this.options.ctx.emitter.toLaunchpad()
this.options.ctx.emitter.toApp()
+
+ throw error
}
}
diff --git a/packages/data-context/src/data/ProjectLifecycleManager.ts b/packages/data-context/src/data/ProjectLifecycleManager.ts
index 0ee303f9017d..8c681fbc3c47 100644
--- a/packages/data-context/src/data/ProjectLifecycleManager.ts
+++ b/packages/data-context/src/data/ProjectLifecycleManager.ts
@@ -103,6 +103,11 @@ export class ProjectLifecycleManager {
async getProjectId (): Promise {
try {
+ // No need to kick off config initialization if we need to migrate
+ if (this.ctx.migration.needsCypressJsonMigration()) {
+ return null
+ }
+
const contents = await this.ctx.project.getConfig()
return contents.projectId ?? null
@@ -458,8 +463,6 @@ export class ProjectLifecycleManager {
const legacyConfigPath = path.join(projectRoot, this.ctx.migration.legacyConfigFile)
if (needsCypressJsonMigration && !this.ctx.isRunMode && this.ctx.fs.existsSync(legacyConfigPath)) {
- this.legacyMigration(legacyConfigPath).catch(this.onLoadError)
-
return false
}
@@ -470,8 +473,9 @@ export class ProjectLifecycleManager {
return this.metaState.hasValidConfigFile
}
- private async legacyMigration (legacyConfigPath: string) {
+ async legacyMigration () {
try {
+ const legacyConfigPath = path.join(this.projectRoot, this.ctx.migration.legacyConfigFile)
// we run the legacy plugins/index.js in a child process
// and mutate the config based on the return value for migration
// only used in open mode (cannot migrate via terminal)
@@ -480,8 +484,6 @@ export class ProjectLifecycleManager {
// should never throw, unless there existing pluginsFile errors out,
// in which case they are attempting to migrate an already broken project.
await this.ctx.actions.migration.initialize(legacyConfig)
-
- this.ctx.emitter.toLaunchpad()
} catch (error) {
this.onLoadError(error)
}
diff --git a/packages/driver/cypress/e2e/cypress/events.cy.ts b/packages/driver/cypress/e2e/cypress/events.cy.ts
new file mode 100644
index 000000000000..e9aca8bfb1bd
--- /dev/null
+++ b/packages/driver/cypress/e2e/cypress/events.cy.ts
@@ -0,0 +1,205 @@
+describe('src/cypress', () => {
+ describe('events', () => {
+ it('fail event', (done) => {
+ cy.on('fail', (err, runnable) => {
+ expect(err.message).to.equal('foo')
+ expect(runnable).to.equal(Cypress.state('runnable'))
+
+ done()
+ })
+
+ throw new Error('foo')
+ })
+
+ it('viewport:changed event', () => {
+ let called = false
+
+ cy.on('viewport:changed', (viewport) => {
+ expect(viewport).to.deep.equal({ viewportWidth: 100, viewportHeight: 100 })
+ called = true
+ })
+
+ cy.viewport(100, 100).then(() => {
+ expect(called).to.be.true
+ })
+ })
+
+ it('scrolled event', (done) => {
+ cy.viewport(100, 100)
+ Cypress.$('')
+ .attr('id', 'button')
+ .css({
+ position: 'absolute',
+ left: '0px',
+ top: '50px',
+ })
+ .appendTo(cy.$$('body'))
+
+ cy.on('scrolled', ($el, type) => {
+ expect($el[0]).to.eq(Cypress.$('#button')[0])
+ expect(type).to.eq('element')
+
+ done()
+ })
+
+ cy.get('#button').trigger('mousedown')
+ })
+
+ context('command events', () => {
+ it('command:enqueued event', () => {
+ let called = false
+
+ const handler = (command) => {
+ expect(command.name).to.eq('log')
+ called = true
+ cy.off('command:enqueued', handler)
+ }
+
+ cy.on('command:enqueued', handler)
+
+ cy.log('foo').then(() => {
+ expect(called).to.be.true
+ })
+ })
+
+ it('command:start event', () => {
+ let called = false
+
+ const handler = (command) => {
+ expect(command.attributes.name).to.eq('log')
+ called = true
+ cy.off('command:start', handler)
+ }
+
+ cy.on('command:start', handler)
+
+ cy.log('foo').then(() => {
+ expect(called).to.be.true
+ })
+ })
+
+ it('command:end event', () => {
+ let called = false
+
+ const handler = (command) => {
+ expect(command.attributes.name).to.eq('log')
+ called = true
+ cy.off('command:end', handler)
+ }
+
+ cy.on('command:end', handler)
+
+ cy.log('foo').then(() => {
+ expect(called).to.be.true
+ })
+ })
+
+ it('command:retry event', (done) => {
+ const handler = (options) => {
+ expect(options._retries).to.equal(1)
+ expect(options.error.message).to.equal('Expected to find element: `#foo`, but never found it.')
+ done()
+ }
+
+ cy.on('command:retry', handler)
+
+ cy.get('#foo')
+ })
+ })
+
+ context('log events', () => {
+ it('log:added event', () => {
+ const attrs: any[] = []
+ const logs: any[] = []
+
+ const handler = (attr, log) => {
+ attrs.push(attr)
+ logs.push(log)
+ }
+
+ cy.on('log:added', handler)
+
+ Cypress.log({ name: 'log', message: `foo` })
+
+ cy.log('foo').then(() => {
+ expect(attrs[0].name).to.eq('log')
+ expect(logs[0].attributes.name).to.eq('log')
+ })
+ })
+
+ it('log:changed event', (done) => {
+ const handler = (attr, log) => {
+ cy.off('log:changed', handler)
+ expect(attr.name).to.eq('bar')
+ expect(log.attributes.name).to.eq('bar')
+ done()
+ }
+
+ const log = Cypress.log({ message: `foo` })
+
+ cy.on('log:changed', handler)
+
+ log?.set('name', 'bar')
+ })
+ })
+
+ // these tests need to be run together since they are testing lifecycle events
+ context('lifecycle (test:before/after:run) events', () => {
+ let afterRunnable
+ let beforeRunnable
+ let beforeAsyncRunnable
+ let expectedAfterRunnable
+
+ const beforeHandler = (test, runnable) => {
+ expect(test.title).to.eq('test 2')
+
+ beforeRunnable = runnable
+ Cypress.off('test:before:run', beforeHandler)
+ }
+
+ const beforeAsyncHandler = (test, runnable) => {
+ expect(test.title).to.eq('test 2')
+
+ beforeAsyncRunnable = runnable
+ Cypress.off('test:before:run:async', beforeAsyncHandler)
+ }
+
+ const afterHandler = (test, runnable) => {
+ expect(test.title).to.eq('test 1')
+
+ afterRunnable = runnable
+ Cypress.off('test:after:run', afterHandler)
+ }
+
+ before(() => {
+ Cypress.on('test:before:run', beforeHandler)
+ Cypress.on('test:before:run:async', beforeAsyncHandler)
+ Cypress.on('test:after:run', afterHandler)
+ })
+
+ after(() => {
+ Cypress.off('test:before:run', beforeHandler)
+ Cypress.off('test:before:run:async', beforeAsyncHandler)
+ Cypress.off('test:after:run', afterHandler)
+ })
+
+ it('test 1', () => {
+ // this is the runnable that we expect to be passed to the test:after:run event
+ // and it will be verified in the next test since we need to wait for the test to finish
+ expectedAfterRunnable = Cypress.state('runnable')
+ })
+
+ it('test 2', () => {
+ // the before runnables should be from this test
+ const runnable = Cypress.state('runnable')
+
+ // use === to avoid the circular references
+ expect(beforeAsyncRunnable === runnable).to.be.true
+ expect(beforeRunnable === runnable).to.be.true
+
+ // the after runnable should be from the previous test
+ expect(afterRunnable).to.deep.equal(expectedAfterRunnable)
+ })
+ })
+ })
+})
diff --git a/packages/driver/cypress/e2e/e2e/origin/commands/waiting.cy.ts b/packages/driver/cypress/e2e/e2e/origin/commands/waiting.cy.ts
index f6fbea48f06a..20d62ed5771e 100644
--- a/packages/driver/cypress/e2e/e2e/origin/commands/waiting.cy.ts
+++ b/packages/driver/cypress/e2e/e2e/origin/commands/waiting.cy.ts
@@ -3,54 +3,31 @@ import { findCrossOriginLogs } from '../../../../support/utils'
declare global {
interface Window {
xhrGet: any
- abortRequests: any
}
}
-let reqQueue: XMLHttpRequest[] = []
-
const xhrGet = (url) => {
const xhr = new window.XMLHttpRequest()
xhr.open('GET', url)
- reqQueue.push(xhr)
xhr.send()
}
-const abortRequests = () => {
- reqQueue.forEach((xhr) => xhr.abort())
- reqQueue = []
-}
-
context('cy.origin waiting', { browser: '!webkit' }, () => {
before(() => {
cy.origin('http://www.foobar.com:3500', () => {
- let reqQueue: XMLHttpRequest[] = []
-
window.xhrGet = (url) => {
const xhr = new window.XMLHttpRequest()
xhr.open('GET', url)
- reqQueue.push(xhr)
xhr.send()
}
-
- window.abortRequests = () => {
- reqQueue.forEach((xhr) => xhr.abort())
- reqQueue = []
- }
})
})
let logs: Map
beforeEach(() => {
- cy.origin('http://www.foobar.com:3500', () => {
- window.abortRequests()
- })
-
- abortRequests()
-
logs = new Map()
cy.on('log:changed', (attrs, log) => {
diff --git a/packages/driver/package.json b/packages/driver/package.json
index 747ddc0cde71..573585d4629a 100644
--- a/packages/driver/package.json
+++ b/packages/driver/package.json
@@ -48,7 +48,7 @@
"crypto-js": "4.1.1",
"cypress-multi-reporters": "1.4.0",
"dayjs": "^1.10.3",
- "debug": "^4.3.2",
+ "debug": "^4.3.4",
"error-stack-parser": "2.0.6",
"errorhandler": "1.5.1",
"eventemitter2": "6.4.7",
diff --git a/packages/electron/package.json b/packages/electron/package.json
index 6985d0cf1cd5..08eabe7aaa56 100644
--- a/packages/electron/package.json
+++ b/packages/electron/package.json
@@ -18,7 +18,7 @@
"dependencies": {
"@packages/icons": "0.0.0-development",
"bluebird": "3.5.3",
- "debug": "^4.3.2",
+ "debug": "^4.3.4",
"fs-extra": "9.1.0",
"lodash": "^4.17.21",
"minimist": "1.2.6"
diff --git a/packages/errors/src/errorUtils.ts b/packages/errors/src/errorUtils.ts
index 501249803ee7..f54e7b9ae51c 100644
--- a/packages/errors/src/errorUtils.ts
+++ b/packages/errors/src/errorUtils.ts
@@ -50,9 +50,10 @@ type AllowedChalkColors = 'red' | 'blue' | 'green' | 'magenta' | 'yellow'
*
* @param err
* @param color
+ * @param causeDepth If error has a `cause` limits the maximum depth of causes to log. Set to `0` to not log any `cause`
* @returns
*/
-export const logError = function (err: CypressError | ErrorLike, color: AllowedChalkColors = 'red') {
+export const logError = function (err: CypressError | ErrorLike, color: AllowedChalkColors = 'red', causeDepth: number = 3) {
console.log(chalk[color](err.message))
if (err.details) {
@@ -67,5 +68,11 @@ export const logError = function (err: CypressError | ErrorLike, color: AllowedC
console.log(chalk[color](err.stack ?? ''))
+ if (causeDepth > 0 && err['cause']) {
+ // Limit the recursions on `cause` in case there is a loop
+ console.log(chalk[color]('Caused by:'))
+ logError(err['cause'], color, causeDepth - 1)
+ }
+
return err
}
diff --git a/packages/errors/test/unit/errors_spec.ts b/packages/errors/test/unit/errors_spec.ts
index 61f15f6f5ef5..609464bebeda 100644
--- a/packages/errors/test/unit/errors_spec.ts
+++ b/packages/errors/test/unit/errors_spec.ts
@@ -3,7 +3,7 @@ import style from 'ansi-styles'
import chai, { expect } from 'chai'
/* eslint-disable no-console */
import chalk from 'chalk'
-import sinon from 'sinon'
+import sinon, { SinonSpy } from 'sinon'
import * as errors from '../../src'
import { parseResolvedPattern } from '../../src/errorUtils'
@@ -72,16 +72,65 @@ describe('lib/errors', () => {
expect(console.log).to.be.calledWithMatch(chalk.magenta(userError.stack ?? ''))
})
- it('logs err.stack in development', () => {
- process.env.CYPRESS_INTERNAL_ENV = 'development'
+ describe('err.stack', () => {
+ it('is logged if not a known Cypress error', () => {
+ const err = new Error('foo')
- const err = new Error('foo')
+ const ret = errors.log(err)
- const ret = errors.log(err)
+ expect(ret).to.eq(err)
+
+ expect(console.log).to.be.calledWith(chalk.red(err.stack ?? ''))
+ })
+
+ it('is not logged if a known Cypress error', () => {
+ const err = new Error('foo')
+
+ err['isCypressErr'] = true
+
+ const ret = errors.log(err)
+
+ expect(ret).to.be.undefined
+
+ expect(console.log).not.to.be.calledWith(chalk.red(err.stack ?? ''))
+ })
+ })
+
+ context('err.cause', () => {
+ let err
+
+ beforeEach(() => {
+ err = new Error('foo')
+ err['cause'] = err
+ })
+
+ it('is not logged if a known Cypress error', () => {
+ err['isCypressErr'] = true
+
+ const ret = errors.log(err)
+
+ expect(ret).to.be.undefined
+
+ expect(console.log).not.to.be.calledWith(chalk.red('Caused by:'))
+ })
+
+ it('is not logged if max cause depth === 0', () => {
+ const ret = errors.log(err, 'red', 0)
+
+ expect(ret).to.eq(ret)
+
+ expect(console.log).not.to.be.calledWith(chalk.red('Caused by:'))
+ })
+
+ it('is logged to a specified max depth', () => {
+ const ret = errors.log(err, 'red', 5)
+
+ expect(ret).to.eq(err)
- expect(ret).to.eq(err)
+ const causeLogs = (console.log as SinonSpy).getCalls().filter((call) => call.args[0] === chalk.red('Caused by:'))
- expect(console.log).to.be.calledWith(chalk.red(err.stack ?? ''))
+ expect(causeLogs).to.have.length(5)
+ })
})
})
diff --git a/packages/frontend-shared/src/gql-components/HeaderBarContent.vue b/packages/frontend-shared/src/gql-components/HeaderBarContent.vue
index fee774a9cda2..0dd818466da2 100644
--- a/packages/frontend-shared/src/gql-components/HeaderBarContent.vue
+++ b/packages/frontend-shared/src/gql-components/HeaderBarContent.vue
@@ -217,6 +217,10 @@ mutation GlobalPageHeader_clearCurrentProject {
currentProject {
id
}
+ # This ensures the cache is updated with null after clearing project
+ migration {
+ configFileNameBefore
+ }
}
}
`
diff --git a/packages/frontend-shared/src/gql-components/modals/SelectCloudProjectModal.cy.tsx b/packages/frontend-shared/src/gql-components/modals/SelectCloudProjectModal.cy.tsx
index 41ed65f58165..84fba37b9d25 100644
--- a/packages/frontend-shared/src/gql-components/modals/SelectCloudProjectModal.cy.tsx
+++ b/packages/frontend-shared/src/gql-components/modals/SelectCloudProjectModal.cy.tsx
@@ -134,6 +134,20 @@ describe('', () => {
mountDialog()
})
+ it('can switch between organizations with and without projects', () => {
+ cy.get('[data-cy="selectOrganization"]').click()
+ cy.findByRole('listbox').within(() => cy.findAllByText('Test Org 2').click())
+
+ cy.contains('button', defaultMessages.runs.connect.modal.selectProject.connectProject).should('not.exist')
+ cy.contains('button', defaultMessages.runs.connect.modal.selectProject.createProject).should('be.visible')
+
+ cy.get('[data-cy="selectOrganization"]').click()
+ cy.findByRole('listbox').within(() => cy.findAllByText('Test Org 1').click())
+
+ cy.contains('button', defaultMessages.runs.connect.modal.selectProject.createProject).should('not.exist')
+ cy.contains('button', defaultMessages.runs.connect.modal.selectProject.connectProject).should('be.visible')
+ })
+
context('create new project', () => {
beforeEach(() => {
cy.contains('a', defaultMessages.runs.connect.modal.selectProject.createNewProject).click()
diff --git a/packages/frontend-shared/src/gql-components/modals/SelectCloudProjectModal.vue b/packages/frontend-shared/src/gql-components/modals/SelectCloudProjectModal.vue
index 68cf5779c0aa..1ae2486aaeb3 100644
--- a/packages/frontend-shared/src/gql-components/modals/SelectCloudProjectModal.vue
+++ b/packages/frontend-shared/src/gql-components/modals/SelectCloudProjectModal.vue
@@ -344,10 +344,12 @@ watch(projectOptions, (newVal, oldVal) => {
}
if (newVal.length === 1) {
- pickedProject.value = projectOptions.value[0]
+ pickedProject.value = newVal[0]
} else {
- pickedProject.value = projectOptions.value.find((p) => p.name === projectName.value)
+ pickedProject.value = newVal.find((p) => p.name === projectName.value)
}
+
+ newProject.value = newVal.length === 0
}, {
immediate: true,
})
diff --git a/packages/frontend-shared/src/locales/en-US.json b/packages/frontend-shared/src/locales/en-US.json
index d088613b58fb..73a759357e2c 100644
--- a/packages/frontend-shared/src/locales/en-US.json
+++ b/packages/frontend-shared/src/locales/en-US.json
@@ -650,6 +650,10 @@
}
},
"debugPage": {
+ "openFile": {
+ "openInIDE": "Open in IDE",
+ "notFoundLocally": "Opening in IDE is disabled because the spec is not found in this project"
+ },
"limit": {
"title": "Cypress renders up to 100 failed test results",
"message": "This run has {n} failed tests | This run has {n} failed test | This run has {n} failed tests",
diff --git a/packages/graphql/src/schemaTypes/objectTypes/gql-Query.ts b/packages/graphql/src/schemaTypes/objectTypes/gql-Query.ts
index 2ccad6aa708c..b19ff1ee705b 100644
--- a/packages/graphql/src/schemaTypes/objectTypes/gql-Query.ts
+++ b/packages/graphql/src/schemaTypes/objectTypes/gql-Query.ts
@@ -43,7 +43,18 @@ export const Query = objectType({
t.field('migration', {
type: Migration,
description: 'Metadata about the migration, null if we aren\'t showing it',
- resolve: (root, args, ctx) => ctx.coreData.migration.legacyConfigForMigration ? ctx.coreData.migration : null,
+ resolve: async (root, args, ctx) => {
+ // First check to see if "legacyConfigForMigration" is defined as that means we have started migration
+ if (ctx.coreData.migration.legacyConfigForMigration) return ctx.coreData.migration.legacyConfigForMigration
+
+ if (!ctx.migration.needsCypressJsonMigration()) {
+ return null
+ }
+
+ await ctx.lifecycleManager.legacyMigration()
+
+ return ctx.coreData.migration.legacyConfigForMigration
+ },
})
t.nonNull.field('dev', {
diff --git a/packages/https-proxy/package.json b/packages/https-proxy/package.json
index 8df5553e8953..189a2a093a2d 100644
--- a/packages/https-proxy/package.json
+++ b/packages/https-proxy/package.json
@@ -16,7 +16,7 @@
},
"dependencies": {
"bluebird": "3.5.3",
- "debug": "^4.3.2",
+ "debug": "^4.3.4",
"fs-extra": "9.1.0",
"lodash": "^4.17.21",
"node-forge": "1.3.0",
diff --git a/packages/launcher/package.json b/packages/launcher/package.json
index 5fb91d122112..b1c20a3b01df 100644
--- a/packages/launcher/package.json
+++ b/packages/launcher/package.json
@@ -14,7 +14,7 @@
},
"dependencies": {
"bluebird": "3.5.3",
- "debug": "^4.3.2",
+ "debug": "^4.3.4",
"execa": "4.0.0",
"fs-extra": "9.1.0",
"lodash": "^4.17.21",
diff --git a/packages/launchpad/cypress/e2e/project-setup.cy.ts b/packages/launchpad/cypress/e2e/project-setup.cy.ts
index 26d16e99f4ee..dab37fce599e 100644
--- a/packages/launchpad/cypress/e2e/project-setup.cy.ts
+++ b/packages/launchpad/cypress/e2e/project-setup.cy.ts
@@ -259,8 +259,6 @@ describe('Launchpad: Setup Project', () => {
cy.findByRole('button', { name: 'Continue' })
.should('not.have.disabled')
.click()
-
- verifyChooseABrowserPage()
})
it('moves to "Choose a browser" page after clicking "Continue" button in first step in configuration page', () => {
@@ -486,8 +484,7 @@ describe('Launchpad: Setup Project', () => {
})
describe('project not been configured for cypress', () => {
- // TODO: unskip once Object API lands https://github.com/cypress-io/cypress/pull/20861
- it.skip('can setup component testing', () => {
+ it('can setup component testing', () => {
scaffoldAndOpenProject('pristine')
cy.visitLaunchpad()
@@ -499,7 +496,7 @@ describe('Launchpad: Setup Project', () => {
cy.findByText('Confirm the front-end framework and bundler used in your project.')
- cy.findByRole('button', { name: 'Front-end Framework React.js (detected)' }).click()
+ cy.contains('Pick a framework').click()
cy.findByRole('option', { name: 'Create React App' }).click()
cy.get('[data-testid="select-bundler"').should('not.exist')
@@ -508,34 +505,28 @@ describe('Launchpad: Setup Project', () => {
cy.findByRole('button', { name: 'Back' }).click()
cy.get('[data-cy-testingtype="component"]').click()
- cy.findByRole('button', { name: 'Front-end Framework React.js (detected)' }).click()
+ cy.contains('Pick a framework').click()
cy.findByRole('option', { name: 'Vue.js 3' }).click()
cy.findByRole('button', { name: 'Bundler(dev server) Pick a bundler' }).click()
cy.findByRole('option', { name: 'Vite' }).click()
- cy.findByRole('button', { name: 'TypeScript' }).click()
cy.findByRole('button', { name: 'Next step' }).should('not.have.disabled')
cy.findByRole('button', { name: 'Next step' }).click()
cy.findByRole('button', { name: 'Skip' }).click()
- cy.get('[data-cy=valid]').within(() => {
- cy.contains('cypress.config.ts')
- cy.containsPath('cypress/support/component-index.html')
- cy.containsPath(`cypress/support/component.ts`)
- cy.containsPath(`cypress/support/commands.ts`)
- cy.containsPath('cypress/fixtures/example.json')
- })
+ cy.contains('cypress.config.js')
+ cy.containsPath('cypress/support/component-index.html')
+ cy.containsPath('cypress/support/component.js')
+ cy.containsPath('cypress/support/commands.js')
+ cy.containsPath('cypress/fixtures/example.json')
cy.findByRole('button', { name: 'Continue' }).click()
-
- verifyChooseABrowserPage()
})
- // TODO: unskip once Object API lands https://github.com/cypress-io/cypress/pull/20861
- it.skip('setup component testing with typescript files', () => {
- scaffoldAndOpenProject('pristine')
+ it('setup component testing with typescript files', () => {
+ scaffoldAndOpenProject('pristine-yarn')
cy.visitLaunchpad()
verifyWelcomePage({ e2eIsConfigured: false, ctIsConfigured: false })
@@ -546,26 +537,21 @@ describe('Launchpad: Setup Project', () => {
cy.findByText('Confirm the front-end framework and bundler used in your project.')
- cy.findByRole('button', { name: 'Front-end Framework React.js (detected)' }).click()
+ cy.contains('Pick a framework').click()
cy.findByRole('option', { name: 'Create React App' }).click()
- cy.findByRole('button', { name: 'TypeScript' }).click()
cy.findByRole('button', { name: 'Next step' }).click()
cy.findByRole('button', { name: 'Skip' }).click()
- cy.get('[data-cy=valid]').within(() => {
- cy.contains('cypress.config.ts')
- cy.containsPath('cypress/support/component-index.html')
- cy.containsPath('cypress/support/component.ts')
- cy.containsPath('cypress/support/commands.ts')
- cy.containsPath('cypress/fixtures/example.json')
- })
+ cy.contains('cypress.config.ts')
+ cy.containsPath('cypress/support/component-index.html')
+ cy.containsPath('cypress/support/component.ts')
+ cy.containsPath('cypress/support/commands.ts')
+ cy.containsPath('cypress/fixtures/example.json')
verifyScaffoldedFiles('component')
cy.findByRole('button', { name: 'Continue' }).click()
-
- verifyChooseABrowserPage()
})
})
})
@@ -583,8 +569,7 @@ describe('Launchpad: Setup Project', () => {
cy.findByDisplayValue('yarn add -D react-scripts react-dom react').should('be.visible')
})
- // TODO: fix flaky test https://github.com/cypress-io/cypress/issues/23153
- it.skip('makes the right command for pnpm', () => {
+ it('makes the right command for pnpm', () => {
scaffoldAndOpenProject('pristine-pnpm')
cy.visitLaunchpad()
@@ -593,11 +578,10 @@ describe('Launchpad: Setup Project', () => {
cy.get('[data-testid="select-framework"]').click()
cy.findByText('Create React App').click()
cy.findByText('Next step').click()
- cy.get('code').should('contain.text', 'pnpm install -D ')
+ cy.findByTestId('terminal-prompt-input').should('have.value', 'pnpm install -D react-scripts react-dom react')
})
- // TODO: fix flaky test https://github.com/cypress-io/cypress/issues/23153
- it.skip('makes the right command for npm', () => {
+ it('makes the right command for npm', () => {
scaffoldAndOpenProject('pristine-npm')
cy.visitLaunchpad()
@@ -606,7 +590,7 @@ describe('Launchpad: Setup Project', () => {
cy.get('[data-testid="select-framework"]').click()
cy.findByText('Create React App').click()
cy.findByText('Next step').click()
- cy.get('code').should('contain.text', 'npm install -D ')
+ cy.findByTestId('terminal-prompt-input').should('have.value', 'npm install -D react-scripts react-dom react')
})
})
diff --git a/packages/network/package.json b/packages/network/package.json
index bb6283bea0ce..704eef7e2ecb 100644
--- a/packages/network/package.json
+++ b/packages/network/package.json
@@ -17,7 +17,7 @@
"@cypress/parse-domain": "2.4.0",
"bluebird": "3.5.3",
"concat-stream": "1.6.2",
- "debug": "^4.3.2",
+ "debug": "^4.3.4",
"fs-extra": "9.1.0",
"lodash": "^4.17.21",
"minimatch": "3.0.5",
diff --git a/packages/packherd-require/package.json b/packages/packherd-require/package.json
index 822ad9620876..18ecef37260f 100644
--- a/packages/packherd-require/package.json
+++ b/packages/packherd-require/package.json
@@ -17,7 +17,7 @@
},
"dependencies": {
"convert-source-map": "^1.7.0",
- "debug": "^4.3.1",
+ "debug": "^4.3.4",
"source-map-js": "^0.6.2"
},
"devDependencies": {
diff --git a/packages/proxy/lib/http/index.ts b/packages/proxy/lib/http/index.ts
index 67fc7629b155..87448976c8f6 100644
--- a/packages/proxy/lib/http/index.ts
+++ b/packages/proxy/lib/http/index.ts
@@ -23,6 +23,7 @@ import type { RemoteStates } from '@packages/server/lib/remote_states'
import type { CookieJar } from '@packages/server/lib/util/cookies'
import type { RequestedWithAndCredentialManager } from '@packages/server/lib/util/requestedWithAndCredentialManager'
import type { AutomationCookie } from '@packages/server/lib/automation/cookies'
+import { errorUtils } from '@packages/errors'
function getRandomColorFn () {
return chalk.hex(`#${Number(
@@ -70,7 +71,7 @@ export const defaultMiddleware = {
export type ServerCtx = Readonly<{
config: CyServer.Config & Cypress.Config
shouldCorrelatePreRequests?: () => boolean
- getFileServerToken: () => string
+ getFileServerToken: () => string | undefined
getCookieJar: () => CookieJar
remoteStates: RemoteStates
requestedWithAndCredentialManager: RequestedWithAndCredentialManager
@@ -182,7 +183,14 @@ export function _runStage (type: HttpStages, ctx: any, onError: Function) {
const fullCtx = {
next: () => {
fullCtx.next = () => {
- throw new Error('Error running proxy middleware: Cannot call this.next() more than once in the same middleware function. Doing so can cause unintended issues.')
+ const error = new Error('Error running proxy middleware: Detected `this.next()` was called more than once in the same middleware function, but a middleware can only be completed once.')
+
+ if (ctx.error) {
+ error.message = error.message += '\nThis middleware invocation previously encountered an error which may be related, see `error.cause`'
+ error['cause'] = ctx.error
+ }
+
+ throw error
}
copyChangedCtx()
@@ -207,6 +215,8 @@ export function _runStage (type: HttpStages, ctx: any, onError: Function) {
try {
middleware.call(fullCtx)
} catch (err) {
+ err.message = `Internal error while proxying "${ctx.req.method} ${ctx.req.proxiedUrl}" in ${middlewareName}:\n${err.message}`
+ errorUtils.logError(err)
fullCtx.onError(err)
}
})
@@ -230,7 +240,7 @@ export class Http {
config: CyServer.Config
shouldCorrelatePreRequests: () => boolean
deferredSourceMapCache: DeferredSourceMapCache
- getFileServerToken: () => string
+ getFileServerToken: () => string | undefined
remoteStates: RemoteStates
middleware: HttpMiddlewareStacks
netStubbingState: NetStubbingState
diff --git a/packages/proxy/package.json b/packages/proxy/package.json
index d49c2325ef39..7dcdf6b73260 100644
--- a/packages/proxy/package.json
+++ b/packages/proxy/package.json
@@ -18,7 +18,7 @@
"chalk": "2.4.2",
"charset": "1.0.1",
"common-tags": "1.8.0",
- "debug": "^4.3.2",
+ "debug": "^4.3.4",
"grapheme-splitter": "1.0.4",
"iconv-lite": "0.6.2",
"lodash": "^4.17.19",
diff --git a/packages/proxy/test/unit/http/index.spec.ts b/packages/proxy/test/unit/http/index.spec.ts
index a18aec6f2fc3..2d394857e489 100644
--- a/packages/proxy/test/unit/http/index.spec.ts
+++ b/packages/proxy/test/unit/http/index.spec.ts
@@ -63,13 +63,13 @@ describe('http', function () {
incomingRequest.throws(new Error('oops'))
error.callsFake(function () {
- expect(this.error.message).to.eq('oops')
+ expect(this.error.message).to.eq('Internal error while proxying "GET url" in 0:\noops')
this.end()
})
return new Http(httpOpts)
// @ts-ignore
- .handle({}, { on, off })
+ .handle({ method: 'GET', proxiedUrl: 'url' }, { on, off })
.then(function () {
expect(incomingRequest).to.be.calledOnce
expect(incomingResponse).to.not.be.called
@@ -88,13 +88,13 @@ describe('http', function () {
incomingResponse.throws(new Error('oops'))
error.callsFake(function () {
- expect(this.error.message).to.eq('oops')
+ expect(this.error.message).to.eq('Internal error while proxying "GET url" in 0:\noops')
this.end()
})
return new Http(httpOpts)
// @ts-ignore
- .handle({}, { on, off })
+ .handle({ method: 'GET', proxiedUrl: 'url' }, { on, off })
.then(function () {
expect(incomingRequest).to.be.calledOnce
expect(incomingResponse).to.be.calledOnce
@@ -141,7 +141,7 @@ describe('http', function () {
})
error.callsFake(function () {
- expect(this.error.message).to.eq('goto error stack')
+ expect(this.error.message).to.eq('Internal error while proxying "GET url" in 1:\ngoto error stack')
expect(this).to.include.keys(expectedKeys)
this.errorAdded = errorAdded
this.next()
@@ -159,7 +159,7 @@ describe('http', function () {
return new Http(httpOpts)
// @ts-ignore
- .handle({}, { on, off })
+ .handle({ method: 'GET', proxiedUrl: 'url' }, { on, off })
.then(function () {
[
incomingRequest, incomingRequest2,
diff --git a/packages/proxy/test/unit/http/request-middleware.spec.ts b/packages/proxy/test/unit/http/request-middleware.spec.ts
index 396afb22b541..58995e35c758 100644
--- a/packages/proxy/test/unit/http/request-middleware.spec.ts
+++ b/packages/proxy/test/unit/http/request-middleware.spec.ts
@@ -770,4 +770,70 @@ describe('http/request-middleware', () => {
})
})
})
+
+ describe('SendRequestOutgoing', () => {
+ const { SendRequestOutgoing } = RequestMiddleware
+
+ let ctx
+
+ beforeEach(() => {
+ const headers = {}
+ const remoteStates = new RemoteStates(() => {})
+
+ ctx = {
+ onError: sinon.stub(),
+ request: {
+ create: (opts) => {
+ return {
+ inputArgs: opts,
+ on: (event, callback) => {
+ if (event === 'response') {
+ callback()
+ }
+ },
+ }
+ },
+ },
+ req: {
+ body: '{}',
+ headers,
+ socket: {
+ on: () => {},
+ },
+ },
+ res: {
+ on: (event, listener) => {},
+ off: (event, listener) => {},
+ } as Partial,
+ remoteStates,
+ }
+ })
+
+ context('same-origin file request', () => {
+ beforeEach(() => {
+ ctx.getFileServerToken = () => 'abcd1234'
+ ctx.req.proxiedUrl = 'https://www.cypress.io/file'
+ ctx.remoteStates.set({
+ origin: 'https://www.cypress.io',
+ strategy: 'file',
+ } as any)
+ })
+
+ it('adds `x-cypress-authorization` header', async () => {
+ await testMiddleware([SendRequestOutgoing], ctx)
+ .then(() => {
+ expect(ctx.req.headers['x-cypress-authorization']).to.equal('abcd1234')
+ })
+ })
+
+ it('handles nil fileServer token', async () => {
+ ctx.getFileServerToken = () => undefined
+
+ await testMiddleware([SendRequestOutgoing], ctx)
+ .then(() => {
+ expect(ctx.req.headers['x-cypress-authorization']).to.be.undefined
+ })
+ })
+ })
+ })
})
diff --git a/packages/proxy/test/unit/http/response-middleware.spec.ts b/packages/proxy/test/unit/http/response-middleware.spec.ts
index 0e40bd65bb4b..efed881d6d5f 100644
--- a/packages/proxy/test/unit/http/response-middleware.spec.ts
+++ b/packages/proxy/test/unit/http/response-middleware.spec.ts
@@ -31,41 +31,70 @@ describe('http/response-middleware', function () {
])
})
- it('errors if this.next() is called more than once in the same middleware', function (done) {
- const middleware = function () {
- this.next()
- this.next()
- }
+ describe('multiple this.next invocations', () => {
+ context('within the same middleware', () => {
+ it('throws an error', function (done) {
+ const middleware = function () {
+ this.next()
+ this.next()
+ }
+
+ testMiddleware([middleware], {
+ res: {
+ on: (event, listener) => {},
+ off: (event, listener) => {},
+ },
+ onError (err) {
+ expect(err.message).to.equal('Internal error while proxying "undefined undefined" in 0:\nError running proxy middleware: Detected `this.next()` was called more than once in the same middleware function, but a middleware can only be completed once.')
+
+ done()
+ },
+ })
+ })
- testMiddleware([middleware], {
- res: {
- on: (event, listener) => {},
- off: (event, listener) => {},
- },
- onError (err) {
- expect(err.message).to.equal('Error running proxy middleware: Cannot call this.next() more than once in the same middleware function. Doing so can cause unintended issues.')
+ it('includes a previous context error in error message if one exists', (done) => {
+ const middleware = function () {
+ this.next()
+ this.next()
+ }
+ const error = new Error('previous error')
- done()
- },
+ testMiddleware([middleware], {
+ error,
+ res: {
+ on: (event, listener) => {},
+ off: (event, listener) => {},
+ },
+ onError (err) {
+ expect(err.message).to.contain('This middleware invocation previously encountered an error which may be related, see `error.cause`')
+ expect(err['cause']).to.equal(error)
+ done()
+ },
+ method: 'GET',
+ proxiedUrl: 'url',
+ })
+ })
})
- })
- it('does not error if this.next() is called more than once in different middleware', function () {
- const middleware1 = function () {
- this.next()
- }
- const middleware2 = function () {
- this.next()
- }
+ context('across different middleware', () => {
+ it('does not throw an error', function () {
+ const middleware1 = function () {
+ this.next()
+ }
+ const middleware2 = function () {
+ this.next()
+ }
- return testMiddleware([middleware1, middleware2], {
- res: {
- on: (event, listener) => {},
- off: (event, listener) => {},
- },
- onError () {
- throw new Error('onError should not be called')
- },
+ return testMiddleware([middleware1, middleware2], {
+ res: {
+ on: (event, listener) => {},
+ off: (event, listener) => {},
+ },
+ onError () {
+ throw new Error('onError should not be called')
+ },
+ })
+ })
})
})
diff --git a/packages/reporter/cypress/e2e/memory.cy.ts b/packages/reporter/cypress/e2e/memory.cy.ts
new file mode 100644
index 000000000000..0e11fc21e462
--- /dev/null
+++ b/packages/reporter/cypress/e2e/memory.cy.ts
@@ -0,0 +1,234 @@
+import { EventEmitter } from 'events'
+import { RootRunnable } from '../../src/runnables/runnables-store'
+import { MobxRunnerStore } from '@packages/app/src/store/mobx-runner-store'
+
+let runner: EventEmitter
+let runnables: RootRunnable
+const { _ } = Cypress
+
+function visitAndRenderReporter (studioEnabled: boolean = false, studioActive: boolean = false) {
+ cy.fixture('runnables_memory').then((_runnables) => {
+ runnables = _runnables
+ })
+
+ runner = new EventEmitter()
+
+ const runnerStore = new MobxRunnerStore('e2e')
+
+ runnerStore.setSpec({
+ name: 'foo.js',
+ relative: 'relative/path/to/foo.js',
+ absolute: '/absolute/path/to/foo.js',
+ })
+
+ cy.visit('/').then((win) => {
+ win.render({
+ studioEnabled,
+ runner,
+ runnerStore,
+ })
+ })
+
+ cy.get('.reporter').then(() => {
+ runner.emit('runnables:ready', runnables)
+ runner.emit('reporter:start', { studioActive })
+ })
+
+ return runnerStore
+}
+
+describe('tests', () => {
+ beforeEach(() => {
+ visitAndRenderReporter()
+ })
+
+ context('run mode', () => {
+ beforeEach(() => {
+ _.each(runnables.suites, (suite) => {
+ _.each(suite.tests, (test) => {
+ runner.emit('test:after:run', test, false)
+ })
+ })
+ })
+
+ it('clears logs for a collapsed test', () => {
+ cy.contains('passed')
+ .as('passed')
+ .closest('.runnable')
+ .should('have.class', 'test')
+ .find('.runnable-instruments').should('not.exist')
+
+ cy.get('@passed').click()
+
+ cy.get('@passed')
+ .parents('.collapsible').first()
+ .find('.attempt-item').eq(0)
+ .contains('No commands were issued in this test.')
+
+ cy.percySnapshot()
+ })
+
+ it('retains logs for an expanded test', () => {
+ cy.contains('failed')
+ .parents('.collapsible').first()
+ .should('have.class', 'is-open')
+ .find('.collapsible-content')
+ .should('be.visible')
+
+ cy.contains('failed')
+ .parents('.collapsible').first()
+ .find('.attempt-item')
+ .eq(0)
+ .find('.attempt-1')
+ .within(() => {
+ cy.get('.sessions-container')
+ cy.get('.runnable-agents-region')
+ cy.get('.runnable-routes-region')
+ cy.get('.runnable-commands-region')
+ })
+
+ cy.percySnapshot()
+ })
+
+ it('retains logs for failed attempt and clears logs for passed attempt after retry', () => {
+ cy.contains('passed after retry')
+ .parents('.collapsible').first()
+ .should('not.have.class', 'is-open')
+ .find('.collapsible-content')
+ .should('not.exist')
+
+ cy.contains('passed after retry').click()
+
+ cy.contains('passed after retry')
+ .parents('.collapsible').first()
+ .find('.attempt-item').as('attempts')
+
+ cy.get('@attempts').eq(0).as('firstAttempt')
+ .find('.collapsible')
+ .should('not.have.class', 'is-open')
+ .find('.collapsible-indicator').should('not.exist')
+
+ cy.get('@firstAttempt')
+ .contains('Attempt 1')
+ .click()
+
+ cy.get('@firstAttempt')
+ .find('.attempt-1')
+ .within(() => {
+ cy.get('.sessions-container')
+ cy.get('.runnable-agents-region')
+ cy.get('.runnable-routes-region')
+ cy.get('.runnable-commands-region')
+ })
+
+ cy.get('@attempts').eq(1).as('secondAttempt')
+ .find('.collapsible')
+ .should('have.class', 'is-open')
+ .find('.collapsible-indicator').should('not.exist')
+
+ cy.get('@secondAttempt')
+ .contains('No commands were issued in this test.')
+
+ cy.percySnapshot()
+ })
+
+ it('retains logs for failed attempts', () => {
+ cy.contains('failed with retries')
+ .parents('.collapsible').first()
+ .find('.attempt-item').as('attempts')
+
+ cy.get('@attempts').eq(0).as('firstAttempt')
+ .find('.collapsible')
+ .should('not.have.class', 'is-open')
+ .find('.collapsible-indicator').should('not.exist')
+
+ cy.get('@firstAttempt')
+ .contains('Attempt 1')
+ .click()
+
+ cy.get('@firstAttempt')
+ .find('.attempt-1')
+ .within(() => {
+ cy.get('.sessions-container')
+ cy.get('.runnable-agents-region')
+ cy.get('.runnable-routes-region')
+ cy.get('.runnable-commands-region')
+ })
+
+ cy.get('@attempts').eq(1).as('secondAttempt')
+ .find('.collapsible')
+ .should('have.class', 'is-open')
+ .find('.collapsible-content')
+ .should('be.visible')
+
+ cy.get('@secondAttempt')
+ .find('.attempt-2')
+ .within(() => {
+ cy.get('.sessions-container')
+ cy.get('.runnable-agents-region')
+ cy.get('.runnable-routes-region')
+ cy.get('.runnable-commands-region')
+ })
+
+ cy.contains('failed with retries')
+ .scrollIntoView()
+ .percySnapshot()
+ })
+ })
+
+ context('open mode', () => {
+ beforeEach(() => {
+ _.each(runnables.suites, (suite) => {
+ _.each(suite.tests, (test) => {
+ runner.emit('test:after:run', test, true)
+ })
+ })
+ })
+
+ it('retains logs for a collapsed test', () => {
+ cy.contains('passed')
+ .as('passed')
+ .closest('.runnable')
+ .should('have.class', 'test')
+ .find('.runnable-instruments').should('not.exist')
+
+ cy.get('@passed').click()
+
+ cy.get('@passed')
+ .parents('.collapsible').first()
+ .find('.attempt-item')
+ .eq(0)
+ .find('.attempt-1')
+ .within(() => {
+ cy.get('.sessions-container')
+ cy.get('.runnable-agents-region')
+ cy.get('.runnable-routes-region')
+ cy.get('.runnable-commands-region')
+ })
+
+ cy.percySnapshot()
+ })
+
+ it('retains logs for an expanded test', () => {
+ cy.contains('failed')
+ .parents('.collapsible').first()
+ .should('have.class', 'is-open')
+ .find('.collapsible-content')
+ .should('be.visible')
+
+ cy.contains('failed')
+ .parents('.collapsible').first()
+ .find('.attempt-item')
+ .eq(0)
+ .find('.attempt-1')
+ .within(() => {
+ cy.get('.sessions-container')
+ cy.get('.runnable-agents-region')
+ cy.get('.runnable-routes-region')
+ cy.get('.runnable-commands-region')
+ })
+
+ cy.percySnapshot()
+ })
+ })
+})
diff --git a/packages/reporter/cypress/e2e/unit/test_model.cy.ts b/packages/reporter/cypress/e2e/unit/test_model.cy.ts
index 2d6f0f03c4d6..f520c0b2d52e 100644
--- a/packages/reporter/cypress/e2e/unit/test_model.cy.ts
+++ b/packages/reporter/cypress/e2e/unit/test_model.cy.ts
@@ -101,7 +101,7 @@ describe('Test model', () => {
command.isLongRunning = true
- test.finish({} as UpdatableTestProps)
+ test.finish({} as UpdatableTestProps, false)
expect(test.isLongRunning).to.be.false
})
})
@@ -282,21 +282,21 @@ describe('Test model', () => {
it('sets the test as inactive', () => {
const test = createTest()
- test.finish({} as UpdatableTestProps)
+ test.finish({} as UpdatableTestProps, false)
expect(test.isActive).to.be.false
})
it('updates the state of the test', () => {
const test = createTest()
- test.finish({ state: 'failed' } as UpdatableTestProps)
+ test.finish({ state: 'failed' } as UpdatableTestProps, false)
expect(test.state).to.equal('failed')
})
it('updates the test err', () => {
const test = createTest()
- test.finish({ err: { name: 'SomeError' } as Err } as UpdatableTestProps)
+ test.finish({ err: { name: 'SomeError' } as Err } as UpdatableTestProps, false)
expect(test.err.name).to.equal('SomeError')
})
@@ -304,7 +304,7 @@ describe('Test model', () => {
const test = createTest({ hooks: [{ hookId: 'h1', hookName: 'before each' }] })
test.addLog(createCommand({ instrument: 'command' }))
- test.finish({ failedFromHookId: 'h1', err: { message: 'foo' } as Err } as UpdatableTestProps)
+ test.finish({ state: 'failed', failedFromHookId: 'h1', err: { message: 'foo' } as Err } as UpdatableTestProps, false)
expect(test.lastAttempt.hooks[1].failed).to.be.true
})
@@ -312,7 +312,7 @@ describe('Test model', () => {
const test = createTest()
expect(() => {
- test.finish({ hookId: 'h1' } as UpdatableTestProps)
+ test.finish({ hookId: 'h1' } as UpdatableTestProps, false)
}).not.to.throw()
})
})
diff --git a/packages/reporter/cypress/fixtures/runnables_memory.json b/packages/reporter/cypress/fixtures/runnables_memory.json
new file mode 100644
index 000000000000..b2cc249c667e
--- /dev/null
+++ b/packages/reporter/cypress/fixtures/runnables_memory.json
@@ -0,0 +1,484 @@
+{
+ "id": "r1",
+ "title": "",
+ "root": true,
+ "hooks": [],
+ "tests": [],
+ "suites": [
+ {
+ "id": "r2",
+ "title": "suite 1",
+ "root": false,
+ "hooks": [],
+ "tests": [
+ {
+ "id": "r3",
+ "title": "passed",
+ "state": "passed",
+ "hooks": [
+ {
+ "title": "\"before each\" hook",
+ "hookName": "before each",
+ "hookId": "h1",
+ "pending": false,
+ "body": "() => {\\n cy.session('test', () => {});\\n }",
+ "type": "hook",
+ "currentRetry": 0,
+ "retries": -1
+ }
+ ],
+ "agents": [
+ {
+ "id": 1,
+ "functionName": "get",
+ "name": "spy",
+ "alias": "getAlias",
+ "instrument": "agent",
+ "callCount": 1
+ }
+ ],
+ "routes": [
+ {
+ "id": 1,
+ "name": "route",
+ "numResponses": 1,
+ "method": "GET",
+ "url": "/",
+ "instrument": "route"
+ }
+ ],
+ "commands": [
+ {
+ "id": "c2",
+ "hookId": "h1",
+ "instrument": "command",
+ "message": "test",
+ "name": "session",
+ "sessionInfo": {
+ "id": "test",
+ "isGlobalSession": false,
+ "status": "created"
+ },
+ "state": "passed",
+ "testId": "r3",
+ "type": "parent"
+ },
+ {
+ "id": "c1",
+ "hookId": "r3",
+ "instrument": "command",
+ "message": "http://localhost:3000",
+ "name": "visit",
+ "state": "passed",
+ "testId": "r3",
+ "timeout": 4000,
+ "type": "parent",
+ "wallClockStartedAt": "2020-01-01T00:00:00.000Z"
+ }
+ ]
+ },
+ {
+ "id": "r4",
+ "title": "failed",
+ "state": "failed",
+ "err": {
+ "name": "CommandError",
+ "message": "failed to visit",
+ "stack": "failed to visit\n\ncould not visit http: //localhost:3000"
+ },
+ "hooks": [
+ {
+ "title": "\"before each\" hook",
+ "hookName": "before each",
+ "hookId": "h1",
+ "pending": false,
+ "body": "() => {\\n cy.session('test', () => {});\\n }",
+ "type": "hook",
+ "currentRetry": 0,
+ "retries": -1
+ }
+ ],
+ "agents": [
+ {
+ "id": 1,
+ "functionName": "get",
+ "name": "spy",
+ "alias": "getAlias",
+ "instrument": "agent",
+ "callCount": 1
+ }
+ ],
+ "routes": [
+ {
+ "id": 1,
+ "name": "route",
+ "numResponses": 1,
+ "method": "GET",
+ "url": "/",
+ "instrument": "route"
+ }
+ ],
+ "commands": [
+ {
+ "id": "c2",
+ "hookId": "h1",
+ "instrument": "command",
+ "message": "test",
+ "name": "session",
+ "sessionInfo": {
+ "id": "test",
+ "isGlobalSession": false,
+ "status": "created"
+ },
+ "state": "passed",
+ "testId": "r3",
+ "type": "parent"
+ },
+ {
+ "id": "c1",
+ "hookId": "r3",
+ "instrument": "command",
+ "message": "http://localhost:3000",
+ "name": "visit",
+ "state": "passed",
+ "testId": "r3",
+ "timeout": 4000,
+ "type": "parent",
+ "wallClockStartedAt": "2020-01-01T00:00:00.000Z"
+ }
+ ]
+ },
+ {
+ "id": "r5",
+ "title": "passed after retry",
+ "state": "passed",
+ "retries": 1,
+ "currentRetry": 1,
+ "hooks": [
+ {
+ "title": "\"before each\" hook",
+ "hookName": "before each",
+ "hookId": "h1",
+ "pending": false,
+ "body": "() => {\\n cy.session('test', () => {});\\n }",
+ "type": "hook",
+ "currentRetry": 0,
+ "retries": -1
+ }
+ ],
+ "agents": [
+ {
+ "id": 1,
+ "functionName": "get",
+ "name": "spy",
+ "alias": "getAlias",
+ "instrument": "agent",
+ "callCount": 1
+ }
+ ],
+ "routes": [
+ {
+ "id": 1,
+ "name": "route",
+ "numResponses": 1,
+ "method": "GET",
+ "url": "/",
+ "instrument": "route"
+ }
+ ],
+ "commands": [
+ {
+ "id": "c1",
+ "hookId": "h1",
+ "instrument": "command",
+ "message": "test",
+ "name": "session",
+ "sessionInfo": {
+ "id": "test",
+ "isGlobalSession": false,
+ "status": "created"
+ },
+ "state": "passed",
+ "testId": "r5",
+ "type": "parent"
+ },
+ {
+ "id": "c2",
+ "hookId": "r5",
+ "instrument": "command",
+ "message": "http://localhost:3000",
+ "name": "visit",
+ "state": "passed",
+ "testId": "r5",
+ "timeout": 4000,
+ "type": "parent",
+ "wallClockStartedAt": "2020-01-01T00:00:00.000Z"
+ }
+ ],
+ "prevAttempts": [
+ {
+ "hookId": "r88",
+ "id": "c1",
+ "instrument": "command",
+ "message": "#id",
+ "name": "get",
+ "state": "failed",
+ "testId": "r88",
+ "timeout": 4000,
+ "type": "parent",
+ "wallClockStartedAt": "2020-01-01T00:00:00.000Z",
+ "hooks": [
+ {
+ "title": "\"before each\" hook",
+ "hookName": "before each",
+ "hookId": "h1",
+ "pending": false,
+ "body": "() => {\\n cy.session('test', () => {});\\n }",
+ "type": "hook",
+ "currentRetry": 0,
+ "retries": -1
+ }
+ ],
+ "agents": [
+ {
+ "id": 1,
+ "functionName": "get",
+ "name": "spy",
+ "alias": "getAlias",
+ "instrument": "agent",
+ "callCount": 1
+ }
+ ],
+ "routes": [
+ {
+ "id": 1,
+ "name": "route",
+ "numResponses": 1,
+ "method": "GET",
+ "url": "/",
+ "instrument": "route"
+ }
+ ],
+ "commands": [
+ {
+ "id": "c2",
+ "hookId": "h1",
+ "instrument": "command",
+ "message": "test",
+ "name": "session",
+ "sessionInfo": {
+ "id": "test",
+ "isGlobalSession": false,
+ "status": "created"
+ },
+ "state": "passed",
+ "testId": "r3",
+ "type": "parent"
+ },
+ {
+ "hookId": "r5",
+ "id": "c3",
+ "instrument": "command",
+ "message": "#does_not_exist",
+ "name": "get",
+ "state": "failed",
+ "testId": "r5",
+ "timeout": 4000,
+ "type": "parent",
+ "wallClockStartedAt": "2020-01-01T00:00:00.000Z",
+ "err": {
+ "name": "CommandError",
+ "message": "failed to get",
+ "stack": "failed to get element"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "r6",
+ "title": "failed with retries",
+ "state": "failed",
+ "retries": 1,
+ "currentRetry": 1,
+ "hooks": [
+ {
+ "title": "\"before each\" hook",
+ "hookName": "before each",
+ "hookId": "h1",
+ "pending": false,
+ "body": "() => {\\n cy.session('test', () => {});\\n }",
+ "type": "hook",
+ "currentRetry": 0,
+ "retries": -1
+ }
+ ],
+ "agents": [
+ {
+ "id": 1,
+ "functionName": "get",
+ "name": "spy",
+ "alias": "getAlias",
+ "instrument": "agent",
+ "callCount": 1
+ }
+ ],
+ "routes": [
+ {
+ "id": 1,
+ "name": "route",
+ "numResponses": 1,
+ "method": "GET",
+ "url": "/",
+ "instrument": "route"
+ }
+ ],
+ "commands": [
+ {
+ "id": "c2",
+ "hookId": "h1",
+ "instrument": "command",
+ "message": "test",
+ "name": "session",
+ "sessionInfo": {
+ "id": "test",
+ "isGlobalSession": false,
+ "status": "created"
+ },
+ "state": "passed",
+ "testId": "r3",
+ "type": "parent"
+ },
+ {
+ "id": "c1",
+ "hookId": "r6",
+ "instrument": "command",
+ "message": "http://localhost:3000",
+ "name": "visit",
+ "state": "passed",
+ "testId": "r6",
+ "timeout": 4000,
+ "type": "parent",
+ "wallClockStartedAt": "2020-01-01T00:00:00.000Z"
+ },
+ {
+ "hookId": "r6",
+ "id": "c1",
+ "instrument": "command",
+ "message": "#does_not_exist",
+ "name": "get",
+ "state": "failed",
+ "testId": "r6",
+ "timeout": 4000,
+ "type": "parent",
+ "wallClockStartedAt": "2020-01-01T00:00:00.000Z",
+ "err": {
+ "name": "CommandError",
+ "message": "failed to get",
+ "stack": "failed to get element"
+ }
+ }
+ ],
+ "prevAttempts": [
+ {
+ "hookId": "r6",
+ "id": "c1",
+ "instrument": "command",
+ "message": "#does_not_exist",
+ "name": "get",
+ "state": "failed",
+ "testId": "r6",
+ "timeout": 4000,
+ "type": "parent",
+ "wallClockStartedAt": "2020-01-01T00:00:00.000Z",
+ "hooks": [
+ {
+ "title": "\"before each\" hook",
+ "hookName": "before each",
+ "hookId": "h1",
+ "pending": false,
+ "body": "() => {\\n cy.session('test', () => {});\\n }",
+ "type": "hook",
+ "currentRetry": 0,
+ "retries": -1
+ }
+ ],
+ "agents": [
+ {
+ "id": 1,
+ "functionName": "get",
+ "name": "spy",
+ "alias": "getAlias",
+ "instrument": "agent",
+ "callCount": 1
+ }
+ ],
+ "routes": [
+ {
+ "id": 1,
+ "name": "route",
+ "numResponses": 1,
+ "method": "GET",
+ "url": "/",
+ "instrument": "route"
+ }
+ ],
+ "commands": [
+ {
+ "id": "c1",
+ "hookId": "h1",
+ "instrument": "command",
+ "message": "test",
+ "name": "session",
+ "sessionInfo": {
+ "id": "test",
+ "isGlobalSession": false,
+ "status": "created"
+ },
+ "state": "passed",
+ "testId": "r6",
+ "type": "parent"
+ },
+ {
+ "id": "c2",
+ "hookId": "r6",
+ "instrument": "command",
+ "message": "http://localhost:3000",
+ "name": "visit",
+ "state": "passed",
+ "testId": "r6",
+ "timeout": 4000,
+ "type": "parent",
+ "wallClockStartedAt": "2020-01-01T00:00:00.000Z"
+ },
+ {
+ "hookId": "r6",
+ "id": "c3",
+ "instrument": "command",
+ "message": "#does_not_exist",
+ "name": "get",
+ "state": "failed",
+ "testId": "r6",
+ "timeout": 4000,
+ "type": "parent",
+ "wallClockStartedAt": "2020-01-01T00:00:00.000Z",
+ "err": {
+ "name": "CommandError",
+ "message": "failed to get",
+ "stack": "failed to get element"
+ }
+ }
+ ]
+ }
+ ],
+ "err": {
+ "name": "CommandError",
+ "message": "failed to get",
+ "stack": "failed to get element"
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/packages/reporter/package.json b/packages/reporter/package.json
index 71513df849c4..06db4dbf61a8 100644
--- a/packages/reporter/package.json
+++ b/packages/reporter/package.json
@@ -26,7 +26,7 @@
"cypress-multi-reporters": "1.4.0",
"cypress-real-events": "1.6.0",
"lodash": "^4.17.21",
- "markdown-it": "11.0.0",
+ "markdown-it": "11.0.1",
"mobx": "5.15.4",
"mobx-react": "6.1.8",
"prismjs": "1.21.0",
diff --git a/packages/reporter/src/attempts/attempt-model.ts b/packages/reporter/src/attempts/attempt-model.ts
index 59716cb78084..5c15dbfdecca 100644
--- a/packages/reporter/src/attempts/attempt-model.ts
+++ b/packages/reporter/src/attempts/attempt-model.ts
@@ -194,9 +194,23 @@ export default class Attempt {
}
}
- @action finish (props: UpdatableTestProps) {
+ @action finish (props: UpdatableTestProps, isInteractive: boolean) {
this.update(props)
this.isActive = false
+
+ // if the test is not open and we aren't in interactive mode, clear out the attempt details
+ if (!this.test.isOpen && !isInteractive) {
+ this._clear()
+ }
+ }
+
+ _clear () {
+ this.commands = []
+ this.routes = []
+ this.agents = []
+ this.hooks = []
+ this._logs = {}
+ this.sessions = {}
}
_addAgent (props: AgentProps) {
diff --git a/packages/reporter/src/lib/events.ts b/packages/reporter/src/lib/events.ts
index ddd3efe78f33..c808e71100a4 100644
--- a/packages/reporter/src/lib/events.ts
+++ b/packages/reporter/src/lib/events.ts
@@ -95,8 +95,8 @@ const events: Events = {
runnablesStore.runnableStarted(runnable)
}))
- runner.on('test:after:run', action('test:after:run', (runnable: TestProps) => {
- runnablesStore.runnableFinished(runnable)
+ runner.on('test:after:run', action('test:after:run', (runnable: TestProps, isInteractive: boolean) => {
+ runnablesStore.runnableFinished(runnable, isInteractive)
if (runnable.final && !appState.studioActive) {
statsStore.incrementCount(runnable.state!)
}
diff --git a/packages/reporter/src/runnables/runnables-store.ts b/packages/reporter/src/runnables/runnables-store.ts
index d20c5a81d008..ba8e955259b1 100644
--- a/packages/reporter/src/runnables/runnables-store.ts
+++ b/packages/reporter/src/runnables/runnables-store.ts
@@ -161,9 +161,9 @@ export class RunnablesStore {
})
}
- runnableFinished (props: TestProps) {
+ runnableFinished (props: TestProps, isInteractive: boolean) {
this._withTest(props.id, (test) => {
- test.finish(props)
+ test.finish(props, isInteractive)
})
}
diff --git a/packages/reporter/src/test/test-model.ts b/packages/reporter/src/test/test-model.ts
index 61682825e433..f5eac05a97df 100644
--- a/packages/reporter/src/test/test-model.ts
+++ b/packages/reporter/src/test/test-model.ts
@@ -186,11 +186,11 @@ export default class Test extends Runnable {
}
}
- @action finish (props: UpdatableTestProps) {
+ @action finish (props: UpdatableTestProps, isInteractive: boolean) {
this._isFinished = !(props.retries && props.currentRetry) || props.currentRetry >= props.retries
this._withAttempt(props.currentRetry || 0, (attempt: Attempt) => {
- attempt.finish(props)
+ attempt.finish(props, isInteractive)
})
}
diff --git a/packages/rewriter/package.json b/packages/rewriter/package.json
index f1f847af8813..10a30d0c955c 100644
--- a/packages/rewriter/package.json
+++ b/packages/rewriter/package.json
@@ -16,7 +16,7 @@
"bluebird": "3.7.2",
"chai": "4.2.0",
"chai-as-promised": "7.1.1",
- "debug": "^4.3.2",
+ "debug": "^4.3.4",
"lodash": "^4.17.19",
"parse5-html-rewriting-stream": "5.1.1",
"recast": "0.20.4"
diff --git a/packages/scaffold-config/package.json b/packages/scaffold-config/package.json
index ac0f9632f1e5..f35687c4f9b6 100644
--- a/packages/scaffold-config/package.json
+++ b/packages/scaffold-config/package.json
@@ -15,7 +15,7 @@
},
"dependencies": {
"compare-versions": "4.1.3",
- "debug": "^4.3.2",
+ "debug": "^4.3.4",
"fs-extra": "^9.1.0",
"globby": "^11.0.1"
},
diff --git a/packages/server/lib/server-base.ts b/packages/server/lib/server-base.ts
index 0cb818589c90..89d37c01174a 100644
--- a/packages/server/lib/server-base.ts
+++ b/packages/server/lib/server-base.ts
@@ -95,6 +95,12 @@ const notSSE = (req, res) => {
export type WarningErr = Record
+type FileServer = {
+ token: string
+ port: () => number
+ close: () => void
+}
+
export interface OpenServerOptions {
SocketCtor: typeof SocketE2E | typeof SocketCt
testingType: Cypress.TestingType
@@ -112,7 +118,7 @@ export abstract class ServerBase {
protected isListening: boolean
protected socketAllowed: SocketAllowed
protected requestedWithAndCredentialManager: RequestedWithAndCredentialManager
- protected _fileServer
+ protected _fileServer: FileServer | null
protected _baseUrl: string | null
protected _server?: DestroyableHttpServer
protected _socket?: TSocket
@@ -320,7 +326,7 @@ export abstract class ServerBase {
createNetworkProxy ({ config, remoteStates, requestedWithAndCredentialManager, shouldCorrelatePreRequests }) {
const getFileServerToken = () => {
- return this._fileServer.token
+ return this._fileServer?.token
}
this._netStubbingState = netStubbingState()
diff --git a/packages/server/lib/server-e2e.ts b/packages/server/lib/server-e2e.ts
index 7e83a5d79fd2..db37afb6a4e0 100644
--- a/packages/server/lib/server-e2e.ts
+++ b/packages/server/lib/server-e2e.ts
@@ -221,7 +221,7 @@ export class ServerE2E extends ServerBase {
if (!fullyQualifiedRe.test(urlStr)) {
handlingLocalFile = true
- options.headers['x-cypress-authorization'] = this._fileServer.token
+ options.headers['x-cypress-authorization'] = this._fileServer?.token
const state = this._remoteStates.set(urlStr, options)
diff --git a/packages/server/package.json b/packages/server/package.json
index f51620e9ae81..d94fe3da55dc 100644
--- a/packages/server/package.json
+++ b/packages/server/package.json
@@ -55,7 +55,7 @@
"cors": "2.8.5",
"data-uri-to-buffer": "2.0.1",
"dayjs": "^1.9.3",
- "debug": "^4.3.2",
+ "debug": "^4.3.4",
"dirt-simple-file-cache": "^0.4.0",
"duplexify": "4.1.1",
"electron-context-menu": "3.1.1",
diff --git a/packages/ts/package.json b/packages/ts/package.json
index ec92c6fc893d..6bca1270e523 100644
--- a/packages/ts/package.json
+++ b/packages/ts/package.json
@@ -12,7 +12,7 @@
"lint": "eslint --ext .js,.jsx,.ts,.tsx,.json, ."
},
"dependencies": {
- "debug": "^4.3.2",
+ "debug": "^4.3.4",
"ts-node": "^10.9.1",
"tslib": "2.3.1",
"typescript-cached-transpile": "^0.0.6"
diff --git a/packages/v8-snapshot-require/package.json b/packages/v8-snapshot-require/package.json
index 4c68c313fad8..309c0fe803bd 100644
--- a/packages/v8-snapshot-require/package.json
+++ b/packages/v8-snapshot-require/package.json
@@ -17,7 +17,7 @@
},
"dependencies": {
"@packages/packherd-require": "0.0.0-development",
- "debug": "^4.1.1",
+ "debug": "^4.3.4",
"source-map-js": "^0.6.2"
},
"devDependencies": {
diff --git a/renovate.json b/renovate.json
index 78f83499e1eb..d1c9a2b10224 100644
--- a/renovate.json
+++ b/renovate.json
@@ -2,7 +2,7 @@
"extends": [
"config:base"
],
- "automerge": true,
+ "automerge": false,
"major": {
"automerge": false
},
@@ -23,5 +23,20 @@
"schedule": [
"before 3am on the first day of the month"
],
- "dependencyDashboardApproval": true
+ "dependencyDashboardApproval": true,
+ "packageRules": [
+ {
+ "matchPackagePatterns": [
+ "*"
+ ],
+ "semanticCommitType": "chore"
+ },
+ {
+ "matchDepTypes": [
+ "dependencies",
+ "require"
+ ],
+ "semanticCommitType": "dependency"
+ }
+ ]
}
diff --git a/scripts/semantic-commits/parse-changelog.js b/scripts/semantic-commits/parse-changelog.js
index 786b61c62f70..f2bf54e90193 100644
--- a/scripts/semantic-commits/parse-changelog.js
+++ b/scripts/semantic-commits/parse-changelog.js
@@ -41,7 +41,7 @@ async function parseChangelog (pendingRelease = true) {
nextKnownLineBreak = index + 1
if (pendingRelease && !/_Released \d+\/\d+\/\d+ \(PENDING\)_/.test(line)) {
throw new Error(`Expected line number ${index + 1} to include "_Released xx/xx/xxxx (PENDING)_"`)
- } else if (!pendingRelease && !/_Released \d+\/\d+\/\d+__/.test(line)) {
+ } else if (!pendingRelease && !/_Released \d+\/\d+\/\d+_/.test(line)) {
throw new Error(`Expected line number ${index + 1} to include "_Released xx/xx/xxxx_"`)
}
@@ -51,7 +51,7 @@ async function parseChangelog (pendingRelease = true) {
throw new Error(`Expected line number ${index + 1} to be a line break`)
}
} else {
- const result = /\*\*.+?:\*\*/.exec(line)
+ const result = /^\*\*.+?:\*\*/.exec(line)
if (currentSection === '' && !result) {
throw new Error(`Expected line number ${index + 1} to be a valid section header. Received ${line}. Expected one of ...\n - ${userFacingSections.join('\n - ')}`)
diff --git a/scripts/semantic-commits/validate-binary-changelog.js b/scripts/semantic-commits/validate-binary-changelog.js
index a4fd625d3c0d..06e00b5879b1 100644
--- a/scripts/semantic-commits/validate-binary-changelog.js
+++ b/scripts/semantic-commits/validate-binary-changelog.js
@@ -6,13 +6,12 @@ const checkedInBinaryVersion = require('../../package.json').version
const changelog = async () => {
const latestReleaseInfo = await getCurrentReleaseData()
+ let hasVersionBump = checkedInBinaryVersion !== latestReleaseInfo.version
if (process.env.CIRCLECI) {
console.log({ checkedInBinaryVersion })
- const hasVersionBump = checkedInBinaryVersion !== latestReleaseInfo.version
-
- if (process.env.CIRCLE_BRANCH !== 'develop' && process.env.CIRCLE_BRANCH !== 'emily/changelog2' && !/^release\/\d+\.\d+\.\d+$/.test(process.env.CIRCLE_BRANCH) && !hasVersionBump) {
+ if (process.env.CIRCLE_BRANCH !== 'develop' && process.env.CIRCLE_BRANCH !== 'release-12.5.0' && !/^release\/\d+\.\d+\.\d+$/.test(process.env.CIRCLE_BRANCH) && !hasVersionBump) {
console.log('Only verify the entire changelog for develop, a release branch or any branch that bumped to the Cypress version in the package.json.')
return
@@ -30,6 +29,7 @@ const changelog = async () => {
return validateChangelog({
nextVersion,
changedFiles,
+ pendingRelease: !hasVersionBump,
commits,
})
}
diff --git a/scripts/semantic-commits/validate-changelog.js b/scripts/semantic-commits/validate-changelog.js
index 63334445fc49..4f8f9c89c679 100644
--- a/scripts/semantic-commits/validate-changelog.js
+++ b/scripts/semantic-commits/validate-changelog.js
@@ -38,7 +38,7 @@ function _getResolvedMessage (semanticType, prNumber, associatedIssues = []) {
function _printChangeLogExample (semanticType, prNumber, associatedIssues = []) {
const resolveMessage = _getResolvedMessage(semanticType, prNumber, associatedIssues)
- return `${userFacingChanges[semanticType].section}\n - . ${resolveMessage}`
+ return `${userFacingChanges[semanticType].section}\n\n - . ${resolveMessage}`
}
/**
@@ -106,7 +106,7 @@ const _handleErrors = (errors) => {
* Determines if the Cypress changelog has the correct next version and changelog entires given the provided
* list of commits.
*/
-async function validateChangelog ({ changedFiles, nextVersion, commits }) {
+async function validateChangelog ({ changedFiles, nextVersion, pendingRelease, commits }) {
const hasUserFacingCommits = commits.some(({ semanticType }) => hasUserFacingChange(semanticType))
if (!hasUserFacingCommits) {
@@ -132,13 +132,13 @@ async function validateChangelog ({ changedFiles, nextVersion, commits }) {
errors.push(`A changelog entry was not found in cli/CHANGELOG.md.`)
if (commits.length === 1) {
- errors.push(`Please add a changelog entry that describes the changes. Include this entry under the section:/\n\n${_printChangeLogExample(commits[0].semanticType, commits[0].prNumber, commits[0].associatedIssues)}`)
+ errors.push(`Please add a changelog entry that describes the changes. Include this entry under the section:\n\n${_printChangeLogExample(commits[0].semanticType, commits[0].prNumber, commits[0].associatedIssues)}`)
return _handleErrors(errors)
}
}
- const changelog = await parseChangelog()
+ const changelog = await parseChangelog(pendingRelease)
if (nextVersion && !changelog.version === `## ${nextVersion}`) {
errors.push(`The changelog version does not contain the next Cypress version of ${nextVersion}. If the changelog version is correct, please correct the pull request title to correctly reflect the change being made.`)
diff --git a/scripts/wait-on-circle-jobs.js b/scripts/wait-on-circle-jobs.js
index 1c6740f65348..0a2175af20d4 100644
--- a/scripts/wait-on-circle-jobs.js
+++ b/scripts/wait-on-circle-jobs.js
@@ -15,10 +15,6 @@ const jobName = process.env.CIRCLE_JOB || 'wait-on-circle-jobs'
const workflowId = process.env.CIRCLE_WORKFLOW_ID
-const branchesToAlwaysFinalize = ['develop']
-
-const requireAllJobsToPass = !branchesToAlwaysFinalize.includes(process.env.CIRCLE_BRANCH)
-
const getAuth = () => `${process.env.CIRCLE_TOKEN}:`
const verifyCI = () => {
@@ -86,7 +82,7 @@ const waitForAllJobs = async (jobNames, workflowId) => {
const runningJobNames = _.map(runningJobs, 'name')
const failedJobNames = _.map(failedJobs, 'name')
- if (requireAllJobsToPass && _.intersection(jobNames, failedJobNames).length) {
+ if (_.intersection(jobNames, failedJobNames).length) {
console.error('At least one failing job has prevented percy-finalize from running', failedJobs)
process.exit(1)
}
diff --git a/system-tests/package.json b/system-tests/package.json
index f3cc9396534a..e857fb91b62d 100644
--- a/system-tests/package.json
+++ b/system-tests/package.json
@@ -50,7 +50,7 @@
"cookie-parser": "1.4.5",
"cors": "2.8.5",
"dayjs": "^1.9.3",
- "debug": "^4.3.2",
+ "debug": "^4.3.4",
"dockerode": "3.3.1",
"execa": "4",
"express": "4.17.3",
diff --git a/system-tests/projects/module-api/package.json b/system-tests/projects/module-api/package.json
index df38c48b5eb1..4a7704dbf34e 100644
--- a/system-tests/projects/module-api/package.json
+++ b/system-tests/projects/module-api/package.json
@@ -11,7 +11,7 @@
"@bahmutov/all-paths": "1.0.2",
"chdir-promise": "0.6.2",
"check-more-types": "2.24.0",
- "debug": "4.3.3",
+ "debug": "4.3.4",
"lazy-ass": "1.6.0",
"mocha": "9.2.0",
"mocha-banner": "1.1.2",
diff --git a/system-tests/projects/module-api/yarn.lock b/system-tests/projects/module-api/yarn.lock
index 56c3645c1d99..8fba59fade41 100644
--- a/system-tests/projects/module-api/yarn.lock
+++ b/system-tests/projects/module-api/yarn.lock
@@ -207,6 +207,13 @@ debug@4.3.3:
dependencies:
ms "2.1.2"
+debug@4.3.4:
+ version "4.3.4"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
+ integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
+ dependencies:
+ ms "2.1.2"
+
decamelize@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837"
diff --git a/tooling/electron-mksnapshot/package.json b/tooling/electron-mksnapshot/package.json
index 357daccbaab2..953442e44a56 100644
--- a/tooling/electron-mksnapshot/package.json
+++ b/tooling/electron-mksnapshot/package.json
@@ -18,7 +18,7 @@
},
"dependencies": {
"@electron/get": "^1.12.4",
- "debug": "^4.3.1",
+ "debug": "^4.3.4",
"extract-zip": "^2.0.1",
"fs-extra": "^10.0.0",
"temp-dir": "^2.0.0"
diff --git a/tooling/packherd/package.json b/tooling/packherd/package.json
index f6912bcbadab..c49d3497b6a2 100644
--- a/tooling/packherd/package.json
+++ b/tooling/packherd/package.json
@@ -16,7 +16,7 @@
"watch": "tsc --watch"
},
"dependencies": {
- "debug": "^4.3.1",
+ "debug": "^4.3.4",
"esbuild": "^0.15.3"
},
"devDependencies": {
diff --git a/tooling/v8-snapshot/package.json b/tooling/v8-snapshot/package.json
index 685e6dce191d..7dca8f630ded 100644
--- a/tooling/v8-snapshot/package.json
+++ b/tooling/v8-snapshot/package.json
@@ -21,7 +21,7 @@
"@tooling/packherd": "0.0.0-development",
"ansi-colors": "^4.1.1",
"convert-source-map": "^1.8.0",
- "debug": "^4.1.1",
+ "debug": "^4.3.4",
"resolve-from": "^5.0.0",
"source-map-js": "^0.6.2",
"temp-dir": "^2.0.0",
diff --git a/yarn.lock b/yarn.lock
index 854865efd21e..3619331a127b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6166,12 +6166,7 @@
resolved "https://registry.yarnpkg.com/@types/crypto-js/-/crypto-js-4.1.1.tgz#602859584cecc91894eb23a4892f38cfa927890d"
integrity sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA==
-"@types/debug@4.1.5":
- version "4.1.5"
- resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd"
- integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==
-
-"@types/debug@^4.1.6":
+"@types/debug@4.1.7", "@types/debug@^4.1.6":
version "4.1.7"
resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.7.tgz#7cc0ea761509124709b8b2d1090d8f6c17aadb82"
integrity sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==
@@ -20400,10 +20395,10 @@ map-visit@^1.0.0:
promise "7.0.4"
socket-retry-connect "0.0.1"
-markdown-it@11.0.0:
- version "11.0.0"
- resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-11.0.0.tgz#dbfc30363e43d756ebc52c38586b91b90046b876"
- integrity sha512-+CvOnmbSubmQFSA9dKz1BRiaSMV7rhexl3sngKqFyXSagoA3fBdJQ8oZWtRy2knXdpDXaBw44euz37DeJQ9asg==
+markdown-it@11.0.1:
+ version "11.0.1"
+ resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-11.0.1.tgz#b54f15ec2a2193efa66dda1eb4173baea08993d6"
+ integrity sha512-aU1TzmBKcWNNYvH9pjq6u92BML+Hz3h5S/QpfTFwiQF852pLT+9qHsrhM9JYipkOXZxGn+sGH8oyJE9FD9WezQ==
dependencies:
argparse "^1.0.7"
entities "~2.0.0"
@@ -26600,10 +26595,10 @@ simple-get@^4.0.0:
once "^1.3.1"
simple-concat "^1.0.0"
-simple-git@3.15.0:
- version "3.15.0"
- resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-3.15.0.tgz#301a95a943c4f9b0a21d051eb6e6d0ffe4c9754f"
- integrity sha512-FiWoMPlcYHQ+ApRihUsGjC/ZmIlWj62S6MBCwOunczvXcLQt+9ZdrysDrR6QVepkRQfEAaBXrN2QtJKrN6zbtg==
+simple-git@3.16.0:
+ version "3.16.0"
+ resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-3.16.0.tgz#421773e24680f5716999cc4a1d60127b4b6a9dec"
+ integrity sha512-zuWYsOLEhbJRWVxpjdiXl6eyAyGo/KzVW+KFhhw9MqEEJttcq+32jTWSGyxTdf9e/YCohxRE+9xpWFj9FdiJNw==
dependencies:
"@kwsites/file-exists" "^1.1.1"
"@kwsites/promise-deferred" "^1.1.1"