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: update session api types & exposed global helpers #24980

Merged
merged 9 commits into from Dec 6, 2022
72 changes: 52 additions & 20 deletions cli/types/cypress.d.ts
Expand Up @@ -794,25 +794,6 @@ declare namespace Cypress {
onSpecWindow: (window: Window, specList: string[] | Array<() => Promise<void>>) => void
}

interface SessionOptions {
/**
* Whether or not to persist the session across all specs in the run.
* @default {false}
*/
cacheAcrossSpecs?: boolean
/**
* Function to run immediately after the session is created and `setup` function runs or
* after a session is restored and the page is cleared. If this returns `false`, throws an
* exception, returns a Promise which resolves to `false` or rejects or contains any failing
* Cypress command, the session is considered invalid.
*
* If validation fails immediately after `setup`, the test will fail.
* If validation fails after restoring a session, `setup` will re-run.
* @default {false}
*/
validate?: () => Promise<false | void> | void
}

type CanReturnChainable = void | Chainable | Promise<unknown>
type ThenReturn<S, R> =
R extends void ? Chainable<S> :
Expand Down Expand Up @@ -3423,8 +3404,59 @@ declare namespace Cypress {
}

interface Session {
// Clear all saved sessions and re-run the current spec file.
/**
* Clear all sessions, include cached global sessions, saved on the backend.
emilyrohrbough marked this conversation as resolved.
Show resolved Hide resolved
*/
clearAllSavedSessions: () => Promise<void>
/**
* Clear all storage and cookie data across all origins associated with the current session.
*/
clearCurrentSessionData: () => Promise<void>
/**
* Get all storage and cookie data across all origins associated with the current session.
*/
getCurrentSessionData: () => Promise<SessionData>
/**
* Get all storage and cookie data saved on the backend associated with the provided session id.
*/
getSession: (id: string) => Promise<ServerSessionData>
}

type ActiveSessions = Record<string, SessionData>

interface SessionData {
id: string
hydrated: boolean
cacheAcrossSpecs: SessionOptions['cacheAcrossSpecs']
cookies?: Cookie[] | null
localStorage?: OriginStorage[] | null
sessionStorage?: OriginStorage[] | null
setup: () => void
validate?: SessionOptions['validate']
}

interface ServerSessionData extends Omit<SessionData, 'setup' |'validate'> {
setup: string
validate?: string
}

interface SessionOptions {
/**
* Whether or not to persist the session across all specs in the run.
* @default {false}
*/
cacheAcrossSpecs?: boolean
/**
* Function to run immediately after the session is created and `setup` function runs or
* after a session is restored and the page is cleared. If this returns `false`, throws an
* exception, returns a Promise which resolves to `false` or rejects or contains any failing
* Cypress command, the session is considered invalid.
*
* If validation fails immediately after `setup`, the test will fail.
* If validation fails after restoring a session, `setup` will re-run.
* @default {false}
*/
validate?: () => Promise<false | void> | void
}

