@revoloo/cypress6
Version:
Cypress.io end to end testing tool
1,762 lines (1,362 loc) • 82.6 kB
JavaScript
const { $, _ } = Cypress
describe('src/cy/commands/assertions', () => {
before(() => {
cy
.visit('/fixtures/jquery.html')
.then(function (win) {
this.body = win.document.body.outerHTML
})
})
beforeEach(function () {
const doc = cy.state('document')
$(doc.body).empty().html(this.body)
})
context('#should', () => {
beforeEach(function () {
this.logs = []
cy.on('log:added', (attrs, log) => {
this.logs.push(log)
this.lastLog = log
})
return null
})
it('returns the subject for chainability', () => {
cy.noop({ foo: 'bar' }).should('deep.eq', { foo: 'bar' }).then((obj) => {
expect(obj).to.deep.eq({ foo: 'bar' })
})
})
it('can use negation', () => {
cy.noop(false).should('not.be.true')
})
it('works with jquery chai', () => {
const div = $('<div class=\'foo\'>asdf</div>')
cy.$$('body').append(div)
cy
.get('div.foo').should('have.class', 'foo').then(($div) => {
expect($div).to.match(div)
$div.remove()
})
})
it('can chain multiple assertions', () => {
cy
.get('body')
.should('contain', 'div')
.should('have.property', 'length', 1)
})
it('skips over utility commands', () => {
cy.on('command:retry', _.after(2, () => {
cy.$$('div:first').addClass('foo')
}))
cy.on('command:retry', _.after(4, () => {
cy.$$('div:first').attr('id', 'bar')
}))
cy.get('div:first').should('have.class', 'foo').debug().and('have.id', 'bar')
})
it('skips over aliasing', () => {
cy.on('command:retry', _.after(2, () => {
cy.$$('div:first').addClass('foo')
}))
cy.on('command:retry', _.after(4, () => {
cy.$$('div:first').attr('id', 'bar')
}))
cy.get('div:first').as('div').should('have.class', 'foo').debug().and('have.id', 'bar')
})
it('can change the subject', () => {
cy.get('input:first').should('have.property', 'length').should('eq', 1).then((num) => {
expect(num).to.eq(1)
})
})
it('changes the subject with chai-jquery', () => {
cy.$$('input:first').attr('id', 'input')
cy.get('input:first').should('have.attr', 'id').should('eq', 'input')
})
it('changes the subject with JSON', () => {
const obj = { requestJSON: { teamIds: [2] } }
cy.noop(obj).its('requestJSON').should('have.property', 'teamIds').should('deep.eq', [2])
})
// TODO: make cy.then retry
// https://github.com/cypress-io/cypress/issues/627
it.skip('outer assertions retry on cy.then', () => {
const obj = { foo: 'bar' }
cy.wrap(obj).then(() => {
setTimeout(() => {
obj.foo = 'baz'
}
, 1000)
return obj
}).should('deep.eq', { foo: 'baz' })
})
it('does it retry when wrapped', () => {
const obj = { foo: 'bar' }
cy.wrap(obj).then(() => {
setTimeout(() => {
obj.foo = 'baz'
}
, 100)
cy.wrap(obj)
}).should('deep.eq', { foo: 'baz' })
})
describe('function argument', () => {
it('waits until function is true', () => {
const button = cy.$$('button:first')
cy.on('command:retry', _.after(2, () => {
button.addClass('ready')
}))
cy.get('button:first').should(($button) => {
expect($button).to.have.class('ready')
})
})
it('works with regular objects', () => {
const obj = {}
cy.on('command:retry', _.after(2, () => {
obj.foo = 'bar'
}))
cy.wrap(obj).should((o) => {
expect(o).to.have.property('foo').and.eq('bar')
}).then(function () {
// wrap + have property + and eq
expect(this.logs.length).to.eq(3)
})
})
it('logs two assertions', () => {
_.delay(() => {
cy.$$('body').addClass('foo')
}
, Math.random() * 300)
_.delay(() => {
cy.$$('body').prop('id', 'bar')
}
, Math.random() * 300)
cy
.get('body').should(($body) => {
expect($body).to.have.class('foo')
expect($body).to.have.id('bar')
}).then(function () {
cy.$$('body').removeClass('foo').removeAttr('id')
expect(this.logs.length).to.eq(3)
// the messages should have been updated to reflect
// the current state of the <body> element
expect(this.logs[1].get('message')).to.eq('expected **<body#bar.foo>** to have class **foo**')
expect(this.logs[2].get('message')).to.eq('expected **<body#bar.foo>** to have id **bar**')
})
})
it('logs assertions as children even if subject is different', () => {
_.delay(() => {
cy.$$('body').addClass('foo')
}
, Math.random() * 300)
_.delay(() => {
cy.$$('body').prop('id', 'bar')
}
, Math.random() * 300)
cy
.get('body').should(($body) => {
expect($body.attr('class')).to.match(/foo/)
expect($body.attr('id')).to.include('bar')
}).then(function () {
cy.$$('body').removeClass('foo').removeAttr('id')
const types = _.map(this.logs, (l) => l.get('type'))
expect(types).to.deep.eq(['parent', 'child', 'child'])
expect(this.logs.length).to.eq(4)
})
})
it('can be chained', () => {
cy.wrap('ab')
.should((subject) => {
expect(subject).to.be.a('string')
expect(subject).to.contain('a')
})
.should((subject) => {
expect(subject).to.contain('b')
expect(subject).to.have.length(2)
})
.and((subject) => {
expect(subject).to.eq('ab')
expect(subject).not.to.contain('c')
})
.then(function () {
expect(this.logs.length).to.eq(8)
this.logs.slice(1).forEach((log) => {
expect(log.get('name')).to.eq('assert')
})
})
})
context('remote jQuery instances', () => {
beforeEach(function () {
this.remoteWindow = cy.state('window')
})
it('yields the remote jQuery instance', function () {
let fn
this.remoteWindow.$.fn.__foobar = (fn = function () {})
cy
.get('input:first').should(function ($input) {
const isInstanceOf = Cypress.utils.isInstanceOf($input, this.remoteWindow.$)
const hasProp = $input.__foobar === fn
expect(isInstanceOf).to.be.true
expect(hasProp).to.to.true
})
})
})
})
describe('not.exist', () => {
it('resolves eventually not exist', () => {
const button = cy.$$('button:first')
cy.on('command:retry', _.after(2, _.once(() => {
button.remove()
})))
cy.get('button:first').click().should('not.exist')
})
it('resolves all 3 assertions', (done) => {
const logs = []
cy.on('log:added', (attrs, log) => {
if (log.get('name') === 'assert') {
logs.push(log)
if (logs.length === 3) {
done()
}
}
})
cy
.get('#does-not-exist1').should('not.exist')
.get('#does-not-exist2').should('not.exist')
.get('#does-not-exist3').should('not.exist')
})
})
describe('have.text', () => {
it('resolves the assertion', () => {
cy.get('#list li').eq(0).should('have.text', 'li 0').then(function () {
const { lastLog } = this
expect(lastLog.get('name')).to.eq('assert')
expect(lastLog.get('state')).to.eq('passed')
expect(lastLog.get('ended')).to.be.true
})
})
})
describe('have.length', () => {
it('allows valid string numbers', () => {
const { length } = cy.$$('button')
cy.get('button').should('have.length', `${length}`)
})
it('throws when should(\'have.length\') isnt a number', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.eq('You must provide a valid number to a `length` assertion. You passed: `asdf`')
done()
})
cy.get('button').should('have.length', 'asdf')
})
it('does not log extra commands on fail and properly fails command + assertions', function (done) {
cy.on('fail', (err) => {
expect(this.logs.length).to.eq(6)
expect(this.logs[3].get('name')).to.eq('get')
expect(this.logs[3].get('state')).to.eq('failed')
expect(this.logs[3].get('error')).to.eq(err)
expect(this.logs[4].get('name')).to.eq('assert')
expect(this.logs[4].get('state')).to.eq('failed')
expect(this.logs[4].get('error').name).to.eq('AssertionError')
done()
})
cy
.root().should('exist').and('contain', 'foo')
.get('button').should('have.length', 'asdf')
})
it('finishes failed assertions and does not log extra commands when cy.contains fails', function (done) {
cy.on('fail', (err) => {
expect(this.logs.length).to.eq(2)
expect(this.logs[0].get('name')).to.eq('contains')
expect(this.logs[0].get('state')).to.eq('failed')
expect(this.logs[0].get('error')).to.eq(err)
expect(this.logs[1].get('name')).to.eq('assert')
expect(this.logs[1].get('state')).to.eq('failed')
expect(this.logs[1].get('error').name).to.eq('AssertionError')
done()
})
cy.contains('Nested Find').should('have.length', 2)
})
// https://github.com/cypress-io/cypress/issues/6384
it('can chain contains assertions off of cy.contains', () => {
cy.timeout(100)
cy.contains('foo')
.should('not.contain', 'asdfasdf')
cy.contains('foo')
.should('contain', 'foo')
cy.contains(/foo/)
.should('not.contain', 'asdfsadf')
cy.contains(/foo/)
.should('contain', 'foo')
// this isn't valid: .should('contain') does not support regex
// cy.contains(/foo/)
// .should('contain', /foo/)
})
})
describe('have.class', () => {
it('snapshots and ends the assertion after retrying', () => {
cy.on('command:retry', _.after(3, () => {
cy.$$('#foo').addClass('active')
}))
cy.contains('foo').should('have.class', 'active').then(function () {
const { lastLog } = this
expect(lastLog.get('name')).to.eq('assert')
expect(lastLog.get('ended')).to.be.true
expect(lastLog.get('state')).to.eq('passed')
expect(lastLog.get('snapshots').length).to.eq(1)
expect(lastLog.get('snapshots')[0]).to.be.an('object')
})
})
it('retries assertion until true', () => {
const button = cy.$$('button:first')
const retry = _.after(3, () => {
button.addClass('new-class')
})
cy.on('command:retry', retry)
cy.get('button:first').should('have.class', 'new-class')
})
})
// https://github.com/cypress-io/cypress/issues/9644
describe('calledOnceWith', () => {
it('be.calledOnceWith', () => {
const spy = cy.spy().as('spy')
setTimeout(() => {
spy({ bar: 'test' }, 1234)
}, 100)
cy.get('@spy').should(
'be.calledOnceWith',
{
bar: 'test',
},
)
})
it('be.calledOnceWithExactly', () => {
const spy = cy.spy().as('spy')
setTimeout(() => {
spy({ bar: 'test' })
}, 100)
cy.get('@spy').should(
'be.calledOnceWithExactly',
{ bar: 'test' },
)
const spy2 = cy.spy().as('spy2')
setTimeout(() => {
spy2({ bar: 'test' }, 12345)
}, 100)
cy.get('@spy2').should(
'not.be.calledOnceWithExactly',
{ bar: 'test' },
)
})
})
describe('errors', {
defaultCommandTimeout: 50,
}, () => {
it('should not be true', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.eq('expected false to be true')
done()
})
cy.noop(false).should('be.true')
})
it('throws err when not available chainable', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.eq('The chainer `dee` was not found. Could not build assertion.')
done()
})
cy.noop({}).should('dee.eq', {})
})
// https://github.com/cypress-io/cypress/issues/7870
it('handles when a string literal is thrown', () => {
cy.on('fail', (err) => {
expect(err.message).eq('error string')
})
cy.then(() => {
throw 'error string'
})
})
describe('language chainers err', () => {
// https://github.com/cypress-io/cypress/issues/883
const langChainers = ['to', 'be', 'been', 'is', 'that', 'which', 'and', 'has', 'have', 'with', 'at', 'of', 'same', 'but', 'does', 'still']
langChainers.forEach((langChainer) => {
it(`throws err when assertion contains only one language chainer: ${langChainer}`, (done) => {
cy.on('fail', (err) => {
expect(err.message).to.eq(`The chainer \`${langChainer}\` is a language chainer provided to improve the readability of your assertions, not an actual assertion. Please provide a valid assertion.`)
expect(err.docsUrl).to.eq('https://on.cypress.io/assertions')
done()
})
cy.noop(true).should(langChainer, true)
})
})
})
it('throws err when ends with a non available chainable', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.eq('The chainer `eq2` was not found. Could not build assertion.')
done()
})
cy.noop({}).should('deep.eq2', {})
})
it('logs \'should\' when non available chainer', function (done) {
cy.on('fail', (err) => {
const { lastLog } = this
expect(this.logs.length).to.eq(2)
expect(lastLog.get('name')).to.eq('should')
expect(lastLog.get('error')).to.eq(err)
expect(lastLog.get('state')).to.eq('failed')
expect(lastLog.get('snapshots').length).to.eq(1)
expect(lastLog.get('snapshots')[0]).to.be.an('object')
expect(lastLog.get('message')).to.eq('not.contain2, does-not-exist-foo-bar')
done()
})
cy.get('div:first').should('not.contain2', 'does-not-exist-foo-bar')
})
it('throws when eventually times out', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.eq('Timed out retrying after 50ms: expected \'<button>\' to have class \'does-not-have-class\'')
done()
})
cy.get('button:first').should('have.class', 'does-not-have-class')
})
it('throws when the subject isnt in the DOM', function (done) {
cy.$$('button:first').click(function () {
$(this).addClass('foo').remove()
})
cy.on('fail', (err) => {
const names = _.invokeMap(this.logs, 'get', 'name')
// the 'should' is not here because based on
// when we check for the element to be detached
// it never actually runs the assertion
expect(names).to.deep.eq(['get', 'click'])
expect(err.message).to.include('`cy.should()` failed because this element is detached')
done()
})
cy.get('button:first').click().should('have.class', 'foo').then(() => {
done('cy.should was supposed to fail')
})
})
it('throws when the subject eventually isnt in the DOM', function (done) {
cy.timeout(200)
const button = cy.$$('button:first')
cy.on('command:retry', _.after(2, _.once(() => {
button.addClass('foo').remove()
})))
cy.on('fail', (err) => {
const names = _.invokeMap(this.logs, 'get', 'name')
// should is present here due to the retry
expect(names).to.deep.eq(['get', 'click', 'assert'])
expect(err.message).to.include('`cy.should()` failed because this element is detached')
done()
})
cy.get('button:first').click().should('have.class', 'foo').then(() => {
done('cy.should was supposed to fail')
})
})
it('throws when should(\'have.length\') isnt a number', function (done) {
// we specifically turn off logging have.length validation errors
// because the assertion will already be logged
cy.on('fail', (err) => {
const { lastLog } = this
expect(this.logs.length).to.eq(3)
expect(err.message).to.eq('You must provide a valid number to a `length` assertion. You passed: `foo`')
expect(lastLog.get('name')).to.eq('should')
expect(lastLog.get('error')).to.eq(err)
expect(lastLog.get('state')).to.eq('failed')
expect(lastLog.get('snapshots').length).to.eq(1)
expect(lastLog.get('snapshots')[0]).to.be.an('object')
expect(lastLog.get('message')).to.eq('have.length, foo')
done()
})
cy.get('button').should('have.length', 'foo')
})
it('does not additionally log when .should is the current command', function (done) {
cy.on('fail', (err) => {
const { lastLog } = this
expect(this.logs.length).to.eq(1)
expect(lastLog.get('name')).to.eq('should')
expect(lastLog.get('error')).to.eq(err)
expect(lastLog.get('state')).to.eq('failed')
expect(lastLog.get('snapshots').length).to.eq(1)
expect(lastLog.get('snapshots')[0]).to.be.an('object')
expect(lastLog.get('message')).to.eq('deep.eq2, {}')
done()
})
cy.noop({}).should('deep.eq2', {})
})
it('logs and immediately fails on custom match assertions', function (done) {
cy.on('fail', (err) => {
const { lastLog } = this
expect(this.logs.length).to.eq(2)
expect(err.message).to.eq('`match` requires its argument be a `RegExp`. You passed: `foo`')
expect(lastLog.get('name')).to.eq('should')
expect(lastLog.get('error')).to.eq(err)
expect(lastLog.get('state')).to.eq('failed')
expect(lastLog.get('snapshots').length).to.eq(1)
expect(lastLog.get('snapshots')[0]).to.be.an('object')
expect(lastLog.get('message')).to.eq('match, foo')
done()
})
cy.wrap('foo').should('match', 'foo')
})
it('does not log ensureElExistence errors', function (done) {
cy.on('fail', (err) => {
expect(this.logs.length).to.eq(1)
done()
})
cy.get('#does-not-exist')
})
it('throws if used as a parent command', function (done) {
cy.on('fail', (err) => {
expect(this.logs.length).to.eq(1)
expect(err.message).to.include('looks like you are trying to call a child command before running a parent command')
done()
})
cy.should(() => {})
})
})
describe('.log', () => {
it('is type child', () => {
cy.get('button').should('match', 'button').then(function () {
const { lastLog } = this
expect(lastLog.get('name')).to.eq('assert')
expect(lastLog.get('type')).to.eq('child')
})
})
it('is type child when alias between assertions', () => {
cy.get('button').as('btn').should('match', 'button').then(function () {
const { lastLog } = this
expect(lastLog.get('name')).to.eq('assert')
expect(lastLog.get('type')).to.eq('child')
})
})
})
})
context('#and', () => {
it('proxies to #should', () => {
cy.noop({ foo: 'bar' }).should('have.property', 'foo').and('eq', 'bar')
})
})
context('#assert', () => {
beforeEach(function () {
this.logs = []
cy.on('log:added', (attrs, log) => {
this.logs.push(log)
if (attrs.name === 'assert') {
this.lastLog = log
}
})
return null
})
it('does not output should logs on failures', function (done) {
cy.on('fail', () => {
const { length } = this.logs
expect(length).to.eq(1)
done()
})
cy.noop({}).should('have.property', 'foo')
})
it('ends and snapshots immediately and sets child', (done) => {
cy.on('log:added', (attrs, log) => {
if (attrs.name === 'assert') {
cy.removeAllListeners('log:added')
expect(log.get('ended')).to.be.true
expect(log.get('state')).to.eq('passed')
expect(log.get('snapshots').length).to.eq(1)
expect(log.get('snapshots')[0]).to.be.an('object')
expect(log.get('type')).to.eq('child')
done()
}
})
cy.get('body').then(() => {
expect(cy.state('subject')).to.match('body')
})
})
it('sets type to child when subject matches', (done) => {
cy.on('log:added', (attrs, log) => {
if (attrs.name === 'assert') {
cy.removeAllListeners('log:added')
expect(log.get('type')).to.eq('child')
done()
}
})
cy.wrap('foo').then(() => {
expect('foo').to.eq('foo')
})
})
it('sets type to child current command had arguments but does not match subject', (done) => {
cy.on('log:added', (attrs, log) => {
if (attrs.name === 'assert') {
cy.removeAllListeners('log:added')
expect(log.get('type')).to.eq('child')
done()
}
})
cy.get('body').then(($body) => {
expect($body.length).to.eq(1)
})
})
it('sets type to parent when assertion did not involve current subject and didnt have arguments', (done) => {
cy.on('log:added', (attrs, log) => {
if (attrs.name === 'assert') {
cy.removeAllListeners('log:added')
expect(log.get('type')).to.eq('parent')
done()
}
})
cy.get('body').then(() => {
expect(true).to.be.true
})
})
it('removes rest of line when passing assertion includes \', but\' for jQuery subjects', (done) => {
cy.on('log:added', (attrs, log) => {
if (attrs.name === 'assert') {
cy.removeAllListeners('log:added')
expect(log.get('message')).to.eq('expected **<a>** to have attribute **href** with the value **#**')
done()
}
})
cy.get('a:first').then(($a) => {
expect($a).to.have.attr('href', '#')
})
})
it('does not replaces instances of word \'but\' with \'and\' for failing assertion', (done) => {
cy.on('log:added', (attrs, log) => {
if (attrs.name === 'assert') {
cy.removeAllListeners('log:added')
expect(log.get('message')).to.eq('expected **<a>** to have attribute **href** with the value **asdf**, but the value was **#**')
done()
}
})
cy.get('a:first').then(($a) => {
try {
expect($a).to.have.attr('href', 'asdf')
} catch (error) {} // eslint-disable-line no-empty
})
})
it('does not replace \'button\' with \'andton\'', (done) => {
cy.on('log:added', (attrs, log) => {
if (attrs.name === 'assert') {
cy.removeAllListeners('log:added')
expect(log.get('message')).to.eq('expected **<button>** to be **visible**')
done()
}
})
cy.get('button:first').then(($button) => {
expect($button).to.be.visible
})
})
it('#consoleProps for regular objects', (done) => {
cy.on('log:added', (attrs, log) => {
if (attrs.name === 'assert') {
cy.removeAllListeners('log:added')
expect(log.invoke('consoleProps')).to.deep.eq({
Command: 'assert',
expected: 1,
actual: 1,
Message: 'expected 1 to equal 1',
})
done()
}
})
cy.then(() => {
expect(1).to.eq(1)
})
})
it('#consoleProps for DOM objects', (done) => {
cy.on('log:added', (attrs, log) => {
if (attrs.name === 'assert') {
cy.removeAllListeners('log:added')
expect(log.invoke('consoleProps')).to.deep.eq({
Command: 'assert',
subject: log.get('subject'),
Message: 'expected <body> to have property length',
})
done()
}
})
cy
.get('body').then(($body) => {
expect($body).to.have.property('length')
})
})
it('#consoleProps for errors', (done) => {
cy.on('log:added', (attrs, log) => {
if (attrs.name === 'assert') {
cy.removeAllListeners('log:added')
expect(log.invoke('consoleProps')).to.deep.eq({
Command: 'assert',
expected: false,
actual: true,
Message: 'expected true to be false',
Error: log.get('error').stack,
})
done()
}
})
cy.then(() => {
try {
expect(true).to.be.false
} catch (err) {} // eslint-disable-line no-empty
})
})
describe('#patchAssert', () => {
it('wraps \#{this} and \#{exp} in \#{b}', (done) => {
cy.on('log:added', (attrs, log) => {
if (attrs.name === 'assert') {
cy.removeAllListeners('log:added')
expect(log.get('message')).to.eq('expected **foo** to equal **foo**')
done()
}
})
cy.then(() => {
expect('foo').to.eq('foo')
})
})
it('doesnt mutate error message', () => {
cy.then(() => {
try {
expect(true).to.eq(false)
} catch (e) {
expect(e.message).to.eq('expected true to equal false')
}
})
})
describe('jQuery elements', () => {
it('sets _obj to selector', (done) => {
cy.on('log:added', (attrs, log) => {
if (attrs.name === 'assert') {
cy.removeAllListeners('log:added')
expect(log.get('message')).to.eq('expected **<body>** to exist in the DOM')
done()
}
})
cy.get('body').then(($body) => {
expect($body).to.exist
})
})
it('matches empty string attributes', (done) => {
cy.on('log:added', (attrs, log) => {
if (attrs.name === 'assert') {
cy.removeAllListeners('log:added')
expect(log.get('message')).to.eq('expected **<input>** to have attribute **value** with the value **\'\'**')
done()
}
})
cy.$$('body').prepend($('<input value=\'\' />'))
cy.get('input').eq(0).then(($input) => {
expect($input).to.have.attr('value', '')
})
})
it('can chain off of chai-jquery assertions', () => {
const $el = cy.$$('ul#list')
expect($el).to.be.visible.and.have.id('list')
})
describe('without selector', () => {
it('exists', (done) => {
cy.on('log:added', (attrs, log) => {
if (attrs.name === 'assert') {
cy.removeAllListeners('log:added')
expect(log.get('message')).to.eq('expected **<div>** to exist in the DOM')
done()
}
})
// prepend an empty div so it has no id or class
cy.$$('body').prepend($('<div />'))
// expect($div).to.match("div")
cy.get('div').eq(0).then(($div) => {
expect($div).to.exist
})
})
it('uses element name', (done) => {
cy.on('log:added', (attrs, log) => {
if (attrs.name === 'assert') {
cy.removeAllListeners('log:added')
expect(log.get('message')).to.eq('expected **<input>** to match **input**')
done()
}
})
// prepend an empty div so it has no id or class
cy.$$('body').prepend($('<input />'))
cy.get('input').eq(0).then(($div) => {
expect($div).to.match('input')
})
})
})
describe('property assertions', () => {
it('has property', (done) => {
cy.on('log:added', (attrs, log) => {
if (attrs.name === 'assert') {
cy.removeAllListeners('log:added')
expect(log.get('message')).to.eq('expected **<button>** to have property **length**')
done()
}
})
cy.get('button:first').should('have.property', 'length')
})
it('passes on expected subjects without changing them', () => {
cy.state('window').$.fn.foo = 'bar'
cy
.get('input:first').then(($input) => {
expect($input).to.have.property('foo', 'bar')
})
})
})
})
})
})
context('chai assert', () => {
beforeEach(function () {
this.logs = []
cy.on('log:added', (attrs, log) => {
this.logs.push(log)
})
return null
})
it('equal', function () {
assert.equal(1, 1, 'one is one')
expect(this.logs[0].get('message')).to.eq('one is one: expected **1** to equal **1**')
})
it('isOk', function () {
assert.isOk({}, 'is okay')
expect(this.logs[0].get('message')).to.eq('is okay: expected **{}** to be truthy')
})
it('isFalse', function () {
assert.isFalse(false, 'is false')
expect(this.logs[0].get('message')).to.eq('is false: expected **false** to be false')
})
})
context('format quotation marks', () => {
const expectMarkdown = (test, message, done) => {
cy.then(() => {
test()
})
cy.on('log:added', (attrs, log) => {
if (attrs.name === 'assert') {
cy.removeAllListeners('log:added')
expect(log.get('message')).to.eq(message)
done()
}
})
}
it('preserves quotation marks in number strings', (done) => {
expectMarkdown(() => {
try {
expect(25).to.eq('25')
} catch (error) {} /* eslint-disable-line no-empty */
},
`expected **25** to equal **'25'**`,
done)
})
it('preserves quotation marks in empty string', (done) => {
expectMarkdown(() => {
try {
expect(42).to.eq('')
} catch (error) {} /* eslint-disable-line no-empty */
},
`expected **42** to equal **''**`,
done)
})
it('preserves quotation marks if escaped', (done) => {
expectMarkdown(
() => expect(`\'cypress\'`).to.eq(`\'cypress\'`),
// ****'cypress'**** -> ** for emphasizing result string + ** for emphasizing the entire result.
`expected **'cypress'** to equal ****'cypress'****`,
done,
)
})
it('removes quotation marks in DOM elements', (done) => {
expectMarkdown(
() => {
cy.get('body').then(($body) => {
expect($body).to.contain('div')
})
},
`expected **<body>** to contain **div**`,
done,
)
})
it('removes quotation marks in strings', (done) => {
expectMarkdown(() => expect('cypress').to.eq('cypress'), `expected **cypress** to equal **cypress**`, done)
})
it('removes quotation marks in objects', (done) => {
expectMarkdown(
() => expect({ foo: 'bar' }).to.deep.eq({ foo: 'bar' }),
`expected **{ foo: bar }** to deeply equal **{ foo: bar }**`,
done,
)
})
it('formats keys properly for "have.all.keys"', (done) => {
const person = {
name: 'Joe',
age: 20,
}
expectMarkdown(
() => expect(person).to.have.all.keys('name', 'age'),
`expected **{ name: Joe, age: 20 }** to have keys **name**, and **age**`,
done,
)
})
describe('formats strings with spaces', (done) => {
const tester = (message, done) => {
const nbspedMsg = message
.replace(/^\s+/, (match) => {
return match.replace(/\s/g, ' ')
})
.replace(/\s+$/, (match) => {
return match.replace(/\s/g, ' ')
})
expectMarkdown(() => expect(message).to.eq(message), `expected **'${nbspedMsg}'** to equal **'${nbspedMsg}'**`, done)
}
[' 37:46 ', ' test ', ' love'].forEach((v) => {
it(v, (done) => {
tester(v, done)
})
})
})
})
context('chai overrides', () => {
beforeEach(function () {
this.$body = cy.$$('body')
})
describe('#contain', () => {
it('can find input type submit by value', function () {
// $input creates an HTML element to be tested.
// eslint-disable-next-line no-unused-vars
const $input = cy.$$('<input type=\'submit\' value=\'click me\' />').appendTo(this.$body)
cy.get('input[type=submit]').should('contain', 'click me')
})
it('is true when element contains text', () => {
cy.get('div').should('contain', 'Nested Find')
})
it('calls super when not DOM element', () => {
cy.noop('foobar').should('contain', 'oob')
})
// https://github.com/cypress-io/cypress/issues/205
it('fails existence check on not.contain for non-existent DOM', function (done) {
cy.timeout(100)
cy.on('fail', ({ message }) => {
expect(message)
.include('.non-existent')
.include('but never found it')
done()
})
cy.get('.non-existent').should('not.contain', 'foo')
})
// https://github.com/cypress-io/cypress/issues/3549
it('is true when DOM el and not jQuery el contains text', () => {
cy.get('div').then(($el) => {
cy.wrap($el[1]).should('contain', 'Nested Find')
})
})
it('escapes quotes', () => {
const $span = '<span id="escape-quotes">shouldn\'t and can"t</span>'
cy.$$($span).appendTo(cy.$$('body'))
cy.get('#escape-quotes').should('contain', 'shouldn\'t')
})
})
describe('#match', () => {
it('calls super when provided a regex', () => {
expect('foo').to.match(/foo/)
})
it('throws when not provided a regex', () => {
const fn = () => {
expect('foo').to.match('foo')
}
expect(fn).to.throw('`match` requires its argument be a `RegExp`. You passed: `foo`')
})
it('throws with cy.should', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.eq('`match` requires its argument be a `RegExp`. You passed: `bar`')
done()
})
cy.noop('foo').should('match', 'bar')
})
it('does not affect DOM element matching', () => {
cy.get('body').should('match', 'body')
})
})
describe('#exist', () => {
it('uses $el.selector in expectation', (done) => {
cy.on('log:added', (attrs, log) => {
if (attrs.name === 'assert') {
cy.removeAllListeners('log:added')
expect(log.get('message')).to.eq('expected **#does-not-exist** not to exist in the DOM')
done()
}
})
cy.get('#does-not-exist').should('not.exist')
})
})
describe('#be.visible', () => {
it('sets type to child', (done) => {
cy.on('log:added', (attrs, log) => {
if (attrs.name === 'assert') {
cy.removeAllListeners('log:added')
expect(log.get('type')).to.eq('child')
done()
}
})
cy
.get('body')
.get('button').should('be.visible')
})
it('jquery wrapping els and selectors, not changing subject', () => {
cy.wrap(cy.$$('<div></div>').appendTo('body')).should('not.be.visible')
cy.wrap(cy.$$('<div></div>')).should('not.exist')
cy.wrap(cy.$$('<div></div>').appendTo('body')).should('exist')
cy.wrap(cy.$$('.non-existent')).should('not.exist')
})
// https://github.com/cypress-io/cypress/issues/205
describe('does not pass not.visible for non-dom', function () {
beforeEach(function () {
return Cypress.config('defaultCommandTimeout', 50)
})
it('undefined', function (done) {
let spy
spy = cy.spy(function (err) {
expect(err.message).to.contain('attempted to make')
return done()
}).as('onFail')
cy.on('fail', spy)
return cy.wrap().should('not.be.visible')
})
it('null', function (done) {
let spy
spy = cy.spy(function (err) {
expect(err.message).to.contain('attempted to make')
return done()
}).as('onFail')
cy.on('fail', spy)
return cy.wrap(null).should('not.be.visible')
})
it('[]', function (done) {
let spy
spy = cy.spy(function (err) {
expect(err.message).to.contain('attempted to make')
return done()
}).as('onFail')
cy.on('fail', spy)
return cy.wrap([]).should('not.be.visible')
})
it('{}', function (done) {
let spy
spy = cy.spy(function (err) {
expect(err.message).to.contain('attempted to make')
return done()
}).as('onFail')
cy.on('fail', spy)
return cy.wrap({}).should('not.be.visible')
})
it('fails not.visible for detached DOM', function (done) {
cy.on('fail', (err) => {
expect(err.message).include('detached')
done()
})
cy.get('<div></div>').should('not.be.visible')
})
it('fails not.visible for non-existent DOM', function (done) {
cy.on('fail', (err) => {
// prints selector on failure
// https://github.com/cypress-io/cypress/issues/5763
expect(err.message).include('.non-existent')
expect(err.message).include('Expected to find')
done()
})
cy.get('.non-existent', { timeout: 10 }).should('not.visible')
})
})
})
describe('#have.length', () => {
it('formats _obj with cypress', (done) => {
cy.on('log:added', (attrs, log) => {
if (attrs.name === 'assert') {
cy.removeAllListeners('log:added')
expect(log.get('message')).to.eq('expected **<button>** to have a length of **1**')
done()
}
})
cy.get('button:first').should('have.length', 1)
})
it('formats error _obj with cypress', (done) => {
cy.on('log:added', (attrs, log) => {
if (attrs.name === 'assert') {
cy.removeAllListeners('log:added')
expect(log.get('_error').message).to.eq('expected \'<body>\' to have a length of 2 but got 1')
done()
}
})
cy.get('body').should('have.length', 2)
})
it('does not touch non DOM objects', () => {
cy.noop([1, 2, 3]).should('have.length', 3)
})
it('rejects any element not in the document', function () {
cy.$$('<button />').appendTo(this.$body)
cy.$$('<button />').appendTo(this.$body)
const buttons = cy.$$('button')
const { length } = buttons
cy.on('command:retry', _.after(2, () => {
cy.$$('button:last').remove()
}))
cy.wrap(buttons).should('have.length', length - 1)
})
// https://github.com/cypress-io/cypress/issues/14484
it('does not override user-defined error message', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.contain('Filter should have 1 items')
done()
})
cy.get('div').should(($divs) => {
expect($divs, 'Filter should have 1 items').to.have.length(1)
})
})
})
})
context('chai plugins', () => {
beforeEach(function () {
this.logs = []
this.clearLogs = () => {
this.logs = []
}
cy.on('log:added', (attrs, log) => {
this.logs.push(log)
})
return null
})
context('data', () => {
beforeEach(function () {
this.$div = $('<div data-foo=\'bar\' />')
this.$div.data = function () {
throw new Error('data called')
}
})
it('no prop, with prop, negation, and chainable', function () {
expect(this.$div).to.have.data('foo') // 1
expect(this.$div).to.have.data('foo', 'bar') // 2,3
expect(this.$div).to.have.data('foo').and.eq('bar') // 4,5
expect(this.$div).to.have.data('foo').and.match(/bar/) // 6,7
expect(this.$div).not.to.have.data('baz') // 8
expect(this.logs.length).to.eq(8)
})
// https://github.com/cypress-io/cypress/issues/7314
it('supports a number argument', () => {
cy.get('#data-number').then(($el) => {
expect($el).to.have.data('number', 222)
expect($el).not.to.have.data('number', '222')
})
})
it('throws when obj is not DOM', function (done) {
cy.on('fail', (err) => {
expect(this.logs.length).to.eq(1)
expect(this.logs[0].get('error').message).to.be.ok
expect(err.message).to.include('> data')
expect(err.message).to.include('> {}')
done()
})
expect({}).to.have.data('foo')
})
})
context('class', () => {
beforeEach(function () {
this.$div = $('<div class=\'foo bar\' />')
this.$div.hasClass = function () {
throw new Error('hasClass called')
}
})
it('class, not class', function () {
expect(this.$div).to.have.class('foo') // 1
expect(this.$div).to.have.class('bar') // 2
expect(this.$div).not.to.have.class('baz') // 3
expect(this.logs.length).to.eq(3)
const l1 = this.logs[0]
const l3 = this.logs[2]
expect(l1.get('message')).to.eq(
'expected **<div.foo.bar>** to have class **foo**',
)
expect(l3.get('message')).to.eq(
'expected **<div.foo.bar>** not to have class **baz**',
)
})
// https://github.com/cypress-io/cypress/issues/7314
it('supports a number argument', () => {
cy.get('.999').then(($el) => {
expect($el).to.have.class(999)
expect($el).to.have.class('999')
})
})
it('throws when obj is not DOM', function (done) {
cy.on('fail', (err) => {
expect(this.logs.length).to.eq(1)
expect(this.logs[0].get('error').message).to.eq(
'expected \'foo\' to have class \'bar\'',
)
expect(err.message).to.include('> class')
expect(err.message).to.include('> foo')
done()
})
expect('foo').to.have.class('bar')
})
})
context('id', () => {
beforeEach(function () {
this.$div = $('<div id=\'foo\' />')
this.$div.prop = function () {
throw new Error('prop called')
}
this.$div.attr = function () {
throw new Error('attr called')
}
this.$div2 = $('<div />')
this.$div2.prop('id', 'foo')
this.$div2.prop = function () {
throw new Error('prop called')
}
this.$div2.attr = function () {
throw new Error('attr called')
}
this.$div3 = $('<div />')
this.$div3.attr('id', 'foo')
this.$div3.prop = function () {
throw new Error('prop called')
}
this.$div3.attr = function () {
throw new Error('attr called')
}
})
it('id, not id', function () {
expect(this.$div).to.have.id('foo') // 1
expect(this.$div).not.to.have.id('bar') // 2
expect(this.$div2).to.have.id('foo') // 3
expect(this.$div3).to.have.id('foo') // 4
expect(this.logs.length).to.eq(4)
const l1 = this.logs[0]
const l2 = this.logs[1]
expect(l1.get('message')).to.eq(
'expected **<div#foo>** to have id **foo**',
)
expect(l2.get('message')).to.eq(
'expected **<div#foo>** not to have id **bar**',
)
})
// https://github.com/cypress-io/cypress/issues/7314
it('supports a number argument', () => {
cy.get('#456').then(($el) => {
expect($el).to.have.id(456)
expect($el).to.have.id('456')
})
})
it('throws when obj is not DOM', function (done) {
cy.on('fail', (err) => {
expect(this.logs.length).to.eq(1)
expect(this.logs[0].get('error').message).to.eq(
'expected [] to have id \'foo\'',
)
expect(err.message).to.include('> id')
expect(err.message).to.include('> []')
done()
})
expect([]).to.have.id('foo')
})
})
context('html', () => {
beforeEach(function () {
this.$div = $('<div><button>button</button></div>')
this.$div.html = function () {
throw new Error('html called')
}
})
it('html, not html, contain html', function () {
expect(this.$div).to.have.html('<button>button</button>') // 1
expect(this.$div).not.to.have.html('foo') // 2
expect(this.logs.length).to.eq(2)
const l1 = this.logs[0]
const l2 = this.logs[1]
expect(l1.get('message')).to.eq(
'expected **<div>** to have HTML **<button>button</button>**',
)
expect(l2.get('message')).to.eq(
'expected **<div>** not to have HTML **foo**',
)
this.clearLogs()
expect(this.$div).to.contain.html('<button>')
expect(this.logs[0].get('message')).to.eq(
'expected **<div>** to contain HTML **<button>**',
)
this.clearLogs()
expect(this.$div).to.not.contain.html('foo') // 4
expect(this.logs[0].get('message')).to.eq(
'expected **<div>** not to contain HTML **foo**',
)
this.clearLogs()
try {
expect(this.$div).to.have.html('<span>span</span>')
} catch (error) {
expect(this.logs[0].get('message')).to.eq(
'expected **<div>** to have HTML **<span>span</span>**, but the HTML was **<button>button</button>**',
)
}
this.clearLogs()
try {
expect(this.$div).to.contain.html('<span>span</span>')
} catch (error1) {
expect(this.logs[0].get('message')).to.eq(
'expected **<div>** to contain HTML **<span>span</span>**, but the HTML was **<button>button</button>**',
)
}
})
it('throws when obj is not DOM', function (done) {
cy.on('fail', (err) => {
expect(this.logs.length).to.eq(1)
expect(this.logs[0].get('error').message).to.eq(
'expected null to have HTML \'foo\'',
)
expect(err.message).to.include('> html')
expect(err.message).to.include('> null')
done()
})
expect(null).to.have.html('foo')
})
it('partial match', function () {
expect(this.$div).to.contain.html('button')
expect(this.$div).to.include.html('button')
expect(this.$div).to.not.contain.html('span')
cy.get('button').should('contain.html', 'button')
})
})
context('text', () => {
beforeEach(function () {
this.$div = $('<div>foo</div>')
this.$div.text = function () {
throw new Error('text called')
}
})
it('text, not text, contain text', function () {
expect(this.$div).to.have.text('foo') // 1
expect(this.$div).not.to.have.text('bar') // 2
expect(this.logs.length).to.eq(2)
const l1 = this.logs[0]
const l2 = this.logs[1]
expect(l1.get('message')).to.eq(
'expected **<div>** to have text **foo**',
)
expect(l2.get('message')).to.eq(
'expected **<div>** not to have text **bar**',
)
this.clearLogs()
expect(this.$div).to.contain.text('f')
expect(this.logs[0].get('message')).to.eq(
'expected **<div>** to contain text **f**',
)
this.clearLogs()
expect(this.$div).to.not.contain.text('foob')
expect(this.logs[0].get('message')).to.eq(
'expected **<div>** not to contain text **foob**',
)
this.clearLogs()
try {
expect(this.$div).to.have.text('bar')
} catch (error) {
expect(this.logs[0].get('message')).to.eq(
'expected **<div>** to have text **bar**, but the text was **foo**',
)
}
this.clearLogs()
try {
expect(this.$div).to.contain.text('bar')
} catch (error1) {
expect(this.logs[0].get('message')).to.eq(
'expected **<div>** to contain text **bar**, but the text was **foo**',