@revoloo/cypress6
Version:
Cypress.io end to end testing tool
1,714 lines (1,351 loc) • 76.9 kB
JavaScript
const Cookie = require('js-cookie')
const { stripIndent } = require('common-tags')
const helpers = require('../../support/helpers')
const { _, Promise, $ } = Cypress
describe('src/cy/commands/navigation', () => {
context('#reload', () => {
before(() => {
cy
.visit('/fixtures/generic.html')
.then(function (win) {
this.body = win.document.body.outerHTML
})
})
beforeEach(function () {
const doc = cy.state('document')
this.win = cy.state('window')
$(doc.body).empty().html(this.body)
})
afterEach(function () {
cy.state('window', this.win)
})
it('calls into window.location.reload', () => {
const locReload = cy.spy(Cypress.utils, 'locReload')
cy.reload().then(() => {
expect(locReload).to.be.calledWith(false)
})
})
it('can pass forceReload', () => {
const locReload = cy.spy(Cypress.utils, 'locReload')
cy.reload(true).then(() => {
expect(locReload).to.be.calledWith(true)
})
})
it('can pass forceReload + options', () => {
const locReload = cy.spy(Cypress.utils, 'locReload')
cy.reload(true, {}).then(() => {
expect(locReload).to.be.calledWith(true)
})
})
it('can pass just options', () => {
const locReload = cy.spy(Cypress.utils, 'locReload')
cy.reload({}).then(() => {
expect(locReload).to.be.calledWith(false)
})
})
it('returns the window object', () => {
cy
.window().then((oldWin) => {
oldWin.foo = 'bar'
expect(oldWin.foo).to.eq('bar')
cy.reload().then((win) => {
expect(win).not.to.be.undefined
expect(win.foo).to.be.undefined
expect(win).to.eq(cy.state('window'))
})
})
})
it('sdfsdfdsf', function () {
$('sd')
})
it('removes window:load listeners', () => {
const listeners = cy.listeners('window:load')
const winLoad = cy.spy(cy, 'once').withArgs('window:load')
cy.reload().then(() => {
expect(winLoad).to.be.calledOnce
expect(cy.listeners('window:load')).to.deep.eq(listeners)
})
})
// TODO: fix this
it.skip('(FLAKY) sets timeout to Cypress.config(pageLoadTimeout)', {
pageLoadTimeout: 4567,
}, () => {
const timeout = cy.spy(Promise.prototype, 'timeout')
cy.reload().then(() => {
expect(timeout).to.be.calledWith(4567, 'reload')
})
})
it('fires stability:changed and window events events', () => {
const stub1 = cy.stub()
const stub2 = cy.stub()
const stub3 = cy.stub()
cy.on('stability:changed', stub1)
cy.on('window:before:unload', stub2)
cy.on('window:unload', stub3)
cy.reload().then(() => {
expect(stub1.firstCall).to.be.calledWith(false, 'beforeunload')
expect(stub1.secondCall).to.be.calledWith(true, 'load')
expect(stub2).to.be.calledOnce
expect(stub3).to.be.calledOnce
})
})
it('removes listeners', () => {
const win = cy.state('window')
const rel = cy.stub(win, 'removeEventListener')
cy.reload().then(() => {
expect(rel).to.be.calledWith('beforeunload')
expect(rel).to.be.calledWith('unload')
})
})
describe('errors', {
defaultCommandTimeout: 100,
}, () => {
beforeEach(function () {
this.logs = []
cy.on('log:added', (attrs, log) => {
this.lastLog = log
this.logs.push(log)
})
return null
})
it('logs once on failure', {
defaultCommandTimeout: 200,
}, function (done) {
cy.on('fail', (err) => {
expect(this.logs.length).to.eq(1)
done()
})
cy.reload(Infinity)
})
it('throws passing more than 2 args', {
defaultCommandTimeout: 1000,
}, (done) => {
cy.on('fail', (err) => {
expect(err.message).to.eq('`cy.reload()` can only accept a boolean or `options` as its arguments.')
expect(err.docsUrl).to.eq('https://on.cypress.io/reload')
done()
})
cy.reload(1, 2, 3)
})
it('throws passing 2 invalid arguments', { defaultCommandTimeout: 200, retries: 1 }, (done) => {
cy.on('fail', (err) => {
expect(err.message).to.eq('`cy.reload()` can only accept a boolean or `options` as its arguments.')
expect(err.docsUrl).to.eq('https://on.cypress.io/reload')
done()
})
cy.reload(true, 1)
})
it('throws passing 1 invalid argument', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.eq('`cy.reload()` can only accept a boolean or `options` as its arguments.')
expect(err.docsUrl).to.eq('https://on.cypress.io/reload')
done()
})
cy.reload(1)
})
it('fully refreshes page', () => {
cy
.window().then((win) => {
win.foo = 'foo'
})
.reload()
.window().then((win) => {
expect(win.foo).to.be.undefined
})
})
it('throws when reload times out', (done) => {
cy.timeout(1000)
cy.spy(Cypress.utils, 'locReload')
cy
.visit('/timeout?ms=100').then(() => {
let expected = false
// wait until the window finishes loading first
// else we can potentially move onto the next test
// while we're still unstable, which will result in
// properties on the window being inaccessible
// since we only visit once at the beginning of these tests
cy.on('window:load', () => {
expect(expected).to.be.true
done()
})
cy.on('fail', (err) => {
expected = true
expect(err.message).to.include('Your page did not fire its `load` event within `1ms`.')
})
})
.reload({ timeout: 1 })
})
})
describe('.log', () => {
beforeEach(function () {
this.logs = []
cy.on('log:added', (attrs, log) => {
if (attrs.name === 'reload') {
this.lastLog = log
}
this.logs.push(log)
})
return null
})
it('logs reload', () => {
cy.reload().then(function () {
expect(this.lastLog.get('name')).to.eq('reload')
})
})
it('can turn off logging', () => {
cy.reload({ log: false }).then(function () {
expect(this.lastLog).to.be.undefined
})
})
it('does not log \'Page Load\' events', () => {
cy.reload().then(function () {
this.logs.slice(0).forEach((log) => {
expect(log.get('name')).not.eq('page load')
})
})
})
it('logs before + after', () => {
let beforeunload = false
cy
.window().then(function () {
cy.on('window:before:unload', () => {
const { lastLog } = this
beforeunload = true
expect(lastLog.get('snapshots').length).to.eq(1)
expect(lastLog.get('snapshots')[0].name).to.eq('before')
expect(lastLog.get('snapshots')[0].body).to.be.an('object')
return undefined
})
}).reload().then(function () {
const { lastLog } = this
expect(beforeunload).to.be.true
expect(lastLog.get('snapshots').length).to.eq(2)
expect(lastLog.get('snapshots')[1].name).to.eq('after')
expect(lastLog.get('snapshots')[1].body).to.be.an('object')
})
})
})
})
context('#go', () => {
before(() => {
cy
.visit('/fixtures/generic.html')
.then(function (win) {
this.body = win.document.body.outerHTML
})
})
beforeEach(function () {
const doc = cy.state('document')
$(doc.body).empty().html(this.body)
})
// TODO: fix this
it.skip('(FLAKY) sets timeout to Cypress.config(pageLoadTimeout)', {
pageLoadTimeout: 4567,
}, () => {
const timeout = cy.spy(Promise.prototype, 'timeout')
cy
.visit('/fixtures/jquery.html')
.go('back').then(() => {
expect(timeout).to.be.calledWith(4567, 'go')
})
})
it('removes listeners', () => {
cy
.visit('/fixtures/generic.html')
.visit('/fixtures/jquery.html')
.then(() => {
const winLoadListeners = cy.listeners('window:load')
const beforeWinUnloadListeners = cy.listeners('window:before:unload')
const cyOn = cy.spy(cy, 'once')
const winLoad = cyOn.withArgs('window:load')
const beforeWinUnload = cyOn.withArgs('window:before:unload')
cy.go('back').then(() => {
expect(winLoad).to.be.calledOnce
expect(beforeWinUnload).to.be.calledOnce
expect(cy.listeners('window:load')).to.deep.eq(winLoadListeners)
expect(cy.listeners('window:before:unload')).to.deep.eq(beforeWinUnloadListeners)
})
})
})
it('fires stability:changed and window events events', () => {
const stub1 = cy.stub()
const stub2 = cy.stub()
const stub3 = cy.stub()
cy
.visit('/fixtures/generic.html')
.visit('/fixtures/jquery.html')
.then(() => {
cy.on('stability:changed', stub1)
cy.on('window:before:unload', stub2)
cy.on('window:unload', stub3)
})
.go('back').then(() => {
expect(stub1.firstCall).to.be.calledWith(false, 'beforeunload')
expect(stub1.secondCall).to.be.calledWith(true, 'load')
expect(stub2).to.be.calledOnce
expect(stub3).to.be.calledOnce
})
})
it('removes listeners from window', () => {
cy
.visit('/fixtures/generic.html')
.visit('/fixtures/jquery.html')
.then((win) => {
const rel = cy.stub(win, 'removeEventListener')
cy.go('back').then(() => {
expect(rel).to.be.calledWith('beforeunload')
expect(rel).to.be.calledWith('unload')
})
})
})
describe('errors', {
defaultCommandTimeout: 50,
}, () => {
beforeEach(function () {
this.logs = []
cy.on('log:added', (attrs, log) => {
if (attrs.name === 'go') {
this.lastLog = log
this.logs.push(log)
}
})
return null
})
_.each([null, undefined, NaN, Infinity, {}, [], () => {}], (val) => {
it(`throws on: '${val}'`, (done) => {
cy.on('fail', (err) => {
expect(err.message).to.eq('`cy.go()` accepts only a string or number argument')
expect(err.docsUrl).to.eq('https://on.cypress.io/go')
done()
})
cy.go(val)
})
})
it('throws on invalid string', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.eq('`cy.go()` accepts either `forward` or `back`. You passed: `foo`')
expect(err.docsUrl).to.eq('https://on.cypress.io/go')
done()
})
cy.go('foo')
})
it('throws on zero', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.eq('`cy.go()` cannot accept `0`. The number must be greater or less than `0`.')
expect(err.docsUrl).to.eq('https://on.cypress.io/go')
done()
})
cy.go(0)
})
it('throws when go times out', (done) => {
cy.timeout(1000)
cy
.visit('/timeout?ms=100')
.visit('/fixtures/jquery.html')
.then(() => {
let expected = false
// wait until the window finishes loading first
// else we can potentially move onto the next test
// while we're still unstable, which will result in
// properties on the window being inaccessible
// since we only visit once at the beginning of these tests
cy.on('window:load', () => {
expect(expected).to.be.true
done()
})
cy.on('fail', (err) => {
expected = true
expect(err.message).to.include('Your page did not fire its `load` event within `1ms`.')
})
cy.go('back', { timeout: 1 })
})
})
it('only logs once on error', function (done) {
cy.on('fail', (err) => {
expect(this.logs.length).to.eq(1)
expect(this.logs[0].get('error')).to.eq(err)
done()
})
cy
.visit('/fixtures/jquery.html')
.go('back', { timeout: 1 })
})
})
describe('.log', () => {
beforeEach(() => {
cy.visit('/fixtures/generic.html').then(function () {
this.logs = []
cy.on('log:added', (attrs, log) => {
if (attrs.name === 'go') {
this.lastLog = log
}
this.logs.push(log)
})
return null
})
})
it('logs go', () => {
cy
.visit('/fixtures/jquery.html')
.go('back').then(function () {
const { lastLog } = this
expect(lastLog.get('name')).to.eq('go')
expect(lastLog.get('message')).to.eq('back')
})
})
it('can turn off logging', () => {
cy
.visit('/fixtures/jquery.html')
.go('back', { log: false }).then(function () {
expect(this.lastLog).to.be.undefined
})
})
it('does not log \'Page Load\' events', () => {
cy
.visit('/fixtures/jquery.html')
.go('back').then(function () {
this.logs.slice(0).forEach((log) => {
expect(log.get('name')).not.eq('page load')
})
})
})
it('logs before + after', () => {
let beforeunload = false
cy
.visit('/fixtures/jquery.html')
.window().then(function (win) {
cy.on('window:before:unload', () => {
const { lastLog } = this
beforeunload = true
expect(lastLog.get('snapshots').length).to.eq(1)
expect(lastLog.get('snapshots')[0].name).to.eq('before')
expect(lastLog.get('snapshots')[0].body).to.be.an('object')
return undefined
})
cy.go('back').then(function () {
const { lastLog } = this
expect(beforeunload).to.be.true
expect(lastLog.get('snapshots').length).to.eq(2)
expect(lastLog.get('snapshots')[1].name).to.eq('after')
expect(lastLog.get('snapshots')[1].body).to.be.an('object')
})
})
})
})
})
context('#visit', () => {
// TODO: fix this
it.skip('(FLAKY) sets timeout to Cypress.config(pageLoadTimeout)', {
pageLoadTimeout: 4567,
}, () => {
const timeout = cy.spy(Promise.prototype, 'timeout')
cy.visit('/fixtures/jquery.html').then(() => {
expect(timeout).to.be.calledWith(4567)
})
})
it('removes window:load listeners', () => {
const listeners = cy.listeners('window:load')
const winLoad = cy.spy(cy, 'once').withArgs('window:load')
cy.visit('/fixtures/generic.html').then(() => {
// once for about:blank, once for $iframe src
expect(winLoad).to.be.calledTwice
expect(cy.listeners('window:load')).to.deep.eq(listeners)
})
})
it('can visit pages on the same originPolicy', () => {
cy
.visit('http://localhost:3500/fixtures/jquery.html')
.visit('http://localhost:3500/fixtures/generic.html')
.visit('http://localhost:3500/fixtures/dimensions.html')
})
it('resolves the subject to the remote iframe window', () => {
cy.visit('/fixtures/jquery.html').then((win) => {
expect(win).to.eq(cy.state('$autIframe').prop('contentWindow'))
})
})
it('changes the src of the iframe to the initial src', () => {
cy.visit('/fixtures/jquery.html').then(() => {
const src = cy.state('$autIframe').attr('src')
expect(src).to.eq('http://localhost:3500/fixtures/jquery.html')
})
})
it('invokes onLoad callback', function (done) {
const ctx = this
cy.visit('/fixtures/jquery.html', {
onLoad (contentWindow) {
const thisValue = this === ctx
expect(thisValue).be.true
expect(!!contentWindow.Cypress).to.be.true
done()
},
})
})
it('invokes onBeforeLoad callback with cy context', function (done) {
const ctx = this
cy.visit('/fixtures/jquery.html', {
onBeforeLoad (contentWindow) {
const thisValue = this === ctx
expect(thisValue).be.true
expect(!!contentWindow.Cypress).to.be.true
done()
},
})
})
it('does not error without an onBeforeLoad callback', () => {
cy.visit('/fixtures/jquery.html').then(() => {
const prev = cy.state('current').get('prev')
expect(prev.get('args')).to.have.length(1)
})
})
it('calls resolve:url with http:// when localhost', () => {
const backend = cy.spy(Cypress, 'backend')
cy
.visit('localhost:3500/timeout')
.then(() => {
expect(backend).to.be.calledWith('resolve:url', 'http://localhost:3500/timeout')
})
})
it('prepends hostname when visiting locally', () => {
const prop = cy.spy(cy.state('$autIframe'), 'prop')
cy
.visit('fixtures/jquery.html')
.then(() => {
expect(prop).to.be.calledWith('src', 'http://localhost:3500/fixtures/jquery.html')
})
})
it('can visit relative pages on the same originPolicy', () => {
// as long as we are already on the localhost:3500
// domain this will work
cy
.visit('http://localhost:3500/fixtures/dimensions.html')
.visit('/fixtures/jquery.html')
})
it('can visit relative pages with domain like query params', () => {
cy
.visit('http://localhost:3500/fixtures/generic.html')
.visit('http://localhost:3500/fixtures/dimensions.html?email=briancypress.io')
})
it('can visit pages with non-2xx status codes when option failOnStatusCode is false', () => {
cy
.visit('localhost:3500/status-404', { failOnStatusCode: false })
.visit('localhost:3500/status-500', { failOnStatusCode: false })
})
it('strips username + password out of the url when provided', () => {
const backend = cy.spy(Cypress, 'backend')
cy
.visit('http://cypress:password123@localhost:3500/timeout')
.then(() => {
expect(backend).to.be.calledWith('resolve:url', 'http://localhost:3500/timeout')
})
})
it('passes auth options', () => {
const backend = cy.spy(Cypress, 'backend')
const auth = {
username: 'cypress',
password: 'password123',
}
cy
.visit('http://localhost:3500/timeout', { auth })
.then(() => {
expect(backend).to.be.calledWithMatch('resolve:url', 'http://localhost:3500/timeout', { auth })
})
})
it('does not support file:// protocol', {
baseUrl: '',
}, (done) => {
cy.on('fail', (err) => {
expect(err.message).to.contain('`cy.visit()` failed because the \'file://...\' protocol is not supported by Cypress.')
done()
})
cy.visit('file:///cypress/fixtures/generic.html')
})
// https://github.com/cypress-io/cypress/issues/1727
it('can visit a page with undefined content type and html-shaped body', () => {
cy.visit('http://localhost:3500/undefined-content-type')
})
describe('when only hashes are changing', () => {
it('short circuits the visit if the page will not refresh', () => {
let count = 0
const urls = []
cy.on('window:load', () => {
urls.push(cy.state('window').location.href)
count += 1
})
cy
// about:blank yes (1)
.visit('/fixtures/generic.html?foo#bar') // yes (2)
.visit('/fixtures/generic.html?foo#foo') // no (2)
.visit('/fixtures/generic.html?bar#bar') // yes (3)
.visit('/fixtures/dimensions.html?bar#bar') // yes (4)
.visit('/fixtures/dimensions.html?baz#bar') // yes (5)
.visit('/fixtures/dimensions.html#bar') // yes (6)
.visit('/fixtures/dimensions.html') // yes (7)
.visit('/fixtures/dimensions.html#baz') // no (7)
.visit('/fixtures/dimensions.html#') // no (7)
.then(() => {
expect(count).to.eq(7)
expect(urls).to.deep.eq([
'about:blank',
'http://localhost:3500/fixtures/generic.html?foo#bar',
'http://localhost:3500/fixtures/generic.html?bar#bar',
'http://localhost:3500/fixtures/dimensions.html?bar#bar',
'http://localhost:3500/fixtures/dimensions.html?baz#bar',
'http://localhost:3500/fixtures/dimensions.html#bar',
'http://localhost:3500/fixtures/dimensions.html',
])
})
})
})
// https://github.com/cypress-io/cypress/issues/1311
it('window immediately resolves and doesn\'t reload when visiting the same URL with hashes', () => {
const onLoad = cy.stub()
cy
.visit('http://localhost:3500/fixtures/generic.html#foo').then((win) => {
win.foo = 'bar'
})
.visit('http://localhost:3500/fixtures/generic.html#foo', {
onLoad,
})
.then((win) => {
expect(win.bar).to.not.exist
expect(onLoad).not.to.have.been.called
})
})
it('can send headers', () => {
cy.visit({
url: 'http://localhost:3500/dump-headers',
headers: {
'x-foo-baz': 'bar-quux',
},
})
cy.contains('"x-foo-baz":"bar-quux"')
})
it('can send user-agent header', () => {
cy.visit({
url: 'http://localhost:3500/dump-headers',
headers: {
'user-agent': 'something special',
},
})
cy.contains('"user-agent":"something special"')
})
it('can send querystring params', () => {
const qs = { 'foo bar': 'baz quux' }
cy
.visit('http://localhost:3500/dump-qs', { qs })
.then(() => {
cy.contains(JSON.stringify(qs))
cy.url().should('eq', 'http://localhost:3500/dump-qs?foo%20bar=baz%20quux')
})
})
describe('can send a POST request', () => {
it('automatically urlencoded using an object body', () => {
cy.visit('http://localhost:3500/post-only', {
method: 'POST',
body: {
bar: 'baz',
},
})
cy.contains('it worked!').contains('{"bar":"baz"}')
})
it('with any string body and headers', () => {
cy.visit('http://localhost:3500/post-only', {
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify({
bar: 'baz',
}),
})
cy.contains('it worked!').contains('{"bar":"baz"}')
})
})
describe('when origins don\'t match', () => {
beforeEach(() => {
Cypress.emit('test:before:run', { id: 888 })
cy.stub(Cypress.runner, 'getEmissions').returns([])
cy.stub(Cypress.runner, 'getTestsState').returns([])
cy.stub(Cypress.runner, 'getStartTime').returns('12345')
cy.stub(Cypress.Log, 'countLogsByTests').withArgs([]).returns(1)
cy.stub(Cypress.runner, 'countByTestState')
.withArgs([], 'passed').returns(2)
.withArgs([], 'failed').returns(3)
.withArgs([], 'pending').returns(4)
})
it('emits preserve:run:state with title + fn', (done) => {
const obj = {
currentId: 888,
tests: [],
emissions: [],
startTime: '12345',
numLogs: 1,
passed: 2,
failed: 3,
pending: 4,
}
const fn = function (eventName, state) {
_.each(obj, (value, key) => {
expect(state[key]).to.deep.eq(value)
})
done()
}
cy.stub(Cypress, 'backend')
.withArgs('resolve:url')
.resolves({
isOkStatusCode: true,
isHtml: true,
url: 'http://localhost:4200',
})
.withArgs('preserve:run:state')
.callsFake(fn)
cy.visit('http://localhost:4200')
})
it('replaces window.location when origins don\'t match', (done) => {
const fn = function (str, win) {
const isEqual = win === top.window
expect(isEqual).to.be.true
expect(str).to.eq('http://localhost:4200/foo?bar=baz#/tests/integration/foo_spec.js')
done()
}
const fakeUrl = Cypress.Location.create('http://localhost:3500/foo?bar=baz#/tests/integration/foo_spec.js')
cy.stub(Cypress.utils, 'locExisting').returns(fakeUrl)
cy.stub(Cypress.utils, 'locHref')
.callThrough()
.withArgs('http://localhost:4200/foo?bar=baz#/tests/integration/foo_spec.js')
.callsFake(fn)
cy.stub(Cypress, 'backend')
.withArgs('resolve:url')
.resolves({
isOkStatusCode: true,
isHtml: true,
url: 'http://localhost:4200',
})
.withArgs('preserve:run:state')
.resolves()
cy.visit('http://localhost:4200')
})
})
describe('location getter overrides', () => {
before(() => {
cy
.visit('/fixtures/jquery.html?foo=bar#dashboard?baz=quux')
.window().as('win').then((win) => {
// ensure href always returns the full path
// so our tests guarantee that in fact we are
// overriding the location getters
expect(win.location.href).to.include('/fixtures/jquery.html?foo=bar#dashboard?baz=quux')
})
})
beforeEach(function () {
this.win = cy.state('window')
this.eq = (attr, str) => {
expect(this.win.location[attr]).to.eq(str)
}
})
it('hash', function () {
this.eq('hash', '#dashboard?baz=quux')
})
it('hostname', function () {
this.eq('hostname', 'localhost')
})
it('origin', function () {
this.eq('origin', 'http://localhost:3500')
})
it('pathname', function () {
this.eq('pathname', '/fixtures/jquery.html')
})
it('port', function () {
this.eq('port', '3500')
})
it('protocol', function () {
this.eq('protocol', 'http:')
})
it('search', function () {
this.eq('search', '?foo=bar')
})
})
describe('.log', () => {
beforeEach(function () {
cy.stub(Cypress.runner, 'getEmissions').returns([])
this.logs = []
cy.on('log:added', (attrs, log) => {
if (attrs.name === 'visit') {
this.lastLog = log
}
this.logs.push(log)
})
return null
})
it('preserves url on subsequent visits', () => {
cy.visit('/fixtures/jquery.html').get('button').then(function () {
expect(this.lastLog.get('url')).to.eq('http://localhost:3500/fixtures/jquery.html')
})
})
it('does not log \'Page Load\' events', () => {
cy
.visit('/fixtures/generic.html')
.visit('/fixtures/jquery.html')
.then(function () {
this.logs.slice(0).forEach((log) => {
expect(log.get('name')).not.eq('page load')
})
})
})
it('logs immediately before resolving', () => {
let expected = false
cy.on('log:added', (attrs, log) => {
cy.removeAllListeners('log:added')
expect(log.pick('name', 'message')).to.deep.eq({
name: 'visit',
message: 'localhost:3500/fixtures/jquery.html#/hash',
})
expected = true
})
cy.visit('localhost:3500/fixtures/jquery.html#/hash').then(() => {
expect(expected).to.be.true
})
})
it('logs obj once complete', () => {
cy.visit('http://localhost:3500/fixtures/generic.html').then(function () {
const obj = {
state: 'passed',
name: 'visit',
message: 'http://localhost:3500/fixtures/generic.html',
url: 'http://localhost:3500/fixtures/generic.html',
}
const { lastLog } = this
_.each(obj, (value, key) => {
expect(lastLog.get(key)).deep.eq(value, `expected key: ${key} to eq value: ${value}`)
})
})
})
it('logs obj once complete when onLoad is not called', () => {
cy.visit('http://localhost:3500/fixtures/generic.html#foo')
cy.visit('http://localhost:3500/fixtures/generic.html#foo').then(function () {
const obj = {
state: 'passed',
name: 'visit',
message: 'http://localhost:3500/fixtures/generic.html#foo',
url: 'http://localhost:3500/fixtures/generic.html#foo',
}
const { lastLog } = this
_.each(obj, (value, key) => {
expect(lastLog.get(key)).deep.eq(value, `expected key: ${key} to eq value: ${value}`)
})
})
})
it('snapshots once', () => {
cy.visit('/fixtures/generic.html').then(function () {
const { lastLog } = this
expect(lastLog.get('snapshots').length).to.eq(1)
expect(lastLog.get('snapshots')[0]).to.be.an('object')
})
})
it('can turn off logging', () => {
cy.visit('/timeout?ms=0', { log: false }).then(function () {
expect(this.lastLog).not.to.exist
})
})
it('displays file attributes as consoleProps', () => {
cy.stub(Cypress, 'backend')
.withArgs('resolve:url')
.resolves({
isOkStatusCode: true,
isHtml: true,
contentType: 'text/html',
url: 'http://localhost:3500/foo/bar',
filePath: '/path/to/foo/bar',
redirects: [1, 2],
cookies: [{}, {}],
})
cy.visit('/fixtures/jquery.html').then(function () {
expect(this.lastLog.invoke('consoleProps')).to.deep.eq({
'Command': 'visit',
'File Served': '/path/to/foo/bar',
'Resolved Url': 'http://localhost:3500/foo/bar',
'Redirects': [1, 2],
'Cookies Set': [{}, {}],
})
})
})
it('displays http attributes as consoleProps', () => {
cy.stub(Cypress, 'backend')
.withArgs('resolve:url')
.resolves({
isOkStatusCode: true,
isHtml: true,
contentType: 'text/html',
url: 'http://localhost:3500/foo',
originalUrl: 'http://localhost:3500/foo',
redirects: [1, 2],
cookies: [{}, {}],
})
cy.visit('http://localhost:3500/foo').then(function () {
expect(this.lastLog.invoke('consoleProps')).to.deep.eq({
'Command': 'visit',
'Resolved Url': 'http://localhost:3500/foo',
'Redirects': [1, 2],
'Cookies Set': [{}, {}],
})
})
})
it('displays originalUrl http attributes as consoleProps', () => {
cy.stub(Cypress, 'backend')
.withArgs('resolve:url')
.resolves({
isOkStatusCode: true,
isHtml: true,
contentType: 'text/html',
url: 'http://localhost:3500/foo/bar',
originalUrl: 'http://localhost:3500/foo',
redirects: [1, 2],
cookies: [{}, {}],
})
cy.visit('http://localhost:3500/foo').then(function () {
expect(this.lastLog.invoke('consoleProps')).to.deep.eq({
'Command': 'visit',
'Original Url': 'http://localhost:3500/foo',
'Resolved Url': 'http://localhost:3500/foo/bar',
'Redirects': [1, 2],
'Cookies Set': [{}, {}],
})
})
})
it('indicates redirects in the message', () => {
cy.stub(Cypress, 'backend')
.withArgs('resolve:url')
.resolves({
isOkStatusCode: true,
isHtml: true,
contentType: 'text/html',
url: 'http://localhost:3500/foo/bar',
originalUrl: 'http://localhost:3500/foo',
redirects: [1, 2],
cookies: [{}, {}],
})
cy.visit('http://localhost:3500/foo').then(function () {
const { lastLog } = this
expect(lastLog.get('message')).to.eq(
'http://localhost:3500/foo -> 1 -> 2',
)
})
})
it('indicates POST in the message', () => {
cy.visit('http://localhost:3500/post-only', {
method: 'POST',
}).then(function () {
const { lastLog } = this
expect(lastLog.get('message')).to.eq(
'POST http://localhost:3500/post-only',
)
})
})
it('displays note in consoleProps when visiting the same page with a hash', () => {
cy.visit('http://localhost:3500/fixtures/generic.html#foo')
.visit('http://localhost:3500/fixtures/generic.html#foo')
.then(function () {
expect(this.lastLog.invoke('consoleProps')).to.deep.eq({
'Command': 'visit',
'Note': 'Because this visit was to the same hash, the page did not reload and the onBeforeLoad and onLoad callbacks did not fire.',
})
})
})
it('logs options if they are supplied', () => {
cy.visit({
url: 'http://localhost:3500/fixtures/generic.html',
headers: {
'foo': 'bar',
},
notReal: 'baz',
})
.then(function () {
expect(this.lastLog.invoke('consoleProps')['Options']).to.deep.eq({
url: 'http://localhost:3500/fixtures/generic.html',
headers: {
'foo': 'bar',
},
})
})
})
it('does not log options if they are not supplied', () => {
cy.visit('http://localhost:3500/fixtures/generic.html')
.then(function () {
expect(this.lastLog.invoke('consoleProps')['Options']).to.be.undefined
})
})
})
describe('errors', {
defaultCommandTimeout: 50,
}, () => {
beforeEach(function () {
this.logs = []
cy.on('log:added', (attrs, log) => {
if (attrs.name === 'visit') {
this.lastLog = log
this.logs.push(log)
}
})
return null
})
it('sets error command state', function (done) {
cy.stub(Cypress, 'backend')
.withArgs('resolve:url')
.rejects(new Error)
cy.on('fail', (err) => {
const { lastLog } = this
expect(lastLog.get('state')).to.eq('failed')
expect(lastLog.get('error')).to.eq(err)
done()
})
cy.visit('/fixtures/generic.html')
})
it('logs once on error', function (done) {
cy.stub(Cypress, 'backend')
.withArgs('resolve:url')
.rejects(new Error)
cy.on('fail', (err) => {
expect(this.logs.length).to.eq(1)
done()
})
cy.visit('/fixtures/generic.html')
})
it('logs once on timeout error', function (done) {
cy.on('fail', (err) => {
const { lastLog } = this
expect(this.logs.length).to.eq(1)
expect(err.message).to.include('Your page did not fire its `load` event within `20ms`.')
expect(lastLog.get('error')).to.eq(err)
done()
})
cy.visit('/timeout?ms=5000', { timeout: 20 })
})
it('cancels resolve url promise on timeout', (done) => {
cy.on('collect:run:state', () => {
done(new Error('should not have tried to swap domains'))
})
const fn = () => {
// resolve after 100ms
return Promise.delay(100)
.then(() => {
done(new Error('should not have invoked this callback'))
})
}
cy.stub(Cypress, 'backend')
.withArgs('resolve:url')
.callsFake(fn)
cy.on('fail', () => {
done()
})
cy.visit('/', { timeout: 20 })
})
it('throws when url isnt a string', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.eq('`cy.visit()` must be called with a `url` or an `options` object containing a `url` as its 1st argument')
expect(err.docsUrl).to.eq('https://on.cypress.io/visit')
done()
})
cy.visit()
})
it('throws when url is specified twice', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.contain('`cy.visit()` must be called with only one `url`. You specified two urls')
expect(err.docsUrl).to.eq('https://on.cypress.io/visit')
done()
})
cy.visit('http://foobarbaz', {
url: 'http://foobarbaz',
})
})
it('throws when method is unsupported', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.contain('`cy.visit()` was called with an invalid method: `FOO`')
expect(err.docsUrl).to.eq('https://on.cypress.io/visit')
done()
})
cy.visit({
url: 'http://foobarbaz',
method: 'FOO',
})
})
it('throws when headers is not an object', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.contain('`cy.visit()` requires the `headers` option to be an object')
expect(err.docsUrl).to.eq('https://on.cypress.io/visit')
done()
})
cy.visit({
url: 'http://foobarbaz',
headers: 'quux',
})
});
[
'foo',
null,
false,
].forEach((qs) => {
const str = String(qs)
it(`throws when qs is ${str}`, (done) => {
cy.on('fail', (err) => {
expect(err.message).to.contain(`\`cy.visit()\` requires the \`qs\` option to be an object, but received: \`${str}\``)
done()
})
cy.visit({
url: 'http://foobarbaz',
qs,
})
})
})
it('throws when failOnStatusCode is false and retryOnStatusCodeFailure is true', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.contain('These options are incompatible with each other.')
done()
})
cy.visit({
url: 'http://foobarbaz',
failOnStatusCode: false,
retryOnStatusCodeFailure: true,
})
})
it('throws when attempting to visit a 2nd domain on different port', function (done) {
cy.on('fail', (err) => {
const { lastLog } = this
expect(err.message).to.include('`cy.visit()` failed because you are attempting to visit a URL that is of a different origin.')
expect(err.message).to.include('The new URL is considered a different origin because the following parts of the URL are different:')
expect(err.message).to.include('> port')
expect(err.docsUrl).to.eq('https://on.cypress.io/cannot-visit-different-origin-domain')
expect(this.logs.length).to.eq(2)
expect(lastLog.get('error')).to.eq(err)
done()
})
cy.visit('http://localhost:3500/fixtures/generic.html')
cy.visit('http://localhost:3501/fixtures/generic.html')
})
it('throws when attempting to visit a 2nd domain on different protocol', function (done) {
cy.on('fail', (err) => {
const { lastLog } = this
expect(err.message).to.include('`cy.visit()` failed because you are attempting to visit a URL that is of a different origin.')
expect(err.message).to.include('The new URL is considered a different origin because the following parts of the URL are different:')
expect(err.message).to.include('> protocol')
expect(err.docsUrl).to.eq('https://on.cypress.io/cannot-visit-different-origin-domain')
expect(this.logs.length).to.eq(2)
expect(lastLog.get('error')).to.eq(err)
done()
})
cy.visit('http://localhost:3500/fixtures/generic.html')
cy.visit('https://localhost:3500/fixtures/generic.html')
})
it('throws when attempting to visit a 2nd domain on different superdomain', function (done) {
cy.on('fail', (err) => {
const { lastLog } = this
expect(err.message).to.include('`cy.visit()` failed because you are attempting to visit a URL that is of a different origin.')
expect(err.message).to.include('The new URL is considered a different origin because the following parts of the URL are different:')
expect(err.message).to.include('> superdomain')
expect(err.docsUrl).to.eq('https://on.cypress.io/cannot-visit-different-origin-domain')
expect(this.logs.length).to.eq(2)
expect(lastLog.get('error')).to.eq(err)
done()
})
cy.visit('http://localhost:3500/fixtures/generic.html')
cy.visit('http://google.com:3500/fixtures/generic.html')
})
it('throws attemping to visit 2 unique ip addresses', function (done) {
const $autIframe = cy.state('$autIframe')
const load = () => {
return $autIframe.trigger('load')
}
cy.stub(Cypress, 'backend')
.withArgs('resolve:url')
.resolves({
isOkStatusCode: true,
isHtml: true,
url: 'http://127.0.0.1:3500',
})
// whenever we're told to change the src
// just fire the load event directly on the $autIframe
cy.stub(Cypress.utils, 'iframeSrc').callsFake(load)
// make it seem like we're already on http://127.0.0.1:3500
const one = Cypress.Location.create('http://127.0.0.1:3500/fixtures/generic.html')
cy.stub(Cypress.utils, 'locExisting')
.returns(one)
cy.on('fail', (err) => {
const { lastLog } = this
expect(err.message).to.include('`cy.visit()` failed because you are attempting to visit a URL that is of a different origin.')
expect(err.docsUrl).to.eq('https://on.cypress.io/cannot-visit-different-origin-domain')
expect(this.logs.length).to.eq(2)
expect(lastLog.get('error')).to.eq(err)
done()
})
cy
.visit('http://127.0.0.1:3500/fixtures/generic.html')
.visit('http://126.0.0.1:3500/fixtures/generic.html')
})
it('does not call resolve:url when throws attemping to visit a 2nd domain', (done) => {
const backend = cy.spy(Cypress, 'backend')
cy.on('fail', (err) => {
expect(backend).to.be.calledWithMatch('resolve:url', 'http://localhost:3500/fixtures/generic.html')
expect(backend).not.to.be.calledWithMatch('resolve:url', 'http://google.com:3500/fixtures/generic.html')
done()
})
cy
.visit('http://localhost:3500/fixtures/generic.html')
.visit('http://google.com:3500/fixtures/generic.html')
})
it('displays loading_network_failed when _resolveUrl throws', function (done) {
const err1 = new Error('connect ECONNREFUSED 127.0.0.1:64646')
// dont log else we create an endless loop!
const emit = cy.spy(Cypress, 'emit').log(false)
cy.stub(Cypress, 'backend')
.withArgs('resolve:url')
.rejects(err1)
cy.on('fail', (err) => {
const { lastLog } = this
expect(err.message).to.include(stripIndent`\
\`cy.visit()\` failed trying to load:
http://localhost:3500/foo.html
We attempted to make an http request to this URL but the request failed without a response.
We received this error at the network level:
> Error: connect ECONNREFUSED 127.0.0.1:64646
Common situations why this would fail:
- you don't have internet access
- you forgot to run / boot your web server
- your web server isn't accessible
- you have weird network configuration settings on your computer`)
expect(err1.url).to.include('/foo.html')
expect(emit).to.be.calledWith('visit:failed', err1)
expect(this.logs.length).to.eq(1)
expect(lastLog.get('error')).to.eq(err)
done()
})
cy.visit('/foo.html')
})
it('displays loading_file_failed when _resolveUrl resp is not ok', function (done) {
const obj = {
isOkStatusCode: false,
isHtml: true,
contentType: 'text/html',
originalUrl: '/foo.html',
filePath: '/path/to/foo.html',
status: 404,
statusText: 'Not Found',
redirects: [],
}
obj.url = obj.originalUrl
cy.stub(Cypress, 'backend')
.withArgs('resolve:url')
.resolves(obj)
// dont log else we create an endless loop!
const emit = cy.spy(Cypress, 'emit').log(false)
cy.on('fail', (err) => {
const { lastLog } = this
expect(err.message).to.include(stripIndent`\
\`cy.visit()\` failed trying to load:
/foo.html
We failed looking for this file at the path:
/path/to/foo.html
The internal Cypress web server responded with:
> 404: Not Found`)
expect(emit).to.be.calledWithMatch('visit:failed', obj)
expect(this.logs.length).to.eq(1)
expect(lastLog.get('error')).to.eq(err)
done()
})
cy.visit('/foo.html')
})
it('displays loading_file_failed redirects when _resolveUrl resp is not ok', function (done) {
const obj = {
isOkStatusCode: false,
isHtml: true,
contentType: 'text/html',
originalUrl: '/bar',
filePath: '/path/to/bar/',
status: 404,
statusText: 'Not Found',
redirects: [
'301: http://localhost:3500/bar/',
],
}
obj.url = obj.originalUrl
cy.stub(Cypress, 'backend')
.withArgs('resolve:url')
.resolves(obj)
// dont log else we create an endless loop!
const emit = cy.spy(Cypress, 'emit').log(false)
cy.on('fail', (err) => {
const { lastLog } = this
expect(err.message).to.include(stripIndent`\
\`cy.visit()\` failed trying to load:
/bar
We failed looking for this file at the path:
/path/to/bar/
The internal Cypress web server responded with:
> 404: Not Found
We were redirected '1' time to:
- 301: http://localhost:3500/bar/`)
expect(emit).to.be.calledWithMatch('visit:failed', obj)
expect(this.logs.length).to.eq(1)
expect(lastLog.get('error')).to.eq(err)
done()
})
cy.visit('/bar')
})
it('displays loading_http_failed when _resolveUrl resp is not ok', function (done) {
const obj = {
isOkStatusCode: false,
isHtml: true,
contentType: 'text/html',
originalUrl: 'https://google.com/foo',
status: 500,
statusText: 'Server Error',
redirects: [],
}
obj.url = obj.originalUrl
cy.stub(Cypress, 'backend')
.withArgs('resolve:url', 'https://google.com/foo')
.resolves(obj)
// dont log else we create an endless loop!
const emit = cy.spy(Cypress, 'emit').log(false)
cy.on('fail', (err) => {
const { lastLog } = this
expect(err.message).to.include(stripIndent`\
\`cy.visit()\` failed trying to load:
https://google.com/foo
The response we received from your web server was:
> 500: Server Error
This was considered a failure because the status code was not \`2xx\`.
If you do not want status codes to cause failures pass the option: \`failOnStatusCode: false\``)
expect(emit).to.be.calledWithMatch('visit:failed', obj)
expect(this.logs.length).to.eq(1)
expect(lastLog.get('error')).to.eq(err)
done()
})
cy.visit('https://google.com/foo')
})
it('displays loading_http_failed redirects when _resolveUrl resp is not ok', function (done) {
const obj = {
isOkStatusCode: false,
isHtml: true,
contentType: 'text/html',
originalUrl: 'https://google.com/foo',
status: 401,
statusText: 'Unauthorized',
redirects: [
'302: https://google.com/bar/',
'301: https://gmail.com/',
],
}
obj.url = obj.originalUrl
cy.stub(Cypress, 'backend')
.withArgs('resolve:url', 'https://google.com/foo')
.resolves(obj)
// dont log else we create an endless loop!
const emit = cy.spy(Cypress, 'emit').log(false)
cy.on('fail', (err) => {
const { lastLog } = this
expect(err.message).to.include(stripIndent`\
\`cy.visit()\` failed trying to load:
https://google.com/foo
The response we received from your web server was:
> 401: Unauthorized
This was considered a failure because the status code was not \`2xx\`.
This http request was redirected '2' times to: