Skip to content

Commit

Permalink
chore(sessions): add additional tests (#21338)
Browse files Browse the repository at this point in the history
  • Loading branch information
emilyrohrbough committed May 5, 2022
1 parent aa6f3b8 commit dbbad31
Show file tree
Hide file tree
Showing 20 changed files with 682 additions and 356 deletions.
318 changes: 318 additions & 0 deletions packages/driver/cypress/integration/commands/sessions/sessions.spec.js
@@ -0,0 +1,318 @@
const baseUrl = Cypress.config('baseUrl')

before(() => {
// sessions has logic built in to persists sessions on UI refresh
Cypress.session.clearAllSavedSessions()
})

const expectCurrentSessionData = async (obj) => {
return Cypress.session.getCurrentSessionData()
.then((result) => {
cy.log(result)
expect(result.cookies.map((v) => v.name)).members(obj.cookies || [])
expect(result.localStorage).deep.members(obj.localStorage || [])
expect(result.sessionStorage).deep.members(obj.sessionStorage || [])
})
}

describe('cy.session', { retries: 0 }, () => {
describe('args', () => {
it('accepts string as id', () => {
cy.session('session-id', () => {})
cy.session({ name: 'session-id', zkey: 'val' }, () => {})
})

it('accepts array as id', () => {
cy.session('session-id', () => {})
})

it('accepts object as id', () => {
cy.session('session-id', () => {})
})

// redundant?
it('accepts options as third argument', () => {
const setup = cy.stub().as('setupSession')
const validate = cy.stub().as('validateSession')

cy.session('session-id', setup, { validate })
cy.then(() => {
expect(setup).to.be.calledOnce
expect(validate).to.be.calledOnce
})
})
})

describe('errors', () => {
let lastLog = null
let logs = []

beforeEach(() => {
cy.on('log:added', (attrs, log) => {
if (attrs.name === 'session') {
lastLog = log
logs.push(log)
}
})

return null
})

it('throws error when experimentalSessionAndOrigin not enabled', { experimentalSessionAndOrigin: false, experimentalSessionSupport: false }, (done) => {
cy.on('fail', (err) => {
expect(lastLog.get('error')).to.eq(err)
expect(lastLog.get('state')).to.eq('failed')
expect(err.message).to.eq('`cy.session()` requires enabling the `experimentalSessionAndOrigin` flag.')
expect(err.docsUrl).to.eq('https://on.cypress.io/session')

done()
})

cy.session('sessions-not-enabled')
})

it('throws error when experimentalSessionSupport is enabled through test config', { experimentalSessionAndOrigin: false, experimentalSessionSupport: true }, (done) => {
cy.on('fail', (err) => {
expect(lastLog.get('error')).to.eq(err)
expect(lastLog.get('state')).to.eq('failed')
expect(err.message).to.eq('\`cy.session()\` requires enabling the \`experimentalSessionAndOrigin\` flag. The \`experimentalSessionSupport\` flag was enabled but was removed in Cypress version 9.6.0.')
expect(err.docsUrl).to.eq('https://on.cypress.io/session')

done()
})

cy.session('sessions-not-enabled')
})

it('throws error when experimentalSessionSupport is enabled through Cypress.config', { experimentalSessionAndOrigin: false }, (done) => {
Cypress.config('experimentalSessionSupport', true)

cy.on('fail', (err) => {
Cypress.config('experimentalSessionSupport', false)
expect(lastLog.get('error')).to.eq(err)
expect(lastLog.get('state')).to.eq('failed')
expect(err.message).to.eq('\`cy.session()\` requires enabling the \`experimentalSessionAndOrigin\` flag. The \`experimentalSessionSupport\` flag was enabled but was removed in Cypress version 9.6.0.')
expect(err.docsUrl).to.eq('https://on.cypress.io/session')
done()
})

cy.session('sessions-not-enabled')
})

it('throws when sessionId argument was not provided', function (done) {
cy.on('fail', (err) => {
expect(lastLog.get('error')).to.eq(err)
expect(lastLog.get('state')).to.eq('failed')
expect(err.message).to.eq('`cy.session()` was passed an invalid argument. The first argument `id` must be an string or serializable object.')
expect(err.docsUrl).to.eq('https://on.cypress.io/session')

done()
})

cy.session()
})

it('throws when sessionId argument is not an object', function (done) {
cy.on('fail', (err) => {
expect(lastLog.get('error')).to.eq(err)
expect(lastLog.get('state')).to.eq('failed')
expect(err.message).to.eq('`cy.session()` was passed an invalid argument. The first argument `id` must be an string or serializable object.')
expect(err.docsUrl).to.eq('https://on.cypress.io/session')

done()
})

cy.session(1)
})

it('throws when options argument is provided and is not an object', function (done) {
cy.on('fail', (err) => {
expect(lastLog.get('error')).to.eq(err)
expect(lastLog.get('state')).to.eq('failed')
expect(err.message).to.eq('`cy.session()` was passed an invalid argument. The optional third argument `options` must be an object.')
expect(err.docsUrl).to.eq('https://on.cypress.io/session')

done()
})

cy.session('some-session', () => {}, 'invalid_arg')
})

it('throws when options argument has an invalid option', function (done) {
cy.on('fail', (err) => {
expect(lastLog.get('error')).to.eq(err)
expect(lastLog.get('state')).to.eq('failed')
expect(err.message).to.eq('`cy.session()` was passed an invalid option: **invalid_key**\nAvailable options are: `validate`')
expect(err.docsUrl).to.eq('https://on.cypress.io/session')

done()
})

cy.session('some-session', () => {}, { invalid_key: 2 })
})

it('throws when options argument has an option with an invalid type', function (done) {
cy.on('fail', (err) => {
expect(lastLog.get('error')).to.eq(err)
expect(lastLog.get('state')).to.eq('failed')
expect(err.message).to.eq('`cy.session()` was passed an invalid option value. **validate** must be of type **function** but was **number**.')
expect(err.docsUrl).to.eq('https://on.cypress.io/session')

done()
})

cy.session('some-session', () => {}, { validate: 2 })
})

it('throws when setup function is not provided and existing session is not found', function (done) {
cy.on('fail', (err) => {
expect(lastLog.get('error')).to.eq(err)
expect(lastLog.get('state')).to.eq('failed')
expect(err.message).to.eq('No session is defined with the name\n **some-session**\nIn order to use `cy.session()`, provide a `setup` as the second argument:\n\n`cy.session(id, setup)`')
expect(err.docsUrl).to.eq('https://on.cypress.io/session')

done()
})

cy.session('some-session')
})

it('throws when multiple session calls with same sessionId but different options', function (done) {
cy.on('fail', async (err) => {
expect(lastLog.get('error')).to.eq(err)
expect(lastLog.get('state')).to.eq('failed')
expect(err.message).to.eq('You may not call `cy.session()` with a previously used name and different options. If you want to specify different options, please use a unique name other than **duplicate-session**.')
expect(err.docsUrl).to.eq('https://on.cypress.io/session')

await expectCurrentSessionData({
localStorage: [{ origin: baseUrl, value: { one: 'value' } }],
})

done()
})

cy.session('duplicate-session', () => {
// function content
window.localStorage.one = 'value'
})

cy.session('duplicate-session', () => {
// different function content
window.localStorage.two = 'value'
})
})

describe('options.validate failures', () => {
const errorHookMessage = 'This error occurred in a session validate hook after initializing the session. Because validation failed immediately after session setup we failed the test.'

it('throws when options.validate has a failing Cypress command', (done) => {
cy.on('fail', (err) => {
expect(err.message).contain('Expected to find element: `#does_not_exist`')
expect(err.message).contain(errorHookMessage)
expect(err.codeFrame).exist

done()
})

cy.session(['mock-session', 'command'], () => {
cy.log('setup')
}, {
validate () {
cy.get('#does_not_exist', { timeout: 20 })
},
})
})

it('throws when options.validate throws an error', (done) => {
cy.on('fail', (err) => {
expect(err.message).contain('validate error')
expect(err.message).contain(errorHookMessage)
expect(err.codeFrame).exist
done()
})

cy.session(['mock-session', 'throws'], () => {
cy.log('setup')
}, {
validate () {
throw new Error('validate error')
},
})
})

it('throws when options.validate rejects', (done) => {
cy.on('fail', (err) => {
expect(err.message).contain('validate error')
expect(err.message).contain(errorHookMessage)
expect(err.codeFrame).exist

done()
})

cy.session(['mock-session', 'rejects'], () => {
cy.log('setup')
}, {
validate () {
return Promise.reject(new Error('validate error'))
},
})
})

it('throws when options.validate returns false', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.contain('Your `cy.session` **validate** callback returned false.')
expect(err.message).contain(errorHookMessage)
expect(err.codeFrame).exist

done()
})

cy.session(['mock-session', 'return false'], () => {
cy.log('setup')
}, {
validate () {
return false
},
})
})

it('throws when options.validate resolves false', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.contain('Your `cy.session` **validate** callback resolved false.')
expect(err.message).contain(errorHookMessage)
expect(err.codeFrame).exist
done()
})

cy.session(['mock-session', 'resolves false'], () => {
cy.log('setup')
}, {
validate () {
return Promise.resolve(false)
},
})
})

// TODO: emilyrohrbough - 4/3/2022 - figure out what the below comment means
// TODO: cy.validate that will fail, hook into event, soft-reload inside and test everything is halted
// Look at other tests for cancellation
// make error collapsible by default

it('throws when options.validate returns Chainer<false>', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.contain('Your `cy.session` **validate** callback resolved false.')
expect(err.message).contain(errorHookMessage)
done()
})

