Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(server): enable caching global session state #23600

Merged
merged 21 commits into from
Sep 16, 2022
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 32 additions & 33 deletions packages/app/src/runner/event-manager.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import Bluebird from 'bluebird'
import { EventEmitter } from 'events'
import type { MobxRunnerStore } from '@packages/app/src/store/mobx-runner-store'
import type { RunState } from '@packages/types/src/driver'
import type MobX from 'mobx'
import type { LocalBusEmitsMap, LocalBusEventMap, DriverToLocalBus, SocketToDriverMap } from './event-manager-types'
import type { AutomationElementId, FileDetails } from '@packages/types'

import type { RunState, CachedTestState, AutomationElementId, FileDetails, ReporterStartInfo, ReporterRunState } from '@packages/types'

import { logger } from './logger'
import type { Socket } from '@packages/socket/lib/browser'
Expand Down Expand Up @@ -88,8 +88,7 @@ export class EventManager {

const rerun = () => {
if (!this) {
// if the tests have been reloaded
// then there is nothing to rerun
// if the tests have been reloaded then there is nothing to rerun
return
}

Expand Down Expand Up @@ -254,10 +253,10 @@ export class EventManager {
this.saveState(state)
})

this.reporterBus.on('clear:session', () => {
this.reporterBus.on('clear:all:sessions', () => {
if (!Cypress) return

Cypress.backend('clear:session')
Cypress.backend('clear:sessions', true)
.then(rerun)
})

Expand Down Expand Up @@ -341,8 +340,8 @@ export class EventManager {
// @ts-ignore
const $window = this.$CypressDriver.$(window)

// This is a test-only even. It's used to
// trigger a re-reun for the drive rerun.cy.js spec.
// This is a test-only event. It's used to
// trigger a rerun for the driver rerun.cy.js spec.
$window.on('test:trigger:rerun', rerun)

// when we actually unload then
Expand Down Expand Up @@ -400,50 +399,50 @@ export class EventManager {
return Cypress.initialize({
$autIframe,
onSpecReady: () => {
// get the current runnable in case we reran mid-test due to a visit
// to a new domain
this.ws.emit('get:existing:run:state', (state: RunState = {}) => {
// get the current runnable states and cached test state
// in case we reran mid-test due to a visit to a new domain
this.ws.emit('get:cached:test:state', (runState: RunState = {}, testState: CachedTestState) => {
if (!Cypress.runner) {
// the tests have been reloaded
return
}

const hideCommandLog = window.__CYPRESS_CONFIG__.hideCommandLog

this.studioStore.initialize(config, state)
this.studioStore.initialize(config, runState)

const runnables = Cypress.runner.normalizeAll(state.tests, hideCommandLog)
const runnables = Cypress.runner.normalizeAll(runState.tests, hideCommandLog)

const run = () => {
performance.mark('initialize-end')
performance.measure('initialize', 'initialize-start', 'initialize-end')

this._runDriver(state)
this._runDriver(runState, testState)
}

if (!hideCommandLog) {
this.reporterBus.emit('runnables:ready', runnables)
}

if (state?.numLogs) {
Cypress.runner.setNumLogs(state.numLogs)
if (runState?.numLogs) {
Cypress.runner.setNumLogs(runState.numLogs)
}

if (state.startTime) {
Cypress.runner.setStartTime(state.startTime)
if (runState.startTime) {
Cypress.runner.setStartTime(runState.startTime)
}

if (config.isTextTerminal && !state.currentId) {
if (config.isTextTerminal && !runState.currentId) {
// we are in run mode and it's the first load
// store runnables in backend and maybe send to dashboard
return this.ws.emit('set:runnables:and:maybe:record:tests', runnables, run)
}

if (state.currentId) {
if (runState.currentId) {
// if we have a currentId it means
// we need to tell the Cypress to skip
// ahead to that test
Cypress.runner.resumeAtTest(state.currentId, state.emissions)
Cypress.runner.resumeAtTest(runState.currentId, runState.emissions)
}

return run()
Expand All @@ -469,7 +468,7 @@ export class EventManager {
}

return new Bluebird((resolve) => {
this.reporterBus.emit('reporter:collect:run:state', (reporterState) => {
this.reporterBus.emit('reporter:collect:run:state', (reporterState: ReporterRunState) => {
resolve({
...reporterState,
studio: {
Expand Down Expand Up @@ -729,9 +728,9 @@ export class EventManager {
window.top.addEventListener('message', crossOriginOnMessageRef, false)
}

_runDriver (state) {
_runDriver (runState: RunState, testState: CachedTestState) {
performance.mark('run-s')
Cypress.run(() => {
Cypress.run(testState, () => {
performance.mark('run-e')
performance.measure('run', 'run-s', 'run-e')
})
Expand All @@ -740,14 +739,14 @@ export class EventManager {

this.reporterBus.emit('reporter:start', {
startTime: Cypress.runner.getStartTime(),
numPassed: state.passed,
numFailed: state.failed,
numPending: state.pending,
autoScrollingEnabled: state.autoScrollingEnabled,
isSpecsListOpen: state.isSpecsListOpen,
scrollTop: state.scrollTop,
numPassed: runState.passed,
numFailed: runState.failed,
numPending: runState.pending,
autoScrollingEnabled: runState.autoScrollingEnabled,
isSpecsListOpen: runState.isSpecsListOpen,
scrollTop: runState.scrollTop,
studioActive: hasRunnableId,
})
} as ReporterStartInfo)
}

stop () {
Expand All @@ -763,8 +762,8 @@ export class EventManager {
state.setIsLoading(true)

if (!isRerun) {
// only clear session state when a new spec is selected
Cypress.backend('reset:session:state')
// only clear test state when a new spec is selected
Cypress.backend('reset:cached:test:state')
}

// when we are re-running we first need to stop cypress always
Expand Down
1 change: 1 addition & 0 deletions packages/data-context/src/data/ProjectConfigManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,7 @@ export class ProjectConfigManager {
}

async getFullInitialConfig (options: Partial<AllModeOptions> = this.options.ctx.modeOptions, withBrowsers = true): Promise<FullConfig> {
// return cached configuration for new spec and/or new navigating load when Cypress is running tests
if (this._cachedFullConfig) {
return this._cachedFullConfig
}
Expand Down
5 changes: 1 addition & 4 deletions packages/data-context/src/sources/HtmlDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,7 @@ export class HtmlDataSource {
window.__CYPRESS_CONFIG__ = ${JSON.stringify(serveConfig)};
window.__CYPRESS_TESTING_TYPE__ = '${this.ctx.coreData.currentTestingType}'
window.__CYPRESS_BROWSER__ = ${JSON.stringify(this.ctx.coreData.activeBrowser)}
${process.env.CYPRESS_INTERNAL_GQL_NO_SOCKET
? `window.__CYPRESS_GQL_NO_SOCKET__ = 'true';`
: ''
}
${process.env.CYPRESS_INTERNAL_GQL_NO_SOCKET ? `window.__CYPRESS_GQL_NO_SOCKET__ = 'true';` : ''}
</script>
`)
}
Expand Down
5 changes: 3 additions & 2 deletions packages/driver/cypress/e2e/commands/sessions/manager.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ describe('src/cy/commands/sessions/manager.ts', () => {
validate: undefined,
cookies: null,
localStorage: null,
sessionStorage: null,
hydrated: false,
})

Expand All @@ -245,15 +246,15 @@ describe('src/cy/commands/sessions/manager.ts', () => {
})

it('sessions.clearAllSavedSessions()', async () => {
const cypressSpy = cy.stub(CypressInstance, 'backend').callThrough().withArgs('clear:session').resolves(null)
const cypressSpy = cy.stub(CypressInstance, 'backend').callThrough().withArgs('clear:sessions', true).resolves(null)

const sessionsManager = new SessionsManager(CypressInstance, () => {})
const sessionsSpy = cy.stub(sessionsManager, 'clearActiveSessions')

await sessionsManager.sessions.clearAllSavedSessions()

expect(sessionsSpy).to.be.calledOnce
expect(cypressSpy).to.be.calledOnceWith('clear:session', null)
expect(cypressSpy).to.be.calledOnceWith('clear:sessions', true)
})

describe('.clearCurrentSessionData()', () => {
Expand Down
20 changes: 11 additions & 9 deletions packages/driver/src/cy/commands/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { LogUtils, Log } from '../../cypress/log'
import { bothUrlsMatchAndOneHasHash } from '../navigation'
import { $Location, LocationObject } from '../../cypress/location'

import type { RunState } from '@packages/types'

import debugFn from 'debug'
const debug = debugFn('cypress:driver:navigation')

Expand Down Expand Up @@ -1162,26 +1164,26 @@ export default (Commands, Cypress, cy, state, config) => {
// tell our backend we're changing origins
// TODO: add in other things we want to preserve
// state for like scrollTop
let s: Record<string, any> = {
let runState: RunState = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙏🏻

currentId: id,
tests: Cypress.runner.getTestsState(),
startTime: Cypress.runner.getStartTime(),
emissions: Cypress.runner.getEmissions(),
}

s.passed = Cypress.runner.countByTestState(s.tests, 'passed')
s.failed = Cypress.runner.countByTestState(s.tests, 'failed')
s.pending = Cypress.runner.countByTestState(s.tests, 'pending')
s.numLogs = LogUtils.countLogsByTests(s.tests)
runState.passed = Cypress.runner.countByTestState(runState.tests, 'passed')
runState.failed = Cypress.runner.countByTestState(runState.tests, 'failed')
runState.pending = Cypress.runner.countByTestState(runState.tests, 'pending')
runState.numLogs = LogUtils.countLogsByTests(runState.tests)

return Cypress.action('cy:collect:run:state')
.then((a = []) => {
.then((otherRunStates = []) => {
// merge all the states together holla'
s = _.reduce(a, (memo, obj) => {
runState = _.reduce(otherRunStates, (memo, obj) => {
return _.extend(memo, obj)
}, s)
}, runState)

return Cypress.backend('preserve:run:state', s)
return Cypress.backend('preserve:run:state', runState)
})
.then(() => {
// and now we must change the url to be the new
Expand Down
8 changes: 5 additions & 3 deletions packages/driver/src/cy/commands/sessions/manager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import _ from 'lodash'
import { $Location } from '../../../cypress/location'

import type { ServerSessionData } from '@packages/types'
import {
getCurrentOriginStorage,
setPostMessageLocalStorage,
Expand Down Expand Up @@ -120,6 +120,7 @@ export default class SessionsManager {
id: options.id,
cookies: null,
localStorage: null,
sessionStorage: null,
setup: options.setup,
hydrated: false,
validate: options.validate,
Expand All @@ -132,8 +133,9 @@ export default class SessionsManager {

clearAllSavedSessions: async () => {
this.clearActiveSessions()
const clearAllSessions = true

return this.Cypress.backend('clear:session', null)
return this.Cypress.backend('clear:sessions', clearAllSessions)
},

clearCurrentSessionData: async () => {
Expand Down Expand Up @@ -205,7 +207,7 @@ export default class SessionsManager {
}
},

getSession: (id: string) => {
getSession: (id: string): Promise<ServerSessionData> => {
return this.Cypress.backend('get:session', id)
},

Expand Down
2 changes: 1 addition & 1 deletion packages/driver/src/cy/commands/sessions/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import _ from 'lodash'
import $ from 'jquery'
import { $Location } from '../../../cypress/location'
import Bluebird from 'bluebird'
import { $Location } from '../../../cypress/location'

type SessionData = Cypress.Commands.Session.SessionData

Expand Down
6 changes: 5 additions & 1 deletion packages/driver/src/cypress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ import $Keyboard from './cy/keyboard'
import * as resolvers from './cypress/resolvers'
import { PrimaryOriginCommunicator, SpecBridgeCommunicator } from './cross-origin/communicator'

import type { CachedTestState } from '@packages/types'

const debug = debugFn('cypress:driver:cypress')

declare global {
Expand Down Expand Up @@ -277,11 +279,13 @@ class $Cypress {
}
}

run (fn) {
run (cachedTestState: CachedTestState, fn) {
if (!this.runner) {
$errUtils.throwErrByPath('miscellaneous.no_runner')
}

this.state(cachedTestState)

return this.runner.run(fn)
}

Expand Down
5 changes: 3 additions & 2 deletions packages/driver/types/cy/commands/session.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@ declare namespace Cypress {
type SessionSetup = (log: Cypress.Log) => Chainable<S>
type SessionValidation = (log: Cypress.Log) => Chainable<S>

interface LocalStorage {
interface Storage {
origin: string
value: Record<string, any>
}

interface SessionData {
id: string
cookies?: Array<Cypress.Cookie> | null
localStorage?: Array<LocalStorage> | null
localStorage?: Array<Storage> | null
sessionStorage?: Array<Storage> | null
setup: () => void
hydrated: boolean
validate?: Cypress.SessionOptions['validate']
Expand Down
4 changes: 2 additions & 2 deletions packages/proxy/lib/http/response-middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ const debug = null

// https://github.com/cypress-io/cypress/issues/1756
const zlibOptions = {
flush: zlib.Z_SYNC_FLUSH,
finishFlush: zlib.Z_SYNC_FLUSH,
flush: zlib.constants.Z_SYNC_FLUSH,
finishFlush: zlib.constants.Z_SYNC_FLUSH,
}

// https://github.com/cypress-io/cypress/issues/1543
Expand Down
7 changes: 1 addition & 6 deletions packages/reporter/src/header/stats-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,7 @@ import { action, computed, observable } from 'mobx'
import { TestState } from '../test/test-model'
import { IntervalID } from '../lib/types'

export interface StatsStoreStartInfo {
startTime: string
numPassed?: number
numFailed?: number
numPending?: number
}
import type { StatsStoreStartInfo } from '@packages/types'

const defaults = {
numPassed: 0,
Expand Down
Loading