type SameSiteStatus = 'no_restriction' | 'strict' | 'lax'
Expand Down
6 changes: 3 additions & 3 deletions packages/app/cypress/e2e/runner/sessions.ui.cy.ts
Expand Up @@ -354,7 +354,7 @@ describe('runner/cypress sessions.ui.spec', {
before(() => {
cy.then(async () => {
await Cypress.action('cy:url:changed', '')
await Cypress.action('cy:visit:blank', { type: 'on' })
await Cypress.action('cy:visit:blank', { testIsolation: false })
})
.then(() => {
loadSpec({
Expand Down Expand Up @@ -550,7 +550,7 @@ describe('runner/cypress sessions.ui.spec', {
before(() => {
cy.then(async () => {
await Cypress.action('cy:url:changed', '')
await Cypress.action('cy:visit:blank', { type: 'on' })
await Cypress.action('cy:visit:blank', { testIsolation: false })
})
.then(() => {
loadSpec({
Expand Down Expand Up @@ -630,7 +630,7 @@ describe('runner/cypress sessions.ui.spec', {
before(() => {
cy.then(async () => {
await Cypress.action('cy:url:changed', '')
await Cypress.action('cy:visit:blank', { type: 'on' })
await Cypress.action('cy:visit:blank', { testIsolation: false })
})
.then(() => {
loadSpec({
Expand Down
79 changes: 38 additions & 41 deletions packages/driver/cypress/e2e/commands/sessions/manager.cy.ts
Expand Up @@ -22,7 +22,7 @@ describe('src/cy/commands/sessions/manager.ts', () => {
it('adds session when none were previously added', () => {
const cySpy = cy.spy(cy, 'state').withArgs('activeSessions')

const activeSession: Cypress.Commands.Session.ActiveSessions = {
const activeSession: Cypress.ActiveSessions = {
'session_1': {
id: 'session_1',
setup: () => {},
Expand All @@ -42,7 +42,7 @@ describe('src/cy/commands/sessions/manager.ts', () => {
})

it('adds session when other sessions were previously added', () => {
const existingSessions: Cypress.Commands.Session.ActiveSessions = {
const existingSessions: Cypress.ActiveSessions = {
'session_1': {
id: 'session_1',
setup: () => {},
Expand All @@ -59,7 +59,7 @@ describe('src/cy/commands/sessions/manager.ts', () => {

const cySpy = cy.stub(cy, 'state').callThrough().withArgs('activeSessions').returns(existingSessions)

const activeSession: Cypress.Commands.Session.ActiveSessions = {
const activeSession: Cypress.ActiveSessions = {
'session_3': {
id: 'session_3',
setup: () => {},
Expand Down Expand Up @@ -94,7 +94,7 @@ describe('src/cy/commands/sessions/manager.ts', () => {
})

it('returns session when found', () => {
const activeSessions: Cypress.Commands.Session.ActiveSessions = {
const activeSessions: Cypress.ActiveSessions = {
'session_1': {
id: 'session_1',
setup: () => {},
Expand Down Expand Up @@ -135,7 +135,7 @@ describe('src/cy/commands/sessions/manager.ts', () => {
})

it('updates the existing active sessions to "hydrated: false"', () => {
const existingSessions: Cypress.Commands.Session.ActiveSessions = {
const existingSessions: Cypress.ActiveSessions = {
'session_1': {
id: 'session_1',
setup: () => {},
Expand Down Expand Up @@ -166,24 +166,41 @@ describe('src/cy/commands/sessions/manager.ts', () => {
})
})

describe('.sessions', () => {
it('sessions.defineSession()', () => {
const sessionsManager = new SessionsManager(CypressInstance, cy)
const setup = cy.stub()
const sess = sessionsManager.sessions.defineSession({ id: '1', setup })

expect(sess).to.deep.eq({
id: '1',
setup,
validate: undefined,
cookies: null,
cacheAcrossSpecs: false,
localStorage: null,
sessionStorage: null,
hydrated: false,
})
it('.defineSession()', () => {
const sessionsManager = new SessionsManager(CypressInstance, cy)
const setup = cy.stub()
const sess = sessionsManager.defineSession({ id: '1', setup })

expect(sess).to.deep.eq({
id: '1',
setup,
validate: undefined,
cookies: null,
cacheAcrossSpecs: false,
localStorage: null,
sessionStorage: null,
hydrated: false,
})
})

it('.saveSessionData()', async () => {
const cypressSpy = cy.stub(CypressInstance, 'backend').callThrough().withArgs('save:session').resolves(null)

const sessionsManager = new SessionsManager(CypressInstance, cy)
const sessionsSpy = cy.stub(sessionsManager, 'setActiveSession')

const setup = cy.stub()
const sess = { id: '1', setup }

await sessionsManager.saveSessionData(sess)

expect(sessionsSpy).to.be.calledOnce
expect(sessionsSpy.getCall(0).args[0]).to.deep.eq({ 1: sess })

expect(cypressSpy).to.be.calledOnceWith('save:session')
})

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

Expand Down Expand Up @@ -260,26 +277,6 @@ describe('src/cy/commands/sessions/manager.ts', () => {
})
})

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

const sessionsManager = new SessionsManager(CypressInstance, cy)
const sessionsSpy = cy.stub(sessionsManager, 'setActiveSession')

const setup = cy.stub()
const sess = { id: '1', setup }

await sessionsManager.sessions.saveSessionData(sess)

expect(sessionsSpy).to.be.calledOnce
expect(sessionsSpy.getCall(0).args[0]).to.deep.eq({ 1: sess })

expect(cypressSpy).to.be.calledOnceWith('save:session')
})

// TODO:
describe('sessions.setSessionData', () => {})

it('sessions.getCookies()', async () => {
const cookies = [{ id: 'cookie' }]
const cypressSpy = cy.stub(CypressInstance, 'automation').withArgs('get:cookies').resolves(cookies)
Expand Down
1 change: 1 addition & 0 deletions packages/driver/package.json
Expand Up @@ -16,6 +16,7 @@
"@babel/code-frame": "7.8.3",
"@cypress/sinon-chai": "2.9.1",
"@cypress/unique-selector": "0.4.4",
"@cypress/webpack-dev-server": "0.0.0-development",
Copy link
Contributor

Choose a reason for hiding this comment

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

Why was this dependency added?

Copy link
Member Author

Choose a reason for hiding this comment

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

I was getting local typesript errors because we use it in our cypress.config.js file: https://github.com/cypress-io/cypress/blob/develop/packages/driver/cypress.config.ts#L2

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah I see. Didn't realize the driver had any component tests, though the ones that exist seem sort of unnecessary, especially since the driver doesn't render any components. We should probably remove those and the associated configuration, but that can wait.

"@cypress/webpack-preprocessor": "0.0.0-development",
"@cypress/what-is-circular": "1.0.1",
"@packages/config": "0.0.0-development",
Expand Down
16 changes: 6 additions & 10 deletions packages/driver/src/cy/commands/sessions/index.ts
Expand Up @@ -12,10 +12,6 @@ import {
statusMap,
} from './utils'

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

type SessionData = Cypress.Commands.Session.SessionData

/**
* Session data should be cleared with spec browser launch.
*
Expand All @@ -30,7 +26,7 @@ export default function (Commands, Cypress, cy) {

Cypress.on('run:start', () => {
// @ts-ignore
Object.values(Cypress.state('activeSessions') || {}).forEach((sessionData: ServerSessionData) => {
Object.values(Cypress.state('activeSessions') || {}).forEach((sessionData: Cypress.ServerSessionData) => {
if (sessionData.cacheAcrossSpecs) {
sessionsManager.registeredSessions.set(sessionData.id, true)
}
Expand Down Expand Up @@ -89,7 +85,7 @@ export default function (Commands, Cypress, cy) {
})
}

let session: SessionData = sessionsManager.getActiveSession(id)
let session: Cypress.SessionData = sessionsManager.getActiveSession(id)
const isRegisteredSessionForSpec = sessionsManager.registeredSessions.has(id)

if (session) {
Expand Down Expand Up @@ -120,7 +116,7 @@ export default function (Commands, Cypress, cy) {
$errUtils.throwErrByPath('sessions.session.duplicateId', { args: { id } })
}

session = sessions.defineSession({
session = sessionsManager.defineSession({
id,
setup,
validate: options.validate,
Expand Down Expand Up @@ -215,7 +211,7 @@ export default function (Commands, Cypress, cy) {

_log.set({ consoleProps: () => getConsoleProps(testSession) })

return sessions.setSessionData(testSession)
return sessionsManager.setSessionData(testSession)
}

function validateSession (existingSession, step: keyof typeof SESSION_STEPS) {
Expand Down Expand Up @@ -428,7 +424,7 @@ export default function (Commands, Cypress, cy) {
}

sessionsManager.registeredSessions.set(existingSession.id, true)
await sessions.saveSessionData(existingSession)
await sessionsManager.saveSessionData(existingSession)

return statusMap.complete(step)
})
Expand All @@ -440,7 +436,7 @@ export default function (Commands, Cypress, cy) {
* 2. validate session
* 3. if validation fails, catch error and recreate session
*/
const restoreSessionWorkflow = (existingSession: SessionData) => {
const restoreSessionWorkflow = (existingSession: Cypress.SessionData) => {
return cy.then(async () => {
setSessionLogStatus(statusMap.inProgress(SESSION_STEPS.restore))
await navigateAboutBlank()
Expand Down