cy.session(['mock-session', 'Chainer<false>'], () => {
cy.log('setup')
}, {
validate () {
return cy.wrap(false)
},
})
})
})
})
})
2 changes: 1 addition & 1 deletion packages/driver/package.json
Expand Up @@ -5,7 +5,7 @@
"scripts": {
"clean-deps": "rm -rf node_modules",
"cypress:open": "node ../../scripts/cypress open",
"cypress:run": "node ../../scripts/cypress run --spec \"cypress/integration/*/*\",\"cypress/integration/*/!(origin)/**/*\"",
"cypress:run": "node ../../scripts/cypress run --spec \"cypress/integration/*/*\",\"cypress/integration/*/!(origin|sessions)/**/*\"",
"cypress:open-experimentalSessionAndOrigin": "node ../../scripts/cypress open --config experimentalSessionAndOrigin=true",
"cypress:run-experimentalSessionAndOrigin": "node ../../scripts/cypress run --config experimentalSessionAndOrigin=true",
"postinstall": "patch-package",
Expand Down
6 changes: 3 additions & 3 deletions packages/driver/src/cy/commands/sessions/index.ts
Expand Up @@ -66,7 +66,7 @@ export default function (Commands, Cypress, cy) {
const sessionCommand = cy.state('current')

// stringify deterministically if we were given an object
id = typeof id === 'string' ? id : stringifyStable(id)
id = _.isString(id) ? id : stringifyStable(id)

if (options) {
if (!_.isObject(options)) {
Expand All @@ -77,14 +77,14 @@ export default function (Commands, Cypress, cy) {
'validate': 'function',
}

Object.keys(options).forEach((key) => {
Object.entries(options).forEach(([key, value]) => {
const expectedType = validOpts[key]

if (!expectedType) {
$errUtils.throwErrByPath('sessions.session.wrongArgOptionUnexpected', { args: { key } })
}

const actualType = typeof options[key]
const actualType = typeof value

if (actualType !== expectedType) {
$errUtils.throwErrByPath('sessions.session.wrongArgOptionInvalid', { args: { key, expected: expectedType, actual: actualType } })
Expand Down
2 changes: 1 addition & 1 deletion packages/driver/src/cypress.ts
Expand Up @@ -672,7 +672,7 @@ class $Cypress {
// clone the error object
// and set stack cleaned
// to prevent bluebird from
// attaching long stace traces
// attaching long stack traces
// which otherwise make this err
// unusably long
const err = $errUtils.makeErrFromObj(e) as BackendError
Expand Down
7 changes: 4 additions & 3 deletions packages/driver/src/cypress/error_messages.ts
Expand Up @@ -1651,18 +1651,19 @@ export default {

sessions: {
validate_callback_false: {
message: 'Your `cy.session` **validate** callback {{reason}}',
message: 'Your `cy.session` **validate** callback {{reason}}.',
},
experimentNotEnabled (experimentalSessionSupport) {
experimentNotEnabled ({ experimentalSessionSupport }) {
if (experimentalSessionSupport) {
return {
message: stripIndent`
${cmd('session')} requires enabling the \`experimentalSessionAndOrigin\` flag. The \`experimentalSessionSupport\` flag was enabled but was removed in Cypress version 9.6.0.`,
docsUrl: 'https://on.cypress.io/session',
}
}

return {
message: `${cmd('session')} requires enabling the \`experimentalSessionAndOrigin\` flag`,
message: `${cmd('session')} requires enabling the \`experimentalSessionAndOrigin\` flag.`,
docsUrl: 'https://on.cypress.io/session',
}
},
Expand Down

3 comments on commit dbbad31

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on dbbad31 May 5, 2022

Choose a reason for hiding this comment

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

Circle has built the linux x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/9.6.1/linux-x64/develop-dbbad318aa9649d9254b831a845cbc3b1628c48b/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on dbbad31 May 5, 2022

Choose a reason for hiding this comment

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

Circle has built the darwin x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/9.6.1/darwin-x64/develop-dbbad318aa9649d9254b831a845cbc3b1628c48b/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on dbbad31 May 5, 2022

Choose a reason for hiding this comment

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

Circle has built the win32 x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/9.6.1/win32-x64/develop-dbbad318aa9649d9254b831a845cbc3b1628c48b/cypress.tgz

Please sign in to comment.