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(sessions): add additional tests #21338

Merged
merged 27 commits into from May 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
203b2d6
chore(sessions): break out sessions manager code
emilyrohrbough Apr 25, 2022
e9a1a88
manager manages registered sessions
emilyrohrbough Apr 26, 2022
7321cfb
some unit tests
emilyrohrbough Apr 26, 2022
4002732
add more tests and some slight clean up
emilyrohrbough Apr 26, 2022
248b362
.
emilyrohrbough Apr 26, 2022
690e889
fix run mode issue.
emilyrohrbough Apr 26, 2022
2f6062c
bind correctly for spies
emilyrohrbough Apr 28, 2022
d5e9137
remove types. not sure on the return values.
emilyrohrbough Apr 28, 2022
4a72be7
Merge branch 'develop' into sessions-refactor-pt2
emilyrohrbough Apr 28, 2022
845f80b
fix tests
emilyrohrbough Apr 29, 2022
9e0ac1f
Merge branch 'develop' into sessions-refactor-pt2
emilyrohrbough Apr 29, 2022
56e3394
check in dump
emilyrohrbough Apr 29, 2022
d43630c
add some command tests
emilyrohrbough May 2, 2022
28d6d46
more driver tests and fix session config error
emilyrohrbough May 3, 2022
5182463
Fix parsing error argument
emilyrohrbough May 3, 2022
2e8136a
test for failed validation error messges
emilyrohrbough May 3, 2022
ab8fd1b
Merge branch 'develop' into session-refactor-pt3
emilyrohrbough May 3, 2022
01ea0ff
wait for diff pr
emilyrohrbough May 3, 2022
bed8cf3
update ui tests
emilyrohrbough May 3, 2022
a0a87b9
add more ui tests
emilyrohrbough May 3, 2022
faf1375
align with 10.0 test setup
emilyrohrbough May 4, 2022
6be1866
clean up
emilyrohrbough May 4, 2022
36f6319
will add later
emilyrohrbough May 4, 2022
0ba9a8d
fix
emilyrohrbough May 4, 2022
96184ad
fix tests
emilyrohrbough May 4, 2022
a2d6283
Merge branch 'develop' into session-refactor-pt3
emilyrohrbough May 5, 2022
896d285
Merge branch 'develop' into session-refactor-pt3
emilyrohrbough May 5, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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?
Copy link
Contributor

Choose a reason for hiding this comment

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

what is redundant here?

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