@revoloo/cypress6
Version:
Cypress.io end to end testing tool
812 lines (605 loc) • 21.9 kB
JavaScript
import sinon from 'sinon'
import $ from 'jquery'
import driver from '@packages/driver'
import studioRecorder, { StudioRecorder } from './studio-recorder'
import eventManager from '../lib/event-manager'
const createEvent = (props) => {
return {
isTrusted: true,
type: 'click',
...props,
}
}
describe('StudioRecorder', () => {
const cyVisitStub = sinon.stub()
const getSelectorStub = sinon.stub().returns('.selector')
let instance
beforeEach(() => {
instance = new StudioRecorder()
sinon.stub(instance, 'attachListeners')
sinon.stub(instance, 'removeListeners')
driver.$ = $
sinon.stub(eventManager, 'emit')
sinon.stub(eventManager, 'getCypress').returns({
cy: {
visit: cyVisitStub,
},
SelectorPlayground: {
getSelector: getSelectorStub,
},
env: () => null,
})
})
afterEach(() => {
sinon.restore()
})
it('exports a singleton by default', () => {
expect(studioRecorder).to.be.instanceOf(StudioRecorder)
})
context('#startLoading', () => {
it('sets isLoading, isOpen to true', () => {
instance.startLoading()
expect(instance.isLoading).to.be.true
expect(instance.isOpen).to.be.true
})
})
context('#setTestId', () => {
it('sets testId to id and hasRunnableId to true', () => {
instance.setTestId('r2')
expect(instance.testId).to.equal('r2')
expect(instance.hasRunnableId).to.be.true
})
it('does not clear suite id', () => {
instance.suiteId = 'r1'
instance.setTestId('r2')
expect(instance.suiteId).to.equal('r1')
})
})
context('#setSuiteId', () => {
it('sets suiteId to id and hasRunnableId to true', () => {
instance.setSuiteId('r1')
expect(instance.suiteId).to.equal('r1')
expect(instance.hasRunnableId).to.be.true
})
it('clears test id', () => {
instance.testId = 'r2'
instance.setSuiteId('r1')
expect(instance.testId).to.be.null
})
})
context('#start', () => {
beforeEach(() => {
sinon.stub(instance, 'visitUrl')
})
it('sets isActive, isOpen to true and isLoading to false', () => {
instance.start(null)
expect(instance.isActive).to.be.true
expect(instance.isLoading).to.be.false
expect(instance.isOpen).to.be.true
})
it('clears any existing logs', () => {
instance.logs = ['log 1', 'log 2']
instance.start(null)
expect(instance.logs).to.be.empty
})
it('visits url if url has been set', () => {
instance.url = 'cypress.io'
instance.start(null)
expect(instance.visitUrl).to.be.called
})
it('attaches listeners to the body', () => {
instance.start('body')
expect(instance.attachListeners).to.be.calledWith('body')
})
})
context('#stop', () => {
beforeEach(() => {
instance.start()
})
it('removes listeners', () => {
instance.stop()
expect(instance.removeListeners).to.be.called
})
it('sets isActive, isLoading to false and isOpen is true', () => {
instance.stop()
expect(instance.isActive).to.be.false
expect(instance.isOpen).to.be.true
})
})
context('#reset', () => {
beforeEach(() => {
instance.start()
})
it('removes listeners', () => {
instance.reset()
expect(instance.removeListeners).to.be.called
})
it('sets isActive, isOpen to false', () => {
instance.reset()
expect(instance.isActive).to.be.false
expect(instance.isOpen).to.be.false
})
it('clears logs and url', () => {
instance.reset()
expect(instance.logs).to.be.empty
expect(instance.url).to.be.null
})
it('does not remove runnable ids', () => {
instance.testId = 'r2'
instance.suiteId = 'r1'
instance.reset()
expect(instance.hasRunnableId).to.be.true
})
})
context('#cancel', () => {
beforeEach(() => {
instance.start()
})
it('removes listeners', () => {
instance.cancel()
expect(instance.removeListeners).to.be.called
})
it('sets isActive, isOpen to false', () => {
instance.cancel()
expect(instance.isActive).to.be.false
expect(instance.isOpen).to.be.false
})
it('clears logs and url', () => {
instance.logs = ['log 1', 'log 2']
instance.cancel()
expect(instance.logs).to.be.empty
expect(instance.url).to.be.null
})
it('removes runnable ids', () => {
instance.testId = 'r2'
instance.suiteId = 'r1'
instance.cancel()
expect(instance.hasRunnableId).to.be.false
})
})
context('#startSave', () => {
beforeEach(() => {
instance.start()
})
it('shows save modal if suite', () => {
instance.suiteId = 'r1'
instance.startSave()
expect(instance.saveModalIsOpen).to.be.true
})
it('skips modal and goes directly to save if test', () => {
sinon.stub(instance, 'save')
instance.testId = 'r2'
instance.startSave()
expect(instance.save).to.be.called
})
})
context('#save', () => {
beforeEach(() => {
instance.start()
})
it('closes save modal', () => {
instance.showSaveModal()
instance.save()
expect(instance.saveModalIsOpen).to.be.false
})
it('removes listeners', () => {
instance.save()
expect(instance.removeListeners).to.be.called
})
it('sets isActive to false and isOpen is true', () => {
instance.save()
expect(instance.isActive).to.be.false
expect(instance.isOpen).to.be.true
})
it('emits studio:save with relevant test information', () => {
const fileDetails = {
absoluteFile: '/path/to/spec.js',
line: 10,
column: 4,
}
const logs = ['log 1', 'log 2']
instance.setFileDetails(fileDetails)
instance.logs = logs
instance.testId = 'r2'
instance.save()
expect(eventManager.emit).to.be.calledWith('studio:save', {
fileDetails,
commands: logs,
isSuite: false,
testName: null,
})
})
it('emits studio:save with relevant suite information', () => {
const fileDetails = {
absoluteFile: '/path/to/spec.js',
line: 10,
column: 4,
}
const logs = ['log 1', 'log 2']
instance.setFileDetails(fileDetails)
instance.logs = logs
instance.suiteId = 'r1'
instance.save('new test name')
expect(eventManager.emit).to.be.calledWith('studio:save', {
fileDetails,
commands: logs,
isSuite: true,
testName: 'new test name',
})
})
})
context('#visitUrl', () => {
it('visits existing url by default', () => {
instance.url = 'cypress.io'
instance.visitUrl()
expect(cyVisitStub).to.be.calledWith('cypress.io')
})
it('visits and sets new url', () => {
instance.visitUrl('example.com')
expect(instance.url).to.equal('example.com')
expect(cyVisitStub).to.be.calledWith('example.com')
})
it('adds a log for the visited url', () => {
instance.visitUrl('cypress.io')
expect(instance.logs[0].selector).to.be.null
expect(instance.logs[0].name).to.equal('visit')
expect(instance.logs[0].message).to.equal('cypress.io')
})
})
// https://github.com/cypress-io/cypress/issues/14658
context('#recordMouseEvent', () => {
beforeEach(() => {
instance.testId = 'r2'
})
it('does not record events not sent by the user', () => {
instance._recordMouseEvent(createEvent({ isTrusted: false }))
expect(instance._previousMouseEvent).to.be.null
})
it('records the selector and element for an event', () => {
const el = $('<div />')[0]
instance._recordMouseEvent(createEvent({ target: el, type: 'mouseover' }))
expect(instance._previousMouseEvent.selector).to.equal('.selector')
expect(instance._previousMouseEvent.element).to.equal(el)
})
it('clears previous event on mouseout', () => {
const el = $('<div />')[0]
instance._previousMouseEvent = {
selector: '.selector',
element: el,
}
instance._recordMouseEvent(createEvent({ target: el, type: 'mouseout' }))
expect(instance._previousMouseEvent).to.be.null
})
it('replaces previous mouse event if element is different', () => {
const el1 = $('<div />')[0]
const el2 = $('<p />')[0]
instance._previousMouseEvent = {
selector: '.previous-selector',
element: el1,
}
instance._recordMouseEvent(createEvent({ target: el2, type: 'mouseover' }))
expect(instance._previousMouseEvent.selector).to.equal('.selector')
expect(instance._previousMouseEvent.element).to.equal(el2)
})
it('does not replace previous mouse event if element is the same', () => {
const el = $('<div />')[0]
instance._previousMouseEvent = {
selector: '.previous-selector',
element: el,
}
instance._recordMouseEvent(createEvent({ target: el, type: 'mousedown' }))
expect(instance._previousMouseEvent.selector).to.equal('.previous-selector')
expect(instance._previousMouseEvent.element).to.equal(el)
})
})
context('#getName', () => {
it('returns the event type by default', () => {
const $el = $('<div />')
const name = instance._getName(createEvent({ type: 'click' }), $el)
expect(name).to.equal('click')
})
it('returns select when a select changes', () => {
const $el = $('<select />')
const name = instance._getName(createEvent({ type: 'change' }), $el)
expect(name).to.equal('select')
})
it('returns type on keydown', () => {
const $el = $('<input />')
const name = instance._getName(createEvent({ type: 'keydown' }), $el)
expect(name).to.equal('type')
})
it('returns check on radio button click', () => {
const $el = $('<input type="radio" />')
const name = instance._getName(createEvent({ type: 'click' }), $el)
expect(name).to.equal('check')
})
it('returns check when checkbox is checked', () => {
const $el = $('<input type="checkbox" checked />')
const name = instance._getName(createEvent({ type: 'click' }), $el)
expect(name).to.equal('check')
})
it('returns uncheck when checkbox is unchecked', () => {
const $el = $('<input type="checkbox" />')
const name = instance._getName(createEvent({ type: 'click' }), $el)
expect(name).to.equal('uncheck')
})
})
context('#getMessage', () => {
it('returns null if the event has no value', () => {
const $el = $('<div />')
const message = instance._getMessage(createEvent({ type: 'click' }), $el)
expect(message).to.be.null
})
it('returns target value if the event has a value', () => {
const $el = $('<input value="blue" />')
const message = instance._getMessage(createEvent({ type: 'change' }), $el)
expect(message).to.equal('blue')
})
it('returns input value on keyup', () => {
const $el = $('<input value="value" />')
const message = instance._getMessage(createEvent({ type: 'keyup', key: 'e' }), $el)
expect(message).to.equal('value')
})
it('returns input value on keyup for special keys', () => {
const $el = $('<input value="value" />')
const message = instance._getMessage(createEvent({ type: 'keydown', key: 'Backspace' }), $el)
expect(message).to.equal('value')
})
it('returns input value with { escaped', () => {
const $el = $('<input value="my{value}" />')
const message = instance._getMessage(createEvent({ type: 'keydown', key: '}' }), $el)
expect(message).to.equal('my{{}value}')
})
it('returns input value with {enter} on enter keydown', () => {
const $el = $('<input value="value" />')
const message = instance._getMessage(createEvent({ type: 'keydown', key: 'Enter' }), $el)
expect(message).to.equal('value{enter}')
})
it('returns array if value is an array', () => {
const $el = $('<select multiple><option value="0">0</option><option value="1">1</option></select>')
$el.val(['0', '1'])
const message = instance._getMessage(createEvent({ type: 'change' }), $el)
expect(message).to.eql(['0', '1'])
})
})
context('#recordEvent', () => {
beforeEach(() => {
instance.testId = 'r2'
})
it('does not record events not sent by the user', () => {
instance._recordEvent(createEvent({ isTrusted: false }))
expect(instance.logs).to.be.empty
})
it('does not record events if the test has failed', () => {
instance.testFailed()
const $el = $('<div />')
instance._recordEvent(createEvent({ target: $el }))
expect(instance.logs).to.be.empty
})
it('uses the selector playground to get a selector for the element', () => {
const $el = $('<div />')
instance._recordEvent(createEvent({ target: $el }))
expect(getSelectorStub).to.be.calledWith($el)
})
it('uses the selector from a previously recorded mouse event on click', () => {
const el = $('<div />')[0]
instance._previousMouseEvent = {
selector: '.previous-selector',
element: el,
}
instance._recordEvent(createEvent({ type: 'click', target: el }))
expect(instance.logs[0].name).to.equal('click')
expect(instance.logs[0].selector).to.equal('.previous-selector')
})
it('clears previous mouse event after recording any event', () => {
const el = $('<div />')[0]
instance._previousMouseEvent = {
selector: '.previous-selector',
element: $('<input />')[0],
}
instance._recordEvent(createEvent({ type: 'click', target: el }))
expect(instance._previousMouseEvent).to.be.null
})
it('records a clear event before recording a type event', () => {
const $el = $('<input value="val" />')
instance._recordEvent(createEvent({ type: 'keyup', key: 'l', target: $el }))
expect(instance.logs.length).to.equal(2)
expect(instance.logs[0].name).to.equal('clear')
expect(instance.logs[0].message).to.equal(null)
expect(instance.logs[1].name).to.equal('type')
expect(instance.logs[1].message).to.equal('val')
})
it('removes an existing type if additional typing causes element to become empty', () => {
instance.logs = [{
id: 1,
selector: '.selector',
name: 'clear',
message: null,
}, {
id: 2,
selector: '.selector',
name: 'type',
message: 'a',
}]
const $el = $('<input value="" />')
instance._recordEvent(createEvent({ type: 'keyup', key: 'Backspace', target: $el }))
expect(instance.logs.length).to.equal(1)
expect(instance.logs[0].name).to.equal('clear')
expect(instance.logs[0].message).to.equal(null)
})
it('does not record a duplicate clear event if one already exists when typing', () => {
instance.logs = [{
id: 1,
selector: '.selector',
name: 'clear',
message: null,
}]
const $el = $('<input value="val" />')
instance._recordEvent(createEvent({ type: 'keyup', key: 'l', target: $el }))
expect(instance.logs.length).to.equal(2)
expect(instance.logs[0].name).to.equal('clear')
expect(instance.logs[0].message).to.equal(null)
expect(instance.logs[1].name).to.equal('type')
expect(instance.logs[1].message).to.equal('val')
})
it('does not record keyup outside of input', () => {
const $el = $('<div />')
instance._recordEvent(createEvent({ type: 'keyup', key: 'a', target: $el }))
expect(instance.logs).to.be.empty
})
it('does not record unneeded change events', () => {
const $el = $('<input />')
instance._recordEvent(createEvent({ type: 'change', target: $el }))
expect(instance.logs).to.be.empty
})
it('does not record keyup for enter key', () => {
const $el = $('<input value="val" />')
instance._recordEvent(createEvent({ type: 'keyup', key: 'Enter', target: $el }))
expect(instance.logs).to.be.empty
})
it('only records keydown for enter key', () => {
const $el = $('<input value="" />')
instance._recordEvent(createEvent({ type: 'keydown', key: 'a', target: $el }))
expect(instance.logs).to.be.empty
$el.val('a')
instance._recordEvent(createEvent({ type: 'keydown', key: 'b', target: $el }))
expect(instance.logs).to.be.empty
$el.val('ab')
instance._recordEvent(createEvent({ type: 'keydown', key: 'Enter', target: $el }))
expect(instance.logs[1].name).to.equal('type')
expect(instance.logs[1].message).to.equal('ab{enter}')
})
it('records multi select changes', () => {
const $el = $('<select multiple><option value="0">0</option><option value="1">1</option></select>')
$el.val(['0', '1'])
instance._recordEvent(createEvent({ type: 'change', target: $el }))
expect(instance.logs[0].name).to.eql('select')
expect(instance.logs[0].message).to.eql(['0', '1'])
})
it('does not record events on <option>', () => {
const $el = $('<option />')
instance._recordEvent(createEvent({ target: $el }))
expect(instance.logs).to.be.empty
})
it('does not record click events on <select>', () => {
const $el = $('<select />')
instance._recordEvent(createEvent({ type: 'click', target: $el }))
expect(instance.logs).to.be.empty
})
it('adds events to the command log with incrementing ids', () => {
const $el = $('<div />')
instance._recordEvent(createEvent({ type: 'click', target: $el }))
instance._recordEvent(createEvent({ type: 'click', target: $el }))
expect(instance.logs.length).to.equal(2)
expect(instance.logs[0].id).to.equal(1)
expect(instance.logs[0].selector).to.equal('.selector')
expect(instance.logs[0].name).to.equal('click')
expect(instance.logs[0].message).to.equal(null)
expect(instance.logs[1].id).to.equal(2)
expect(instance.logs[1].selector).to.equal('.selector')
expect(instance.logs[1].name).to.equal('click')
expect(instance.logs[1].message).to.equal(null)
})
it('emits two reporter:log:add events for each log', () => {
const $el = $('<button />')
instance._recordEvent(createEvent({ type: 'click', target: $el }))
expect(eventManager.emit).to.be.calledWith('reporter:log:add', {
hookId: 'r2-studio',
id: 's1-get',
instrument: 'command',
isStudio: true,
message: '.selector',
name: 'get',
numElements: 1,
number: 1,
state: 'passed',
testId: 'r2',
type: 'parent',
})
expect(eventManager.emit).to.be.calledWith('reporter:log:add', {
hookId: 'r2-studio',
id: 's1',
instrument: 'command',
isStudio: true,
message: null,
name: 'click',
numElements: 1,
number: undefined,
state: 'passed',
testId: 'r2',
type: 'child',
})
})
it('emits stringified message for arrays', () => {
const $el = $('<select multiple><option value="0">0</option><option value="1">1</option></select>')
$el.val(['0', '1'])
instance._recordEvent(createEvent({ type: 'change', target: $el }))
expect(eventManager.emit).to.be.calledWithMatch('reporter:log:add', {
message: '[0, 1]',
})
})
})
context('#updateLastLog', () => {
it('does not filter if there are no existing logs', () => {
const result = instance._updateLastLog('.selector', 'click', null)
expect(result).to.be.false
})
it('does not filter if selectors do not match', () => {
instance.logs = [{
id: 1,
selector: '.selector',
name: 'type',
message: 'a',
}]
const result = instance._updateLastLog('.different-selector', 'type', 'b')
expect(result).to.be.false
})
it('modifies original log in place with updated value for typing events with same selector', () => {
instance.logs = [{
id: 1,
selector: '.selector',
name: 'type',
message: 'a',
}]
const result = instance._updateLastLog('.selector', 'type', 'ab')
expect(result).to.be.true
expect(instance.logs[0].name).to.equal('type')
expect(instance.logs[0].message).to.equal('ab')
})
it('converts clicks into clears on type and returns false', () => {
instance.logs = [{
id: 1,
selector: '.selector',
name: 'click',
message: null,
}]
const result = instance._updateLastLog('.selector', 'type', 'a')
expect(result).to.be.false
expect(instance.logs[0].name).to.equal('clear')
expect(instance.logs[0].message).to.be.null
})
it('emits reporter:log:state:changed with the child log when a log is updated', () => {
instance.testId = 'r2'
instance.logs = [{
id: 1,
selector: '.selector',
name: 'type',
message: 'a',
}]
instance._updateLastLog('.selector', 'type', 'ab')
expect(eventManager.emit).to.be.calledWith('reporter:log:state:changed', {
hookId: 'r2-studio',
id: 's1',
instrument: 'command',
isStudio: true,
message: 'ab',
name: 'type',
numElements: 1,
number: undefined,
state: 'passed',
testId: 'r2',
type: 'child',
})
})
})
})