@revoloo/cypress6
Version:
Cypress.io end to end testing tool
901 lines (721 loc) • 28.1 kB
JavaScript
describe('Runs List', function () {
beforeEach(function () {
cy.fixture('user').as('user')
cy.fixture('config').as('config')
cy.fixture('projects').as('projects')
cy.fixture('specs').as('specs')
cy.fixture('runs').as('runs')
cy.fixture('organizations').as('orgs')
cy.fixture('dashboard_projects').as('dashboardProjects')
this.goToRuns = () => {
return cy.get('.navbar-default a')
.contains('Runs').click()
}
this.validCiProject = {
id: 'project-id-123',
public: true,
orgId: '000',
}
cy.visitIndex().then(function (win) {
let start = win.App.start
this.win = win
this.ipc = win.App.ipc
cy.stub(this.ipc, 'getOptions').resolves({ projectRoot: '/foo/bar' })
cy.stub(this.ipc, 'updaterCheck').resolves(false)
cy.stub(this.ipc, 'closeBrowser').resolves(null)
cy.stub(this.ipc, 'getSpecs').yields(null, this.specs)
cy.stub(this.ipc, 'getOrgs').resolves(this.orgs)
cy.stub(this.ipc, 'getDashboardProjects').resolves(this.dashboardProjects)
cy.stub(this.ipc, 'requestAccess')
cy.stub(this.ipc, 'setupDashboardProject')
cy.stub(this.ipc, 'externalOpen')
cy.stub(this.ipc, 'beginAuth').resolves()
cy.stub(this.ipc, 'setClipboardText')
this.openProject = this.util.deferred()
cy.stub(this.ipc, 'openProject').returns(this.openProject.promise)
this.getCurrentUser = this.util.deferred()
cy.stub(this.ipc, 'getCurrentUser').returns(this.getCurrentUser.promise)
this.pingApiServer = this.util.deferred()
cy.stub(this.ipc, 'pingApiServer').returns(this.pingApiServer.promise)
this.getProjectStatus = this.util.deferred()
cy.stub(this.ipc, 'getProjectStatus').returns(this.getProjectStatus.promise)
this.getRuns = this.util.deferred()
cy.stub(this.ipc, 'getRuns').returns(this.getRuns.promise)
start()
})
})
context('page display', function () {
beforeEach(function () {
this.getCurrentUser.resolve(this.user)
this.openProject.resolve(this.config)
this.getRuns.resolve(this.runs)
this.goToRuns()
})
it('highlights run nav', () => {
cy.get('.navbar-default a')
.contains('Runs').should('have.class', 'active')
})
})
context('api server connection', function () {
beforeEach(function () {
const timestamp = new Date(2016, 11, 19, 10, 0, 0).valueOf()
cy.clock(timestamp)
this.getCurrentUser.resolve(this.user)
this.openProject.resolve(this.config)
this.getRuns.resolve(this.runs)
this.goToRuns()
})
it('pings api server', function () {
expect(this.ipc.pingApiServer).to.be.called
cy.get('.loader')
})
describe('success', function () {
beforeEach(function () {
this.pingApiServer.resolve()
})
it('shows runs', () => {
cy.contains('h5', 'Runs')
})
context('displays each run\'s data', function () {
beforeEach(function () {
cy.get('.runs-container li').first().as('firstRunRow')
cy.get('.runs-container li').eq(1).as('secondRunRow')
cy.get('.runs-container li').eq(3).as('fourthRunRow')
})
it('displays build num', function () {
cy.get('@secondRunRow').contains(`#${this.runs[1].buildNumber}`)
cy.percySnapshot()
})
it('displays commit info', function () {
cy.get('@firstRunRow').contains(this.runs[0].commit.branch)
cy.get('@firstRunRow').contains(this.runs[0].commit.message)
})
it('display no info msg & does not display avatar', () => {
cy.get('@secondRunRow').within(function () {
cy.get('.user-avatar').should('not.exist')
cy.contains('No commit info found')
})
})
it('displays platform info', () => {
cy.get('@secondRunRow').within(function () {
cy.contains(this.runs[1].instances[0].platform.osVersionFormatted)
cy.contains(this.runs[1].instances[0].platform.browserName)
cy.get('.fa-apple')
cy.get('.browser-icon')
.should('have.attr', 'src')
.and('include', './img/chrome')
})
})
it('does not display browser when null', () => {
cy.get('@firstRunRow').within(function () {
cy.contains(this.runs[0].instances[0].platform.osVersionFormatted)
cy.get('.browser-icon').should('not.exist')
})
})
it('displays totals', function () {
cy.get('@secondRunRow').contains(this.runs[1].totalFailed)
cy.get('@secondRunRow').contains(this.runs[1].totalPassed)
})
it('displays times', function () {
cy.get('@secondRunRow').contains('a few secs ago')
cy.get('@secondRunRow').contains('00:16')
})
it('displays separate timers for incomplete runs', function () {
cy.get('@firstRunRow').contains('12:24:47')
cy.get('@fourthRunRow').contains('12:45:47')
.then(() => {
cy.tick(1000)
})
cy.get('@firstRunRow').contains('12:24:48')
cy.get('@fourthRunRow').contains('12:45:48')
})
context('spec display', function () {
it('displays spec if defined when 1 instance', function () {
cy.get('@firstRunRow').contains(this.runs[1].instances[0].spec)
})
it('does not display spec if null', () => {
cy.get('.runs-container li').eq(2).contains('spec').should('not.exist')
})
it('does not display spec if multiple instances', () => {
cy.get('.runs-container li').eq(2).contains('spec').should('not.exist')
})
})
context('parallelization disabled', () => {
it('adds a warning indicator to the run list item', function () {
cy.get('.env-duration .fa-exclamation-triangle')
.should('exist')
.trigger('mouseover')
cy.get('.cy-tooltip').contains('Parallelization was disabled for this run')
cy.percySnapshot()
})
})
})
})
describe('failure', function () {
beforeEach(function () {
this.pingApiServerAgain = this.util.deferred()
this.ipc.pingApiServer.onCall(1).returns(this.pingApiServerAgain.promise)
this.pingApiServer.reject({
apiUrl: 'http://api.server',
message: 'ECONNREFUSED',
})
})
it('shows "cannot connect to api server" message', function () {
cy.contains('Cannot connect to API server')
cy.contains('http://api.server')
cy.contains('ECONNREFUSED')
cy.percySnapshot()
})
describe('trying again', function () {
beforeEach(() => {
cy.contains('Try again').click()
})
it('pings again', () => {
cy.get('.loader').then(function () {
expect(this.ipc.pingApiServer).to.be.calledTwice
})
})
it('shows new error on failure', function () {
this.pingApiServerAgain.reject({
apiUrl: 'http://api.server',
message: 'WHADJAEXPECT',
})
cy.contains('Cannot connect to API server')
cy.contains('http://api.server')
cy.contains('WHADJAEXPECT')
})
it('shows runs', function () {
this.pingApiServerAgain.resolve()
cy.contains('h5', 'Runs')
})
})
describe('api help link', () => {
it('goes to external api help link', () => {
cy.contains('Learn more').click().then(function () {
expect(this.ipc.externalOpen).to.be.calledWith('https://on.cypress.io/help-connect-to-api')
})
})
})
})
})
context('with a current user', function () {
beforeEach(function () {
this.getCurrentUser.resolve(this.user)
this.config.projectId = this.projects[0].id
this.openProject.resolve(this.config)
this.pingApiServer.resolve()
const timestamp = new Date(2016, 11, 19, 10, 0, 0).valueOf()
cy.clock(timestamp)
.then(() => {
return this.goToRuns()
}).then(() => {
return this.getRuns.resolve(this.runs)
})
})
it('fetches runs', function () {
expect(this.ipc.getRuns).to.be.called
})
it('lists runs', function () {
cy.get('.runs-container li')
.should('have.length', this.runs.length)
})
it('displays link to dashboard that goes to admin project runs', () => {
cy.contains('See all').click()
.then(function () {
expect(this.ipc.externalOpen).to.be.calledWith(`https://on.cypress.io/dashboard/projects/${this.projects[0].id}/runs`)
})
})
it('displays run status icon', () => {
cy.get('.runs-container li').first().find('> div')
.should('have.class', 'running')
})
it('displays last updated', () => {
cy.contains('Last updated: 10:00:00am')
})
it('clicking run opens admin', function () {
cy.get('.runs-container li').first()
.click()
.then(() => {
expect(this.ipc.externalOpen).to.be.calledWith(`https://on.cypress.io/dashboard/projects/${this.projects[0].id}/runs/${this.runs[0].buildNumber}`)
})
})
})
context('without a current user', function () {
beforeEach(function () {
this.getCurrentUser.resolve(null)
this.openProject.resolve(this.config)
this.pingApiServer.resolve()
this.goToRuns()
})
it('does not fetch runs', function () {
expect(this.ipc.getRuns).not.to.be.called
})
it('clicking Log In to Dashboard opens login', () => {
cy.contains('button', 'Log In to Dashboard').click().then(function () {
expect(this.ipc.beginAuth).to.be.calledOnce
})
cy.percySnapshot()
})
it('clicking Log In to Dashboard passes utm code', () => {
cy.contains('button', 'Log In to Dashboard').click().then(function () {
expect(this.ipc.beginAuth).to.be.calledWith('Runs Tab')
})
})
})
context('without a project id', function () {
beforeEach(function () {
this.getCurrentUser.resolve(this.user)
this.config.projectId = undefined
this.openProject.resolve(this.config)
this.pingApiServer.resolve()
this.goToRuns()
})
it('displays "need to set up" message', () => {
cy.contains('Connect to the Dashboard to see your recorded test runs here')
})
})
context('without a current user and without project id', function () {
beforeEach(function () {
this.getCurrentUser.resolve(null)
this.config.projectId = undefined
this.openProject.resolve(this.config)
this.pingApiServer.resolve()
this.goToRuns()
})
it('displays "need to set up" message', () => {
cy.contains('Connect to the Dashboard to see your recorded test runs here')
})
describe('click setup project', function () {
beforeEach(() => {
cy.contains('Connect to Dashboard').click()
})
it('shows login message', () => {
cy.get('.login h1').should('contain', 'Log In')
cy.percySnapshot()
})
it('clicking Log In to Dashboard opens login', () => {
cy.contains('button', 'Log In to Dashboard').click().then(function () {
expect(this.ipc.beginAuth).to.be.calledOnce
})
})
})
})
describe('polling runs', function () {
beforeEach(function () {
this.getCurrentUser.resolve(this.user)
this.openProject.resolve(this.config)
this.pingApiServer.resolve()
this.getRunsAgain = this.util.deferred()
this.ipc.getRuns.onCall(1).returns(this.getRunsAgain.promise)
cy.clock()
.then(() => {
return this.goToRuns()
}).then(() => {
return this.getRuns.resolve(this.runs)
})
cy.get('.runs-container') //# wait for original runs to show
cy.clock().then(() => {
this.getRunsAgain = this.util.deferred()
this.ipc.getRuns.onCall(1).returns(this.getRunsAgain.promise)
})
cy.tick(10000)
})
it('has original state of runs', () => {
cy.get('.runs-container li').first().find('> div')
.should('have.class', 'running')
})
it('sends get:runs ipc event', function () {
expect(this.ipc.getRuns).to.be.calledTwice
})
it('disables refresh button', () => {
cy.get('.runs header button').should('be.disabled')
})
it('spins the refresh button', () => {
cy.get('.runs header button i').should('have.class', 'fa-spin')
})
context('success', function () {
beforeEach(function () {
this.runs[0].status = 'passed'
this.getRunsAgain.resolve(this.runs)
})
it('updates the runs', () => {
cy.get('.runs-container li').first().find('> div')
.should('have.class', 'passed')
})
it('enables refresh button', () => {
cy.get('.runs header button').should('not.be.disabled')
})
it('stops spinning the refresh button', () => {
cy.get('.runs header button i').should('not.have.class', 'fa-spin')
})
})
context('errors', function () {
beforeEach(function () {
this.ipcError = (details) => {
const err = Object.assign(details, { name: 'foo', message: 'There\'s an error' })
this.getRunsAgain.reject(err)
}
})
it('displays permissions error', function () {
this.ipcError({ statusCode: 403 })
cy.contains('Request access')
cy.percySnapshot()
})
it('displays "need to set up" message', function () {
this.ipcError({ type: 'NO_PROJECT_ID' })
cy.contains('Connect to the Dashboard to see your recorded test runs here')
})
it('displays old runs if another error', function () {
this.ipcError({ type: 'TIMED_OUT' })
cy.get('.runs-container li').should('have.length', this.runs.length)
})
})
})
describe('manually refreshing runs', function () {
beforeEach(function () {
this.getCurrentUser.resolve(this.user)
this.openProject.resolve(this.config)
this.pingApiServer.resolve()
this.getRunsAgain = this.util.deferred()
this.ipc.getRuns.onCall(1).returns(this.getRunsAgain.promise)
this.getRuns.resolve(this.runs)
this.goToRuns().then(() => {
cy.get('.runs header button').click()
})
})
it('sends get:runs ipc event', function () {
expect(this.ipc.getRuns).to.be.calledTwice
})
it('still shows list of runs', function () {
cy.get('.runs-container li').should('have.length', this.runs.length)
})
it('disables refresh button', () => {
cy.get('.runs header button').should('be.disabled')
})
it('spins the refresh button', () => {
cy.get('.runs header button i').should('have.class', 'fa-spin')
})
describe('when runs have loaded', function () {
beforeEach(function () {
this.getRunsAgain.resolve(this.runs)
})
it('enables refresh button', () => {
cy.get('.runs header button').should('not.be.disabled')
})
it('stops spinning the refresh button', () => {
cy.get('.runs header button i').should('not.have.class', 'fa-spin')
})
})
})
context('error states', function () {
beforeEach(function () {
this.getCurrentUser.resolve(this.user)
this.pingApiServer.resolve()
})
describe('permissions error', function () {
beforeEach(function () {
this.openProject.resolve(this.config)
this.goToRuns()
})
context('statusCode: 403', function () {
beforeEach(function () {
this.getRuns.reject({ name: 'foo', message: 'There\'s an error', statusCode: 403 })
})
it('displays permissions message', () => {
cy.contains('Request access')
})
})
context('any case', function () {
beforeEach(function () {
this.requestAccess = this.util.deferred()
this.ipc.requestAccess.returns(this.requestAccess.promise)
this.getRuns.reject({ name: 'foo', message: 'There\'s an error', statusCode: 403 })
})
context('request access', function () {
beforeEach(() => {
cy.contains('button', 'Request access').as('requestAccessBtn').click()
})
it('sends requests access with project id', function () {
expect(this.ipc.requestAccess).to.be.calledWith(this.config.projectId)
})
it('disables button', () => {
cy.get('@requestAccessBtn').should('be.disabled')
})
it('hides "Request access" text', () => {
cy.get('@requestAccessBtn').find('span').should('not.be.visible')
})
it('shows spinner', () => {
cy.get('@requestAccessBtn').find('> i').should('be.visible')
})
describe('when request succeeds', function () {
beforeEach(function () {
this.requestAccess.resolve()
})
it('shows success message', () => {
cy.contains('Request sent')
cy.percySnapshot()
})
it('persists request state (until app is reloaded at least)', function () {
this.ipc.getRuns.onCall(1).rejects({ name: 'foo', message: 'There\'s an error', statusCode: 403 })
cy.get('.navbar-default a').contains('Tests').click()
cy.get('.navbar-default a').contains('Runs').click()
cy.contains('Request sent')
})
})
describe('when request succeeds and user is already a member', function () {
beforeEach(function () {
this.requestAccess.reject({ name: 'foo', message: 'There\'s an error', type: 'ALREADY_MEMBER' })
this.getRuns = this.util.deferred()
this.ipc.getRuns.onCall(1).returns(this.getRuns.promise)
this.ipc.getRuns.onCall(2).returns(this.getRuns.promise)
})
it('retries getting runs', function () {
cy.wrap(this.ipc.getRuns).its('callCount').should('be.above', 1)
})
it('shows loading spinner', () => {
cy.get('.loader')
})
it('shows runs when getting runs succeeds', function () {
this.getRuns.resolve(this.runs)
cy.get('.runs-container li')
.should('have.length', this.runs.length)
})
})
describe('when request fails', function () {
describe('for unknown reason', function () {
beforeEach(function () {
this.requestAccess.reject({ name: 'foo', message: `\
{
"cheese": "off the cracker"
}\
` })
//# block the subsequent tests until
//# this is displayed in the DOM
cy.contains('Request Failed')
cy.contains('off the cracker')
cy.contains('button', 'Request access').as('requestAccessBtn')
})
it('enables button', () => {
cy.get('@requestAccessBtn').should('not.be.disabled')
cy.percySnapshot()
})
it('shows "Request access" text', () => {
cy.get('@requestAccessBtn').find('span').should('be.visible')
})
it('hides spinner', () => {
cy.get('@requestAccessBtn').find('> i').should('not.be.visible')
})
})
describe('because requested was denied', function () {
beforeEach(function () {
this.requestAccess.reject({ type: 'DENIED', name: 'foo', message: 'There\'s an error' })
})
it('shows "success" message', () => {
cy.contains('Request sent')
})
})
describe('because request was already sent', function () {
beforeEach(function () {
this.requestAccess.reject({ type: 'ALREADY_REQUESTED', name: 'foo', message: 'There\'s an error' })
})
it('shows "success" message', () => {
cy.contains('Request sent')
})
})
describe('because user became unauthenticated', function () {
beforeEach(function () {
this.requestAccess.reject({ name: '', message: '', statusCode: 401 })
})
it('logs user out', () => {
cy.shouldBeLoggedOut()
})
it('shows login message', () => {
cy.get('.empty h4').should('contain', 'Log in')
cy.percySnapshot()
})
it('clicking Log In to Dashboard opens login', () => {
cy.contains('button', 'Log In to Dashboard').click().then(function () {
expect(this.ipc.beginAuth).to.be.called
})
})
})
})
})
})
})
describe('timed out error', function () {
beforeEach(function () {
this.openProject.resolve(this.config)
this.goToRuns().then(() => {
this.getRuns.reject({ name: 'foo', message: 'There\'s an error', type: 'TIMED_OUT' })
})
})
it('displays timed out message', () => {
cy.contains('timed out')
cy.percySnapshot()
})
})
describe('not found error', function () {
beforeEach(function () {
this.openProject.resolve(this.config)
this.goToRuns().then(() => {
this.getRuns.reject({ name: 'foo', message: 'There\'s an error', type: 'NOT_FOUND' })
})
})
it('displays empty message', () => {
cy.contains('Runs cannot be displayed')
cy.percySnapshot()
})
})
describe('unauthenticated error', function () {
beforeEach(function () {
this.openProject.resolve(this.config)
this.goToRuns().then(() => {
this.getRuns.reject({ name: '', message: '', statusCode: 401 })
})
})
it('logs user out', () => {
cy.shouldBeLoggedOut()
})
it('shows login message', () => {
cy.get('.empty h4').should('contain', 'Log in')
})
})
describe('no project id error', function () {
beforeEach(function () {
this.openProject.resolve(this.config)
this.ipc.setupDashboardProject.resolves(this.validCiProject)
this.ipc.getRuns.onCall(1).resolves([])
this.goToRuns().then(() => {
this.getRuns.reject({ name: 'foo', message: 'There\'s an error', type: 'NO_PROJECT_ID' })
})
})
it('displays "need to set up" message', () => {
cy.contains('Connect to the Dashboard to see your recorded test runs here')
cy.percySnapshot()
})
it('clears message after setting up to record', () => {
cy.contains('.btn', 'Connect to Dashboard').click()
cy.get('.organizations-select__dropdown-indicator').click()
cy.get('.organizations-select__menu').should('be.visible')
cy.get('.organizations-select__option')
.contains('Your personal organization').click()
cy.get('.setup-project')
.contains('Create new project').click()
cy.get('.setup-project')
.contains('.btn', 'Set up project').click()
cy.contains('To record your first')
cy.percySnapshot()
})
})
describe('unexpected error', function () {
beforeEach(function () {
this.openProject.resolve(this.config)
this.goToRuns().then(() => {
this.getRuns.reject({ name: 'foo', message: `{"no runs": "for you"}`, type: 'UNKNOWN' })
})
})
it('displays unexpected error message', function () {
cy.contains('unexpected error')
cy.contains('"no runs": "for you"')
cy.percySnapshot()
})
})
describe('unauthorized project', function () {
beforeEach(function () {
this.openProject.resolve(this.config)
this.getProjectStatus.resolve({
state: 'UNAUTHORIZED',
})
this.goToRuns()
})
it('displays permissions message', () => {
cy.contains('Request access')
})
})
describe('invalid project', function () {
beforeEach(function () {
this.openProject.resolve(this.config)
this.ipc.setupDashboardProject.resolves(this.validCiProject)
this.getProjectStatus.resolve({
state: 'INVALID',
})
this.goToRuns()
})
it('displays empty message', () => {
cy.contains('Runs cannot be displayed')
})
it('clicking link opens setup project window', function () {
cy.contains('.btn', 'Set up a project').click()
cy.get('.setup-project').should('be.visible')
})
it('clears message after setting up CI', function () {
cy.contains('.btn', 'Set up a project').click()
cy.get('.organizations-select__dropdown-indicator').click()
cy.get('.organizations-select__menu').should('be.visible')
cy.get('.organizations-select__option')
.contains('Your personal organization').click()
cy.get('.setup-project')
.contains('Create new project').click()
cy.get('.setup-project')
.contains('.btn', 'Set up project').click()
cy.contains('To record your first')
})
})
describe('no runs', () => {
context('having never setup CI', function () {
beforeEach(function () {
this.config.projectId = null
this.openProject.resolve(this.config)
this.goToRuns().then(() => {
this.getRuns.resolve([])
})
})
it('displays "need to set up" message', () => {
cy.contains('Connect to the Dashboard to see your recorded test runs here')
})
it('banner does not cover browser dropdown', () => {
// The browser dropdown would sometimes display behind the runs banner
cy.contains('.dropdown-chosen', 'Chrome').click()
cy.get('.browsers-list')
.find('.dropdown-menu li').first().should('be.visible')
cy.percySnapshot()
})
})
})
context('having previously set up CI', function () {
beforeEach(function () {
this.openProject.resolve(this.config)
this.goToRuns().then(() => {
this.getRuns.resolve([])
})
})
it('displays empty message', () => {
cy.contains('To record your first')
})
it('opens project id guide on clicking "Why?"', () => {
cy.contains('Why?').click()
.then(function () {
expect(this.ipc.externalOpen).to.be.calledWith('https://on.cypress.io/what-is-a-project-id')
})
})
it('opens dashboard on clicking "Cypress Dashboard"', () => {
cy.contains('Cypress Dashboard').click()
.then(function () {
expect(this.ipc.externalOpen).to.be.calledWith(`https://on.cypress.io/dashboard/projects/${this.config.projectId}/runs`)
})
})
it('shows tooltip on hover of copy to clipboard', () => {
cy.get('#code-record-command').find('.action-copy').trigger('mouseover')
cy.get('.cy-tooltip').should('contain', 'Copy to clipboard')
cy.get('#code-record-command').find('.action-copy').trigger('mouseout')
})
it('copies record key command to clipboard', () => {
cy.get('#code-record-command').find('.action-copy').click()
.then(function () {
expect(this.ipc.setClipboardText).to.be.calledWith(`cypress run --record --key <record-key>`)
})
})
})
})
})