@revoloo/cypress6
Version:
Cypress.io end to end testing tool
704 lines (572 loc) • 21.6 kB
JavaScript
require('../spec_helper')
const _ = require('lodash')
const http = require('http')
const socket = require('@packages/socket')
const Promise = require('bluebird')
const mockRequire = require('mock-require')
const browser = {
cookies: {
set () {},
getAll () {},
remove () {},
onChanged: {
addListener () {},
},
},
downloads: {
onCreated: {
addListener () {},
},
onChanged: {
addListener () {},
},
},
windows: {
getLastFocused () {},
},
runtime: {
},
tabs: {
query () {},
executeScript () {},
captureVisibleTab () {},
},
}
mockRequire('webextension-polyfill', browser)
const background = require('../../app/background')
const PORT = 12345
const tab1 = {
'active': false,
'audible': false,
'favIconUrl': 'http://localhost:2020/__cypress/static/img/favicon.ico',
'height': 553,
'highlighted': false,
'id': 1,
'incognito': false,
'index': 0,
'mutedInfo': {
'muted': false,
},
'pinned': false,
'selected': false,
'status': 'complete',
'title': 'foobar',
'url': 'http://localhost:2020/__/#tests',
'width': 1920,
'windowId': 1,
}
const tab2 = {
'active': true,
'audible': false,
'favIconUrl': 'http://localhost:2020/__cypress/static/img/favicon.ico',
'height': 553,
'highlighted': true,
'id': 2,
'incognito': false,
'index': 1,
'mutedInfo': {
'muted': false,
},
'pinned': false,
'selected': true,
'status': 'complete',
'title': 'foobar',
'url': 'https://localhost:2020/__/#tests',
'width': 1920,
'windowId': 1,
}
const tab3 = {
'active': true,
'audible': false,
'favIconUrl': 'http://localhost:2020/__cypress/static/img/favicon.ico',
'height': 553,
'highlighted': true,
'id': 2,
'incognito': false,
'index': 1,
'mutedInfo': {
'muted': false,
},
'pinned': false,
'selected': true,
'status': 'complete',
'title': 'foobar',
'url': 'about:blank',
'width': 1920,
'windowId': 1,
}
describe('app/background', () => {
beforeEach(function (done) {
this.httpSrv = http.createServer()
this.server = socket.server(this.httpSrv, { path: '/__socket.io' })
this.onConnect = (callback) => {
const client = background.connect(`http://localhost:${PORT}`, '/__socket.io')
client.on('connect', _.once(() => {
callback(client)
}))
}
this.stubEmit = (callback) => {
this.onConnect((client) => {
client.emit = _.once(callback)
})
}
this.httpSrv.listen(PORT, done)
})
afterEach(function (done) {
this.server.close()
this.httpSrv.close(() => {
done()
})
})
context('.connect', () => {
it('can connect', function (done) {
this.server.on('connection', () => {
return done()
})
return background.connect(`http://localhost:${PORT}`, '/__socket.io')
})
it('emits \'automation:client:connected\'', (done) => {
const client = background.connect(`http://localhost:${PORT}`, '/__socket.io')
sinon.spy(client, 'emit')
return client.on('connect', _.once(() => {
expect(client.emit).to.be.calledWith('automation:client:connected')
return done()
}))
})
it('listens to cookie changes', (done) => {
const addListener = sinon.stub(browser.cookies.onChanged, 'addListener')
const client = background.connect(`http://localhost:${PORT}`, '/__socket.io')
return client.on('connect', _.once(() => {
expect(addListener).to.be.calledOnce
return done()
}))
})
})
context('cookies', () => {
it('onChanged does not emit when cause is overwrite', function (done) {
const addListener = sinon.stub(browser.cookies.onChanged, 'addListener')
this.onConnect((client) => {
sinon.spy(client, 'emit')
const fn = addListener.getCall(0).args[0]
fn({ cause: 'overwrite' })
expect(client.emit).not.to.be.calledWith('automation:push:request')
done()
})
})
it('onChanged emits automation:push:request change:cookie', function (done) {
const info = { cause: 'explicit', cookie: { name: 'foo', value: 'bar' } }
sinon.stub(browser.cookies.onChanged, 'addListener').yieldsAsync(info)
this.stubEmit((req, msg, data) => {
expect(req).to.eq('automation:push:request')
expect(msg).to.eq('change:cookie')
expect(data).to.deep.eq(info)
done()
})
})
})
context('downloads', () => {
it('onCreated emits automation:push:request create:download', function (done) {
const downloadItem = {
id: '1',
filename: '/path/to/download.csv',
mime: 'text/csv',
url: 'http://localhost:1234/download.csv',
}
sinon.stub(browser.downloads.onCreated, 'addListener').yieldsAsync(downloadItem)
this.stubEmit((req, msg, data) => {
expect(req).to.eq('automation:push:request')
expect(msg).to.eq('create:download')
expect(data).to.deep.eq({
id: `${downloadItem.id}`,
filePath: downloadItem.filename,
mime: downloadItem.mime,
url: downloadItem.url,
})
done()
})
})
it('onChanged emits automation:push:request complete:download', function (done) {
const downloadDelta = {
id: '1',
state: {
current: 'complete',
},
}
sinon.stub(browser.downloads.onChanged, 'addListener').yieldsAsync(downloadDelta)
this.stubEmit((req, msg, data) => {
expect(req).to.eq('automation:push:request')
expect(msg).to.eq('complete:download')
expect(data).to.deep.eq({ id: `${downloadDelta.id}` })
done()
})
})
it('onChanged does not emit if state does not exist', function (done) {
const downloadDelta = {
id: '1',
}
const addListener = sinon.stub(browser.downloads.onChanged, 'addListener')
this.onConnect((client) => {
sinon.spy(client, 'emit')
addListener.getCall(0).args[0](downloadDelta)
expect(client.emit).not.to.be.calledWith('automation:push:request')
done()
})
})
it('onChanged does not emit if state.current is not "complete"', function (done) {
const downloadDelta = {
id: '1',
state: {
current: 'inprogress',
},
}
const addListener = sinon.stub(browser.downloads.onChanged, 'addListener')
this.onConnect((client) => {
sinon.spy(client, 'emit')
addListener.getCall(0).args[0](downloadDelta)
expect(client.emit).not.to.be.calledWith('automation:push:request')
done()
})
})
})
context('.getAll', () => {
it('resolves with specific cookie properties', () => {
sinon.stub(browser.cookies, 'getAll')
.withArgs({ domain: 'localhost' })
.resolves([
{ name: 'foo', value: 'f', path: '/', domain: 'localhost', secure: true, httpOnly: true, expirationDate: 123 },
{ name: 'bar', value: 'b', path: '/', domain: 'localhost', secure: false, httpOnly: false, expirationDate: 456 },
])
return background.getAll({ domain: 'localhost' })
.then((cookies) => {
expect(cookies).to.deep.eq([
{ name: 'foo', value: 'f', path: '/', domain: 'localhost', secure: true, httpOnly: true, expirationDate: 123 },
{ name: 'bar', value: 'b', path: '/', domain: 'localhost', secure: false, httpOnly: false, expirationDate: 456 },
])
})
})
})
context('.query', () => {
beforeEach(function () {
this.code = 'var s; (s = document.getElementById(\'__cypress-string\')) && s.textContent'
})
it('resolves on the 1st tab', function () {
sinon.stub(browser.tabs, 'query')
.withArgs({ windowType: 'normal' })
.resolves([tab1])
sinon.stub(browser.tabs, 'executeScript')
.withArgs(tab1.id, { code: this.code })
.resolves(['1234'])
return background.query({
string: '1234',
element: '__cypress-string',
})
})
it('resolves on the 2nd tab', function () {
sinon.stub(browser.tabs, 'query')
.withArgs({ windowType: 'normal' })
.resolves([tab1, tab2])
sinon.stub(browser.tabs, 'executeScript')
.withArgs(tab1.id, { code: this.code })
.resolves(['foobarbaz'])
.withArgs(tab2.id, { code: this.code })
.resolves(['1234'])
return background.query({
string: '1234',
element: '__cypress-string',
})
})
it('filters out tabs that don\'t start with http', () => {
sinon.stub(browser.tabs, 'query')
.resolves([tab3])
return background.query({
string: '1234',
element: '__cypress-string',
})
.then(() => {
throw new Error('should have failed')
}).catch((err) => {
// we good if this hits
expect(err).to.be.instanceof(Promise.RangeError)
})
})
it('rejects if no tab matches', function () {
sinon.stub(browser.tabs, 'query')
.withArgs({ windowType: 'normal' })
.resolves([tab1, tab2])
sinon.stub(browser.tabs, 'executeScript')
.withArgs(tab1.id, { code: this.code })
.resolves(['foobarbaz'])
.withArgs(tab2.id, { code: this.code })
.resolves(['foobarbaz2'])
return background.query({
string: '1234',
element: '__cypress-string',
})
.then(() => {
throw new Error('should have failed')
}).catch((err) => {
// we good if this hits
expect(err.length).to.eq(2)
expect(err).to.be.instanceof(Promise.AggregateError)
})
})
it('rejects if no tabs were found', () => {
sinon.stub(browser.tabs, 'query')
.resolves([])
return background.query({
string: '1234',
element: '__cypress-string',
})
.then(() => {
throw new Error('should have failed')
}).catch((err) => {
// we good if this hits
expect(err).to.be.instanceof(Promise.RangeError)
})
})
})
context('integration', () => {
beforeEach(function (done) {
done = _.once(done)
this.server.on('connection', (socket1) => {
this.socket = socket1
return done()
})
this.client = background.connect(`http://localhost:${PORT}`, '/__socket.io')
})
describe('get:cookies', () => {
beforeEach(() => {
return sinon.stub(browser.cookies, 'getAll')
.withArgs({ domain: 'google.com' })
.resolves([{}, {}])
})
it('returns all cookies', function (done) {
this.socket.on('automation:response', (id, obj = {}) => {
expect(id).to.eq(123)
expect(obj.response).to.deep.eq([{}, {}])
return done()
})
return this.server.emit('automation:request', 123, 'get:cookies', { domain: 'google.com' })
})
})
describe('get:cookie', () => {
beforeEach(() => {
return sinon.stub(browser.cookies, 'getAll')
.withArgs({ domain: 'google.com', name: 'session' })
.resolves([
{ name: 'session', value: 'key', path: '/login', domain: 'google', secure: true, httpOnly: true, expirationDate: 123 },
])
.withArgs({ domain: 'google.com', name: 'doesNotExist' })
.resolves([])
})
it('returns a specific cookie by name', function (done) {
this.socket.on('automation:response', (id, obj = {}) => {
expect(id).to.eq(123)
expect(obj.response).to.deep.eq({ name: 'session', value: 'key', path: '/login', domain: 'google', secure: true, httpOnly: true, expirationDate: 123 })
return done()
})
return this.server.emit('automation:request', 123, 'get:cookie', { domain: 'google.com', name: 'session' })
})
it('returns null when no cookie by name is found', function (done) {
this.socket.on('automation:response', (id, obj = {}) => {
expect(id).to.eq(123)
expect(obj.response).to.be.null
return done()
})
return this.server.emit('automation:request', 123, 'get:cookie', { domain: 'google.com', name: 'doesNotExist' })
})
})
describe('set:cookie', () => {
beforeEach(() => {
browser.runtime.lastError = { message: 'some error' }
return sinon.stub(browser.cookies, 'set')
.withArgs({ domain: 'google.com', name: 'session', value: 'key', path: '/', secure: false, url: 'http://google.com/' })
.resolves(
{ name: 'session', value: 'key', path: '/', domain: 'google', secure: false, httpOnly: false },
)
.withArgs({ url: 'https://www.google.com', name: 'session', value: 'key' })
.resolves(
{ name: 'session', value: 'key', path: '/', domain: 'google.com', secure: true, httpOnly: false },
)
// 'domain' cannot not set when it's localhost
.withArgs({ name: 'foo', value: 'bar', secure: true, path: '/foo', url: 'https://localhost/foo' })
.rejects({ message: 'some error' })
})
it('resolves with the cookie details', function (done) {
this.socket.on('automation:response', (id, obj = {}) => {
expect(id).to.eq(123)
expect(obj.response).to.deep.eq({ name: 'session', value: 'key', path: '/', domain: 'google', secure: false, httpOnly: false })
return done()
})
return this.server.emit('automation:request', 123, 'set:cookie', { domain: 'google.com', name: 'session', secure: false, value: 'key', path: '/' })
})
it('does not set url when already present', function (done) {
this.socket.on('automation:response', (id, obj = {}) => {
expect(id).to.eq(123)
expect(obj.response).to.deep.eq({ name: 'session', value: 'key', path: '/', domain: 'google.com', secure: true, httpOnly: false })
return done()
})
return this.server.emit('automation:request', 123, 'set:cookie', { url: 'https://www.google.com', name: 'session', value: 'key' })
})
it('rejects with error', function (done) {
this.socket.on('automation:response', (id, obj = {}) => {
expect(id).to.eq(123)
expect(obj.__error).to.eq('some error')
return done()
})
return this.server.emit('automation:request', 123, 'set:cookie', { name: 'foo', value: 'bar', domain: 'localhost', secure: true, path: '/foo' })
})
})
describe('clear:cookies', () => {
beforeEach(() => {
browser.runtime.lastError = { message: 'some error' }
sinon.stub(browser.cookies, 'getAll')
.withArgs({ domain: 'google.com' })
.resolves([
{ name: 'session', value: 'key', path: '/', domain: 'google.com', secure: true, httpOnly: true, expirationDate: 123 },
{ name: 'foo', value: 'bar', path: '/foo', domain: 'google.com', secure: false, httpOnly: false, expirationDate: 456 },
])
.withArgs({ domain: 'should.throw' })
.resolves([
{ name: 'shouldThrow', value: 'key', path: '/', domain: 'should.throw', secure: false, httpOnly: true, expirationDate: 123 },
])
.withArgs({ domain: 'no.details' })
.resolves([
{ name: 'shouldThrow', value: 'key', path: '/', domain: 'no.details', secure: false, httpOnly: true, expirationDate: 123 },
])
return sinon.stub(browser.cookies, 'remove')
.withArgs({ name: 'session', url: 'https://google.com/' })
.resolves(
{ name: 'session', url: 'https://google.com/', storeId: '123' },
)
.withArgs({ name: 'foo', url: 'http://google.com/foo' })
.resolves(
{ name: 'foo', url: 'https://google.com/foo', storeId: '123' },
)
.withArgs({ name: 'noDetails', url: 'http://no.details/' })
.resolves(null)
.withArgs({ name: 'shouldThrow', url: 'http://should.throw/' })
.rejects({ message: 'some error' })
})
it('resolves with array of removed cookies', function (done) {
this.socket.on('automation:response', (id, obj = {}) => {
expect(id).to.eq(123)
expect(obj.response).to.deep.eq([
{ name: 'session', value: 'key', path: '/', domain: 'google.com', secure: true, httpOnly: true, expirationDate: 123 },
{ name: 'foo', value: 'bar', path: '/foo', domain: 'google.com', secure: false, httpOnly: false, expirationDate: 456 },
])
return done()
})
return this.server.emit('automation:request', 123, 'clear:cookies', { domain: 'google.com' })
})
it('rejects with error thrown', function (done) {
this.socket.on('automation:response', (id, obj = {}) => {
expect(id).to.eq(123)
expect(obj.__error).to.eq('some error')
return done()
})
return this.server.emit('automation:request', 123, 'clear:cookies', { domain: 'should.throw' })
})
it('rejects when no details', function (done) {
this.socket.on('automation:response', (id, obj = {}) => {
expect(id).to.eq(123)
expect(obj.__error).to.eq(`Removing cookie failed for: ${JSON.stringify({ url: 'http://no.details/', name: 'shouldThrow' })}`)
return done()
})
return this.server.emit('automation:request', 123, 'clear:cookies', { domain: 'no.details' })
})
})
describe('clear:cookie', () => {
beforeEach(() => {
browser.runtime.lastError = { message: 'some error' }
sinon.stub(browser.cookies, 'getAll')
.withArgs({ domain: 'google.com', name: 'session' })
.resolves([
{ name: 'session', value: 'key', path: '/', domain: 'google.com', secure: true, httpOnly: true, expirationDate: 123 },
])
.withArgs({ domain: 'google.com', name: 'doesNotExist' })
.resolves([])
.withArgs({ domain: 'cdn.github.com', name: 'shouldThrow' })
.resolves([
{ name: 'shouldThrow', value: 'key', path: '/assets', domain: 'cdn.github.com', secure: false, httpOnly: true, expirationDate: 123 },
])
return sinon.stub(browser.cookies, 'remove')
.withArgs({ name: 'session', url: 'https://google.com/' })
.resolves(
{ name: 'session', url: 'https://google.com/', storeId: '123' },
)
.withArgs({ name: 'shouldThrow', url: 'http://cdn.github.com/assets' })
.rejects({ message: 'some error' })
})
it('resolves single removed cookie', function (done) {
this.socket.on('automation:response', (id, obj = {}) => {
expect(id).to.eq(123)
expect(obj.response).to.deep.eq(
{ name: 'session', value: 'key', path: '/', domain: 'google.com', secure: true, httpOnly: true, expirationDate: 123 },
)
return done()
})
return this.server.emit('automation:request', 123, 'clear:cookie', { domain: 'google.com', name: 'session' })
})
it('returns null when no cookie by name is found', function (done) {
this.socket.on('automation:response', (id, obj = {}) => {
expect(id).to.eq(123)
expect(obj.response).to.be.null
return done()
})
return this.server.emit('automation:request', 123, 'clear:cookie', { domain: 'google.com', name: 'doesNotExist' })
})
it('rejects with error', function (done) {
this.socket.on('automation:response', (id, obj = {}) => {
expect(id).to.eq(123)
expect(obj.__error).to.eq('some error')
return done()
})
return this.server.emit('automation:request', 123, 'clear:cookie', { domain: 'cdn.github.com', name: 'shouldThrow' })
})
})
describe('is:automation:client:connected', () => {
beforeEach(() => {
return sinon.stub(browser.tabs, 'query')
.withArgs({ url: 'CHANGE_ME_HOST/*', windowType: 'normal' })
.resolves([])
})
it('queries url and resolve', function (done) {
this.socket.on('automation:response', (id, obj = {}) => {
expect(id).to.eq(123)
expect(obj.response).to.be.undefined
return done()
})
return this.server.emit('automation:request', 123, 'is:automation:client:connected')
})
})
describe('take:screenshot', () => {
beforeEach(() => {
return sinon.stub(browser.windows, 'getLastFocused').resolves({ id: 1 })
})
afterEach(() => {
return delete browser.runtime.lastError
})
it('resolves with screenshot', function (done) {
sinon.stub(browser.tabs, 'captureVisibleTab')
.withArgs(1, { format: 'png' })
.resolves('foobarbaz')
this.socket.on('automation:response', (id, obj = {}) => {
expect(id).to.eq(123)
expect(obj.response).to.eq('foobarbaz')
return done()
})
return this.server.emit('automation:request', 123, 'take:screenshot')
})
it('rejects with browser.runtime.lastError', function (done) {
sinon.stub(browser.tabs, 'captureVisibleTab').withArgs(1, { format: 'png' }).rejects(new Error('some error'))
this.socket.on('automation:response', (id, obj) => {
expect(id).to.eq(123)
expect(obj.__error).to.eq('some error')
return done()
})
return this.server.emit('automation:request', 123, 'take:screenshot')
})
})
})
})