UNPKG

@revoloo/cypress6

Version:

Cypress.io end to end testing tool

1,762 lines (1,362 loc) 82.6 kB
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, '&nbsp;') }) .replace(/\s+$/, (match) => { return match.replace(/\s/g, '&nbsp;') }) 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**',