perf: remove reporter logs for collapsed tests in run mode (#25632)

Co-authored-by: Emily Rohrbough <emilyrohrbough@users.noreply.github.com>
This commit is contained in:
Matt Schile
2023-01-31 09:32:58 -07:00
committed by GitHub
parent ae9488ff54
commit facfd0d88c
9 changed files with 751 additions and 15 deletions

View File

@@ -12,10 +12,14 @@ _Released 01/31/2023 (PENDING)_
- Fixed an issue where alternative Microsoft Edge Beta, Canary, and Dev binary versions were not being discovered by Cypress.
Fixes [#25455](https://github.com/cypress-io/cypress/issues/25455).
**Performance:**
- Improved memory consumption in `run` mode by removing reporter logs for successful tests.
Fixes [#25230](https://github.com/cypress-io/cypress/issues/25230).
**Dependency Updates:**
- Upgraded [`underscore.string`](https://github.com/esamattis/underscore.string/blob/HEAD/CHANGELOG.markdown) from `3.3.5` to `3.3.6` to reference rebuilt assets after security patch to fix regular expression DDOS exploit.
Fixed in [#25574](https://github.com/cypress-io/cypress/pull/25574).
## 12.4.1

View File

@@ -551,7 +551,7 @@ class $Cypress {
// this event is how the reporter knows how to display
// stats and runnable properties such as errors
this.emit('test:after:run', ...args)
this.emit('test:after:run', args[0], this.config('isInteractive'))
this.maybeEmitCypressInCypress('mocha', 'test:after:run', args[0])
if (this.config('isTextTerminal')) {

View File

@@ -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()
})
})
})

View File

@@ -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()
})
})

View File

@@ -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"
}
}
]
}
]
}

View File

@@ -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) {

View File

@@ -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!)
}

View File

@@ -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)
})
}

View File

@@ -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)
})
}