(function() {
var BlackHoleStream, Promise, _, debug, debugFrames, ffmpeg, ffmpegPath, fs, la, os, path, stream, utils;
_ = require("lodash");
la = require("lazy-ass");
os = require("os");
path = require("path");
utils = require("fluent-ffmpeg/lib/utils");
debug = require("debug")("cypress:server:video");
ffmpeg = require("fluent-ffmpeg");
stream = require("stream");
Promise = require("bluebird");
ffmpegPath = require("@ffmpeg-installer/ffmpeg").path;
BlackHoleStream = require("black-hole-stream");
fs = require("./util/fs");
debugFrames = require("debug")("cypress:server:video:frames");
debug("using ffmpeg from %s", ffmpegPath);
ffmpeg.setFfmpegPath(ffmpegPath);
module.exports = {
getMsFromDuration: function(duration) {
return utils.timemarkToSeconds(duration) * 1000;
},
getCodecData: function(src) {
return new Promise(function(resolve, reject) {
return ffmpeg().on("stderr", function(stderr) {
return debug("get codecData stderr log %o", {
message: stderr
});
}).on("codecData", resolve).input(src).format("null").output(new BlackHoleStream()).run();
}).tap(function(data) {
return debug('codecData %o', {
src: src,
data: data
});
}).tapCatch(function(err) {
return debug("getting codecData failed", {
err: err
});
});
},
copy: function(src, dest) {
debug("copying from %s to %s", src, dest);
return fs.copyAsync(src, dest, {
overwrite: true
})["catch"]({
code: "ENOENT"
}, function() {});
},
start: function(name, options) {
var done, endVideoCapture, ended, errored, logErrors, pt, skipped, startCapturing, wantsWrite, writeVideoFrame, written;
if (options == null) {
options = {};
}
pt = stream.PassThrough();
ended = Promise.pending();
done = false;
errored = false;
written = false;
logErrors = true;
wantsWrite = true;
skipped = 0;
_.defaults(options, {
onError: function() {}
});
endVideoCapture = function() {
done = true;
if (!written) {
logErrors = false;
}
pt.end();
return ended.promise;
};
writeVideoFrame = function(data) {
if (done) {
return;
}
written = true;
debugFrames("writing video frame");
if (wantsWrite) {
if (!(wantsWrite = pt.write(data))) {
return pt.once("drain", function() {
debugFrames("video stream drained");
return wantsWrite = true;
});
}
} else {
skipped += 1;
return debugFrames("skipping video frame %o", {
skipped: skipped
});
}
};
startCapturing = function() {
return new Promise(function(resolve) {
var cmd;
return cmd = ffmpeg({
source: pt,
priority: 20
}).inputFormat("image2pipe").inputOptions("-use_wallclock_as_timestamps 1").videoCodec("libx264").outputOptions("-preset ultrafast").on("start", function(command) {
debug("capture started %o", {
command: command
});
return resolve({
cmd: cmd,
startedVideoCapture: new Date
});
}).on("codecData", function(data) {
return debug("capture codec data: %o", data);
}).on("stderr", function(stderr) {
return debug("capture stderr log %o", {
message: stderr
});
}).on("error", function(err, stdout, stderr) {
debug("capture errored: %o", {
error: err.message,
stdout: stdout,
stderr: stderr
});
if (logErrors) {
options.onError(err, stdout, stderr);
}
return ended.reject(err);
}).on("end", function() {
debug("capture ended");
return ended.resolve();
}).save(name);
});
};
return startCapturing().then(function(arg) {
var cmd, startedVideoCapture;
cmd = arg.cmd, startedVideoCapture = arg.startedVideoCapture;
return {
cmd: cmd,
endVideoCapture: endVideoCapture,
writeVideoFrame: writeVideoFrame,
startedVideoCapture: startedVideoCapture
};
});
},
process: function(name, cname, videoCompression, onProgress) {
var total;
if (onProgress == null) {
onProgress = function() {};
}
total = null;
return new Promise(function(resolve, reject) {
var cmd;
debug("processing video from %s to %s video compression %o", name, cname, videoCompression);
return cmd = ffmpeg().input(name).videoCodec("libx264").outputOptions(["-preset fast", "-crf " + videoCompression]).on("start", function(command) {
return debug("compression started %o", {
command: command
});
}).on("codecData", function(data) {
debug("compression codec data: %o", data);
return total = utils.timemarkToSeconds(data.duration);
}).on("stderr", function(stderr) {
return debug("compression stderr log %o", {
message: stderr
});
}).on("progress", function(progress) {
var progressed;
if (!total) {
return;
}
debug("compression progress: %o", progress);
progressed = utils.timemarkToSeconds(progress.timemark);
return onProgress(progressed / total);
}).on("error", function(err, stdout, stderr) {
debug("compression errored: %o", {
error: err.message,
stdout: stdout,
stderr: stderr
});
return reject(err);
}).on("end", function() {
debug("compression ended");
onProgress(1);
return fs.moveAsync(cname, name, {
overwrite: true
}).then(function() {
return resolve();
});
}).save(cname);
});
}
};
}).call(this);
require('../../spec_helper')
const Promise = require('bluebird')
const electron = require('electron')
const stripAnsi = require('strip-ansi')
const snapshot = require('snap-shot-it')
const R = require('ramda')
const pkg = require('@packages/root')
const { ProjectBase } = require('../../../lib/project-base')
const { fs } = require(`${root}../lib/util/fs`)
const user = require(`${root}../lib/user`)
const errors = require(`${root}../lib/errors`)
const config = require(`${root}../lib/config`)
const { ProjectE2E } = require(`${root}../lib/project-e2e`)
const browsers = require(`${root}../lib/browsers`)
const Reporter = require(`${root}../lib/reporter`)
const runMode = require(`${root}../lib/modes/run`)
const openProject = require(`${root}../lib/open_project`)
const videoCapture = require(`${root}../lib/video_capture`)
const env = require(`${root}../lib/util/env`)
const random = require(`${root}../lib/util/random`)
const system = require(`${root}../lib/util/system`)
const specsUtil = require(`${root}../lib/util/specs`)
const { experimental } = require(`${root}../lib/experiments`)
describe('lib/modes/run', () => {
beforeEach(function () {
this.projectInstance = new ProjectBase('/_test-output/path/to/project-e2e')
})
context('.getProjectId', () => {
it('resolves if id', () => {
return runMode.getProjectId('project', 'id123')
.then((ret) => {
expect(ret).to.eq('id123')
})
})
it('resolves if CYPRESS_PROJECT_ID set', () => {
sinon.stub(env, 'get').withArgs('CYPRESS_PROJECT_ID').returns('envId123')
return runMode.getProjectId('project')
.then((ret) => {
expect(ret).to.eq('envId123')
})
})
it('is null when no projectId', () => {
const project = {
getProjectId: sinon.stub().rejects(new Error),
}
return runMode.getProjectId(project)
.then((ret) => {
expect(ret).to.be.null
})
})
})
context('.openProjectCreate', () => {
let onError
beforeEach(() => {
sinon.stub(openProject, 'create').resolves()
onError = sinon.spy()
const options = {
onError,
port: 8080,
env: { foo: 'bar' },
isTextTerminal: true,
projectRoot: '/_test-output/path/to/project/foo',
}
return runMode.openProjectCreate(options.projectRoot, 1234, options)
})
it('calls openProject.create with projectRoot + options', () => {
expect(openProject.create).to.be.calledWithMatch('/_test-output/path/to/project/foo', {
port: 8080,
projectRoot: '/_test-output/path/to/project/foo',
env: { foo: 'bar' },
}, {
morgan: false,
socketId: 1234,
report: true,
isTextTerminal: true,
})
})
it('calls options.onError with error message onError', () => {
const error = { message: 'the message' }
expect(openProject.create.lastCall.args[2].onError).to.be.a('function')
openProject.create.lastCall.args[2].onError(error)
expect(onError).to.be.calledWith(error)
})
})
context('.getElectronProps', () => {
it('sets width and height', () => {
const props = runMode.getElectronProps()
expect(props.width).to.eq(1920)
expect(props.height).to.eq(1080)
})
it('sets show to boolean', () => {
let props = runMode.getElectronProps(false)
expect(props.show).to.be.false
props = runMode.getElectronProps(true)
expect(props.show).to.be.true
})
it('sets onScreencastFrame when write is true', () => {
const write = sinon.stub()
const image = {
data: '',
}
const props = runMode.getElectronProps(true, write)
props.onScreencastFrame(image)
expect(write).to.be.calledOnce
})
it('does not set onScreencastFrame when write is falsy', () => {
const props = runMode.getElectronProps(true, false)
expect(props).not.to.have.property('recordFrameRate')
expect(props).not.to.have.property('onScreencastFrame')
})
it('sets options.show = false onNewWindow callback', () => {
const options = { show: true }
const props = runMode.getElectronProps()
props.onNewWindow(null, null, null, null, options)
expect(options.show).to.eq(false)
})
it('calls options.onError when webContents crashes', function () {
sinon.spy(errors, 'get')
sinon.spy(errors, 'log')
const onError = sinon.spy()
const props = runMode.getElectronProps(true, this.projectInstance, onError)
props.onCrashed()
expect(errors.get).to.be.calledWith('RENDERER_CRASHED')
expect(errors.log).to.be.calledOnce
expect(onError).to.be.called
expect(onError.lastCall.args[0].message).to.include('We detected that the Chromium Renderer process just crashed.')
})
})
context('.launchBrowser', () => {
beforeEach(function () {
this.launch = sinon.stub(openProject, 'launch')
sinon.stub(runMode, 'screenshotMetadata').returns({ a: 'a' })
})
it('can launch electron', function () {
const screenshots = []
const spec = {
absolute: '/path/to/spec',
}
const browser = {
name: 'electron',
family: 'chromium',
isHeaded: false,
}
runMode.launchBrowser({
spec,
browser,
project: this.projectInstance,
writeVideoFrame: 'write',
screenshots,
})
expect(this.launch).to.be.calledWithMatch(browser, spec)
const browserOpts = this.launch.firstCall.args[2]
const { onAfterResponse } = browserOpts.automationMiddleware
expect(onAfterResponse).to.be.a('function')
onAfterResponse('take:screenshot', {}, {})
onAfterResponse('get:cookies')
expect(screenshots).to.deep.eq([{ a: 'a' }])
})
it('can launch chrome', function () {
const spec = {
absolute: '/path/to/spec',
}
const browser = {
name: 'chrome',
family: 'chromium',
isHeaded: true,
}
runMode.launchBrowser({
spec,
browser,
project: {},
})
expect(this.launch).to.be.calledWithMatch(browser, spec, {})
})
})
context('.postProcessRecording', () => {
beforeEach(() => {
sinon.stub(videoCapture, 'process').resolves()
})
it('calls video process with name, cname and videoCompression', () => {
return runMode.postProcessRecording('foo', 'foo-compress', 32, true)
.then(() => {
expect(videoCapture.process).to.be.calledWith('foo', 'foo-compress', 32)
})
})
it('does not call video process when videoCompression is false', () => {
return runMode.postProcessRecording('foo', 'foo-compress', false, true)
.then(() => {
expect(videoCapture.process).not.to.be.called
})
})
it('calls video process if we have been told to upload videos', () => {
return runMode.postProcessRecording('foo', 'foo-compress', 32, true)
.then(() => {
expect(videoCapture.process).to.be.calledWith('foo', 'foo-compress', 32)
})
})
it('does not call video process if there are no failing tests and we have set not to upload video on passing', () => {
return runMode.postProcessRecording('foo', 'foo-compress', 32, false)
.then(() => {
expect(videoCapture.process).not.to.be.called
})
})
})
context('.waitForBrowserToConnect', () => {
it('throws TESTS_DID_NOT_START_FAILED after 3 connection attempts', function () {
sinon.spy(errors, 'warning')
sinon.spy(errors, 'get')
sinon.spy(openProject, 'closeBrowser')
sinon.stub(runMode, 'launchBrowser').resolves()
sinon.stub(runMode, 'waitForSocketConnection').callsFake(() => {
return Promise.delay(1000)
})
const onError = sinon.spy()
return runMode.waitForBrowserToConnect({ project: this.projectInstance, timeout: 10, onError })
.then(() => {
expect(openProject.closeBrowser).to.be.calledThrice
expect(runMode.launchBrowser).to.be.calledThrice
expect(errors.warning).to.be.calledWith('TESTS_DID_NOT_START_RETRYING', 'Retrying...')
expect(errors.warning).to.be.calledWith('TESTS_DID_NOT_START_RETRYING', 'Retrying again...')
expect(errors.get).to.be.calledWith('TESTS_DID_NOT_START_FAILED')
expect(onError).to.be.called
expect(onError.lastCall.args[0].message).to.include('The browser never connected. Something is wrong. The tests cannot run. Aborting...')
})
})
})
context('.waitForSocketConnection', () => {
beforeEach(function () {
this.projectStub = sinon.stub({
on () {},
removeListener () {},
})
})
it('attaches fn to \'socket:connected\' event', function () {
runMode.waitForSocketConnection(this.projectStub, 1234)
expect(this.projectStub.on).to.be.calledWith('socket:connected')
})
it('calls removeListener if socketId matches id', function () {
this.projectStub.on.yields(1234)
return runMode.waitForSocketConnection(this.projectStub, 1234)
.then(() => {
expect(this.projectStub.removeListener).to.be.calledWith('socket:connected')
})
})
describe('integration', () => {
it('resolves undefined when socket:connected fires', function () {
process.nextTick(() => {
return this.projectInstance.emit('socket:connected', 1234)
})
return runMode.waitForSocketConnection(this.projectInstance, 1234)
.then((ret) => {
expect(ret).to.be.undefined
})
})
it('does not resolve if socketId does not match id', function () {
process.nextTick(() => {
return this.projectInstance.emit('socket:connected', 12345)
})
return runMode
.waitForSocketConnection(this.projectInstance, 1234)
.timeout(50)
.then(() => {
throw new Error('should time out and not resolve')
}).catch(Promise.TimeoutError, (err) => {})
})
it('actually removes the listener', function () {
process.nextTick(() => {
this.projectInstance.emit('socket:connected', 12345)
expect(this.projectInstance.listeners('socket:connected')).to.have.length(1)
this.projectInstance.emit('socket:connected', '1234')
expect(this.projectInstance.listeners('socket:connected')).to.have.length(1)
this.projectInstance.emit('socket:connected', 1234)
expect(this.projectInstance.listeners('socket:connected')).to.have.length(0)
})
return runMode.waitForSocketConnection(this.projectInstance, 1234)
})
})
})
context('.waitForTestsToFinishRunning', () => {
beforeEach(function () {
sinon.stub(fs, 'pathExists').resolves(true)
sinon.stub(this.projectInstance, 'getConfig').resolves({})
sinon.spy(runMode, 'getVideoRecordingDelay')
sinon.spy(errors, 'warning')
this.setupProjectEnd = (results) => {
results = results || {
stats: {
failures: 0,
},
}
process.nextTick(() => {
this.projectInstance.emit('end', results)
})
}
})
it('end event resolves with obj, displays stats, displays screenshots, sets video timestamps', function () {
const startedVideoCapture = new Date
const screenshots = [{}, {}, {}]
const endVideoCapture = sinon.stub().resolves()
const results = {
tests: [{ attempts: [1] }, { attempts: [2] }, { attempts: [3] }],
stats: {
tests: 1,
passes: 2,
failures: 3,
pending: 4,
duration: 5,
},
}
sinon.stub(Reporter, 'setVideoTimestamp')
sinon.stub(runMode, 'postProcessRecording').resolves()
sinon.spy(runMode, 'displayResults')
sinon.spy(runMode, 'displayScreenshots')
sinon.spy(Promise.prototype, 'delay')
this.setupProjectEnd(results)
return runMode.waitForTestsToFinishRunning({
project: this.projectInstance,
videoName: 'foo.mp4',
compressedVideoName: 'foo-compressed.mp4',
videoCompression: 32,
videoUploadOnPasses: true,
gui: false,
screenshots,
startedVideoCapture,
endVideoCapture,
spec: {
path: 'cypress/integration/spec.js',
},
})
.then((obj) => {
// since video was recording, there was a delay to let video finish
expect(Reporter.setVideoTimestamp).calledWith(startedVideoCapture, [1, 2, 3])
expect(runMode.getVideoRecordingDelay).to.have.returned(1000)
expect(Promise.prototype.delay).to.be.calledWith(1000)
expect(runMode.postProcessRecording).to.be.calledWith('foo.mp4', 'foo-compressed.mp4', 32, true)
expect(runMode.displayResults).to.be.calledWith(results)
expect(runMode.displayScreenshots).to.be.calledWith(screenshots)
expect(obj).to.deep.eq({
screenshots,
video: 'foo.mp4',
error: null,
hooks: null,
reporterStats: null,
shouldUploadVideo: true,
tests: results.tests,
spec: {
path: 'cypress/integration/spec.js',
},
stats: {
tests: 1,
passes: 2,
failures: 3,
pending: 4,
duration: 5,
},
})
})
})
it('exiting early resolves with no tests, and error', function () {
sinon.useFakeTimers({ shouldAdvanceTime: true })
const err = new Error('foo')
const startedVideoCapture = new Date
const wallClock = new Date()
const screenshots = [{}, {}, {}]
const endVideoCapture = sinon.stub().resolves()
sinon.stub(runMode, 'postProcessRecording').resolves()
sinon.spy(runMode, 'displayResults')
sinon.spy(runMode, 'displayScreenshots')
sinon.spy(Promise.prototype, 'delay')
process.nextTick(() => {
runMode.exitEarly(err)
})
return runMode.waitForTestsToFinishRunning({
project: this.projectInstance,
videoName: 'foo.mp4',
compressedVideoName: 'foo-compressed.mp4',
videoCompression: 32,
videoUploadOnPasses: true,
gui: false,
screenshots,
startedVideoCapture,
endVideoCapture,
spec: {
path: 'cypress/integration/spec.js',
},
})
.then((obj) => {
// since video was recording, there was a delay to let video finish
expect(runMode.getVideoRecordingDelay).to.have.returned(1000)
expect(Promise.prototype.delay).to.be.calledWith(1000)
expect(runMode.postProcessRecording).to.be.calledWith('foo.mp4', 'foo-compressed.mp4', 32, true)
expect(runMode.displayResults).to.be.calledWith(obj)
expect(runMode.displayScreenshots).to.be.calledWith(screenshots)
expect(obj).to.deep.eq({
screenshots,
error: err.message,
video: 'foo.mp4',
hooks: null,
tests: null,
reporterStats: null,
shouldUploadVideo: true,
spec: {
path: 'cypress/integration/spec.js',
},
stats: {
failures: 1,
tests: 0,
passes: 0,
pending: 0,
suites: 0,
skipped: 0,
wallClockDuration: 0,
wallClockStartedAt: wallClock.toJSON(),
wallClockEndedAt: wallClock.toJSON(),
},
})
})
})
it('logs warning and resolves on failed video end', async function () {
this.setupProjectEnd()
sinon.spy(videoCapture, 'process')
const endVideoCapture = sinon.stub().rejects()
await runMode.waitForTestsToFinishRunning({
project: this.projectInstance,
videoName: 'foo.mp4',
compressedVideoName: 'foo-compressed.mp4',
videoCompression: 32,
videoUploadOnPasses: true,
gui: false,
endVideoCapture,
})
expect(errors.warning).to.be.calledWith('VIDEO_POST_PROCESSING_FAILED')
expect(videoCapture.process).not.to.be.called
})
it('logs warning and resolves on failed video compression', async function () {
this.setupProjectEnd()
const endVideoCapture = sinon.stub().resolves()
sinon.stub(videoCapture, 'process').rejects()
await runMode.waitForTestsToFinishRunning({
project: this.projectInstance,
videoName: 'foo.mp4',
compressedVideoName: 'foo-compressed.mp4',
videoCompression: 32,
videoUploadOnPasses: true,
gui: false,
endVideoCapture,
})
expect(errors.warning).to.be.calledWith('VIDEO_POST_PROCESSING_FAILED')
})
it('does not upload video when videoUploadOnPasses is false and no failures', function () {
this.setupProjectEnd()
sinon.spy(runMode, 'postProcessRecording')
sinon.spy(videoCapture, 'process')
const endVideoCapture = sinon.stub().resolves()
return runMode.waitForTestsToFinishRunning({
project: this.projectInstance,
videoName: 'foo.mp4',
compressedVideoName: 'foo-compressed.mp4',
videoCompression: 32,
videoUploadOnPasses: false,
gui: false,
endVideoCapture,
})
.then(() => {
expect(runMode.postProcessRecording).to.be.calledWith('foo.mp4', 'foo-compressed.mp4', 32, false)
expect(videoCapture.process).not.to.be.called
})
})
it('does not delay when not capturing a video', () => {
sinon.stub(runMode, 'listenForProjectEnd').resolves({})
return runMode.waitForTestsToFinishRunning({
startedVideoCapture: null,
})
.then(() => {
expect(runMode.getVideoRecordingDelay).to.have.returned(0)
})
})
describe('when video is deleted in after:spec event', function () {
beforeEach(function () {
this.setupProjectEnd()
sinon.spy(runMode, 'postProcessRecording')
sinon.spy(videoCapture, 'process')
fs.pathExists.resolves(false)
})
it('does not process or upload video', function () {
return runMode.waitForTestsToFinishRunning({
project: this.projectInstance,
startedVideoCapture: new Date(),
videoName: 'foo.mp4',
endVideoCapture: sinon.stub().resolves(),
})
.then((results) => {
expect(runMode.postProcessRecording).not.to.be.called
expect(videoCapture.process).not.to.be.called
expect(results.shouldUploadVideo).to.be.false
})
})
it('nulls out video value from results', function () {
return runMode.waitForTestsToFinishRunning({
project: this.projectInstance,
startedVideoCapture: new Date(),
videoName: 'foo.mp4',
endVideoCapture: sinon.stub().resolves(),
})
.then((results) => {
expect(results.video).to.be.null
})
})
})
})
context('.listenForProjectEnd', () => {
it('resolves with end event + argument', function () {
process.nextTick(() => {
return this.projectInstance.emit('end', { foo: 'bar' })
})
return runMode.listenForProjectEnd(this.projectInstance)
.then((obj) => {
expect(obj).to.deep.eq({
foo: 'bar',
})
})
})
it('stops listening to end event', function () {
process.nextTick(() => {
expect(this.projectInstance.listeners('end')).to.have.length(1)
this.projectInstance.emit('end', { foo: 'bar' })
expect(this.projectInstance.listeners('end')).to.have.length(0)
})
return runMode.listenForProjectEnd(this.projectInstance)
})
})
context('.run browser vs video recording', () => {
beforeEach(function () {
sinon.stub(electron.app, 'on').withArgs('ready').yieldsAsync()
sinon.stub(user, 'ensureAuthToken')
sinon.stub(ProjectE2E, 'ensureExists').resolves()
sinon.stub(ProjectBase, 'ensureExists').resolves()
sinon.stub(random, 'id').returns(1234)
sinon.stub(openProject, 'create').resolves(openProject)
sinon.stub(runMode, 'waitForSocketConnection').resolves()
sinon.stub(runMode, 'waitForTestsToFinishRunning').resolves({
stats: { failures: 10 },
spec: {},
})
sinon.spy(runMode, 'waitForBrowserToConnect')
sinon.stub(videoCapture, 'start').resolves()
sinon.stub(openProject, 'launch').resolves()
sinon.stub(openProject, 'getProject').resolves(this.projectInstance)
sinon.spy(errors, 'warning')
sinon.stub(config, 'get').resolves({
proxyUrl: 'http://localhost:12345',
video: true,
videosFolder: 'videos',
integrationFolder: '/path/to/integrationFolder',
})
sinon.stub(specsUtil, 'find').resolves([
{
name: 'foo_spec.js',
path: 'cypress/integration/foo_spec.js',
absolute: '/path/to/spec.js',
},
])
})
it('shows no warnings for default browser', () => {
return runMode.run()
.then(() => {
expect(errors.warning).to.not.be.called
})
})
it('throws an error if invalid browser family supplied', () => {
const browser = { name: 'opera', family: 'opera - btw when is Opera support coming?' }
sinon.stub(browsers, 'ensureAndGetByNameOrPath').resolves(browser)
return expect(runMode.run({ browser: 'opera' }))
.to.be.rejectedWith(/invalid browser family in/)
})
it('shows no warnings for chrome browser', () => {
return runMode.run({ browser: 'chrome' })
.then(() => {
expect(errors.warning).to.not.be.called
})
})
it('names video file with spec name', () => {
return runMode.run()
.then(() => {
expect(videoCapture.start).to.be.calledWith('videos/foo_spec.js.mp4')
expect(runMode.waitForTestsToFinishRunning).to.be.calledWithMatch({
compressedVideoName: 'videos/foo_spec.js-compressed.mp4',
})
})
})
})
context('.run', () => {
beforeEach(function () {
sinon.stub(this.projectInstance, 'getConfig').resolves({
proxyUrl: 'http://localhost:12345',
})
sinon.stub(electron.app, 'on').withArgs('ready').yieldsAsync()
sinon.stub(user, 'ensureAuthToken')
sinon.stub(ProjectE2E, 'ensureExists').resolves()
sinon.stub(ProjectBase, 'ensureExists').resolves()
sinon.stub(random, 'id').returns(1234)
sinon.stub(openProject, 'create').resolves(openProject)
sinon.stub(system, 'info').resolves({ osName: 'osFoo', osVersion: 'fooVersion' })
sinon.stub(browsers, 'ensureAndGetByNameOrPath').resolves({
name: 'fooBrowser',
path: 'path/to/browser',
version: '777',
family: 'chromium',
})
sinon.stub(runMode, 'waitForSocketConnection').resolves()
sinon.stub(runMode, 'waitForTestsToFinishRunning').resolves({
stats: { failures: 10 },
spec: {},
})
sinon.spy(runMode, 'waitForBrowserToConnect')
sinon.spy(runMode, 'runSpecs')
sinon.stub(openProject, 'launch').resolves()
sinon.stub(openProject, 'getProject').resolves(this.projectInstance)
sinon.stub(specsUtil, 'find').resolves([
{
name: 'foo_spec.js',
path: 'cypress/integration/foo_spec.js',
absolute: '/path/to/spec.js',
},
])
})
it('no longer ensures user session', () => {
return runMode.run()
.then(() => {
expect(user.ensureAuthToken).not.to.be.called
})
})
it('resolves with object and totalFailed', () => {
return runMode.run()
.then((results) => {
expect(results).to.have.property('totalFailed', 10)
})
})
it('passes projectRoot + options to openProject', () => {
const opts = { projectRoot: '/path/to/project', foo: 'bar' }
return runMode.run(opts)
.then(() => {
expect(openProject.create).to.be.calledWithMatch(opts.projectRoot, opts)
})
})
it('passes project + id to waitForBrowserToConnect', function () {
return runMode.run()
.then(() => {
expect(runMode.waitForBrowserToConnect).to.be.calledWithMatch({
project: this.projectInstance,
socketId: 1234,
})
})
})
it('passes project to waitForTestsToFinishRunning', function () {
return runMode.run()
.then(() => {
expect(runMode.waitForTestsToFinishRunning).to.be.calledWithMatch({
project: this.projectInstance,
})
})
})
it('passes headed to openProject.launch', () => {
const browser = { name: 'electron', family: 'chromium' }
browsers.ensureAndGetByNameOrPath.resolves(browser)
return runMode.run({ headed: true })
.then(() => {
expect(openProject.launch).to.be.calledWithMatch(
browser,
{
name: 'foo_spec.js',
path: 'cypress/integration/foo_spec.js',
absolute: '/path/to/spec.js',
},
{
show: true,
},
)
})
})
it('passes sys to runSpecs', () => {
return runMode.run()
.then(() => {
expect(runMode.runSpecs).to.be.calledWithMatch({
sys: {
osName: 'osFoo',
osVersion: 'fooVersion',
},
})
})
})
it('passes browser to runSpecs', () => {
return runMode.run()
.then(() => {
expect(runMode.runSpecs).to.be.calledWithMatch({
browser: {
name: 'fooBrowser',
path: 'path/to/browser',
version: '777',
},
})
})
})
})
context('#displayRunStarting', () => {
// restore pkg.version property
// for some reason I cannot stub property value using Sinon
let version
// save a copy of "true" experiments right away
const names = R.clone(experimental.names)
before(() => {
// reset experiments names before each test
experimental.names = {}
version = pkg.version
})
afterEach(() => {
pkg.version = version
experimental.names = names
})
it('returns heading with experiments', () => {
pkg.version = '1.2.3'
experimental.names = {
experimentalFeatureA: 'experimentalFeatureA',
experimentalFeatureB: 'experimentalFeatureB',
}
const options = {
browser: {
displayName: 'Electron',
majorVersion: 99,
isHeadless: true,
},
config: {
resolved: {
experimentalFeatureA: {
value: true,
from: 'config',
},
experimentalFeatureB: {
value: 4,
from: 'cli',
},
},
},
}
const heading = runMode.displayRunStarting(options)
snapshot('enabled experiments', stripAnsi(heading))
})
it('resets the experiments names', () => {
expect(experimental.names, 'experiments were reset').to.deep.equal(names)
})
it('returns heading with some enabled experiments', () => {
pkg.version = '1.2.3'
experimental.names = {
experimentalFeatureA: 'experimentalFeatureA',
experimentalFeatureB: 'experimentalFeatureB',
}
const options = {
browser: {
displayName: 'Electron',
majorVersion: 99,
isHeadless: true,
},
config: {
resolved: {
// means this feature is not enabled, should not appear in the heading
experimentalFeatureA: {
value: true,
from: 'default',
},
experimentalFeatureB: {
value: 4,
from: 'cli',
},
},
},
}
const heading = runMode.displayRunStarting(options)
const text = stripAnsi(heading)
snapshot('some enabled experiments', text)
// explicit assertions for test clarity
expect(text).to.not.include('experimentalFeatureA')
expect(text).to.include('experimentalFeatureB')
})
it('returns heading without experiments', () => {
pkg.version = '1.2.3'
const options = {
browser: {
displayName: 'Electron',
majorVersion: 99,
isHeadless: true,
},
config: {
resolved: {},
},
}
const heading = runMode.displayRunStarting(options)
snapshot('without enabled experiments', stripAnsi(heading))
})
it('restores pkg.version', () => {
expect(pkg.version).to.not.equal('1.2.3')
})
})
})