UNPKG

@revoloo/cypress6

Version:

Cypress.io end to end testing tool

529 lines (426 loc) 16.6 kB
require('../../spec_helper') const os = require('os') const extension = require('@packages/extension') const plugins = require(`${root}../lib/plugins`) const utils = require(`${root}../lib/browsers/utils`) const chrome = require(`${root}../lib/browsers/chrome`) const { fs } = require(`${root}../lib/util/fs`) describe('lib/browsers/chrome', () => { context('#open', () => { beforeEach(function () { // mock CRI client during testing this.criClient = { ensureMinimumProtocolVersion: sinon.stub().resolves(), send: sinon.stub().resolves(), Page: { screencastFrame: sinon.stub().returns(), }, close: sinon.stub().resolves(), on: sinon.stub(), } this.automation = { push: sinon.stub(), use: sinon.stub().returns(), } // mock launched browser child process object this.launchedBrowser = { kill: sinon.stub().returns(), } this.onCriEvent = (event, data, options) => { this.criClient.on.withArgs(event).yieldsAsync(data) return chrome.open('chrome', 'http://', options, this.automation) .then(() => { this.criClient.on = undefined }) } sinon.stub(chrome, '_writeExtension').resolves('/path/to/ext') sinon.stub(chrome, '_connectToChromeRemoteInterface').resolves(this.criClient) sinon.stub(plugins, 'execute').callThrough() sinon.stub(utils, 'launch').resolves(this.launchedBrowser) sinon.stub(utils, 'getProfileDir').returns('/profile/dir') sinon.stub(utils, 'ensureCleanCache').resolves('/profile/dir/CypressCache') this.readJson = sinon.stub(fs, 'readJson') this.readJson.withArgs('/profile/dir/Default/Preferences').rejects({ code: 'ENOENT' }) this.readJson.withArgs('/profile/dir/Default/Secure Preferences').rejects({ code: 'ENOENT' }) this.readJson.withArgs('/profile/dir/Local State').rejects({ code: 'ENOENT' }) // port for Chrome remote interface communication sinon.stub(utils, 'getPort').resolves(50505) }) afterEach(function () { expect(this.criClient.ensureMinimumProtocolVersion).to.be.calledOnce }) it('focuses on the page, calls CRI Page.visit, enables Page events, and sets download behavior', function () { return chrome.open('chrome', 'http://', {}, this.automation) .then(() => { expect(utils.getPort).to.have.been.calledOnce // to get remote interface port expect(this.criClient.send.callCount).to.equal(4) expect(this.criClient.send).to.have.been.calledWith('Page.bringToFront') expect(this.criClient.send).to.have.been.calledWith('Page.navigate') expect(this.criClient.send).to.have.been.calledWith('Page.enable') expect(this.criClient.send).to.have.been.calledWith('Page.setDownloadBehavior') }) }) it('is noop without before:browser:launch', function () { return chrome.open('chrome', 'http://', {}, this.automation) .then(() => { expect(plugins.execute).not.to.be.called }) }) it('is noop if newArgs are not returned', function () { const args = [] sinon.stub(chrome, '_getArgs').returns(args) sinon.stub(plugins, 'has').returns(true) plugins.execute.resolves(null) return chrome.open('chrome', 'http://', {}, this.automation) .then(() => { // to initialize remote interface client and prepare for true tests // we load the browser with blank page first expect(utils.launch).to.be.calledWith('chrome', 'about:blank', args) }) }) it('sets default window size in headless mode', function () { chrome._writeExtension.restore() return chrome.open({ isHeadless: true, isHeaded: false }, 'http://', {}, this.automation) .then(() => { const args = utils.launch.firstCall.args[2] expect(args).to.include.members([ '--headless', '--window-size=1280,720', ]) }) }) it('does not load extension in headless mode', function () { chrome._writeExtension.restore() return chrome.open({ isHeadless: true, isHeaded: false }, 'http://', {}, this.automation) .then(() => { const args = utils.launch.firstCall.args[2] expect(args).to.include.members([ '--headless', '--remote-debugging-port=50505', '--remote-debugging-address=127.0.0.1', '--user-data-dir=/profile/dir', '--disk-cache-dir=/profile/dir/CypressCache', ]) }) }) it('uses a custom profilePath if supplied', function () { chrome._writeExtension.restore() utils.getProfileDir.restore() const profilePath = '/home/foo/snap/chromium/current' const fullPath = `${profilePath}/Cypress/chromium-stable/interactive` this.readJson.withArgs(`${fullPath}/Default/Preferences`).rejects({ code: 'ENOENT' }) this.readJson.withArgs(`${fullPath}/Default/Secure Preferences`).rejects({ code: 'ENOENT' }) this.readJson.withArgs(`${fullPath}/Local State`).rejects({ code: 'ENOENT' }) return chrome.open({ isHeadless: true, isHeaded: false, profilePath, name: 'chromium', channel: 'stable', }, 'http://', {}, this.automation) .then(() => { const args = utils.launch.firstCall.args[2] expect(args).to.include.members([ `--user-data-dir=${fullPath}`, ]) }) }) it('DEPRECATED: normalizes --load-extension if provided in plugin', function () { plugins.register('before:browser:launch', (browser, config) => { return Promise.resolve(['--foo=bar', '--load-extension=/foo/bar/baz.js']) }) const pathToTheme = extension.getPathToTheme() const onWarning = sinon.stub() return chrome.open('chrome', 'http://', { onWarning }, this.automation) .then(() => { const args = utils.launch.firstCall.args[2] expect(args).to.deep.eq([ '--foo=bar', `--load-extension=/foo/bar/baz.js,/path/to/ext,${pathToTheme}`, '--user-data-dir=/profile/dir', '--disk-cache-dir=/profile/dir/CypressCache', ]) expect(onWarning).calledOnce }) }) it('normalizes --load-extension if provided in plugin', function () { plugins.register('before:browser:launch', (browser, config) => { return Promise.resolve({ args: ['--foo=bar', '--load-extension=/foo/bar/baz.js'], }) }) const pathToTheme = extension.getPathToTheme() return chrome.open('chrome', 'http://', {}, this.automation) .then(() => { const args = utils.launch.firstCall.args[2] expect(args).to.include.members([ '--foo=bar', `--load-extension=/foo/bar/baz.js,/path/to/ext,${pathToTheme}`, '--user-data-dir=/profile/dir', '--disk-cache-dir=/profile/dir/CypressCache', ]) }) }) it('normalizes multiple extensions from plugins', function () { plugins.register('before:browser:launch', (browser, config) => { return Promise.resolve({ args: ['--foo=bar', '--load-extension=/foo/bar/baz.js,/quux.js'] }) }) const pathToTheme = extension.getPathToTheme() const onWarning = sinon.stub() return chrome.open('chrome', 'http://', { onWarning }, this.automation) .then(() => { const args = utils.launch.firstCall.args[2] expect(args).to.include.members([ '--foo=bar', `--load-extension=/foo/bar/baz.js,/quux.js,/path/to/ext,${pathToTheme}`, '--user-data-dir=/profile/dir', '--disk-cache-dir=/profile/dir/CypressCache', ]) expect(onWarning).not.calledOnce }) }) it('cleans up an unclean browser profile exit status', function () { this.readJson.withArgs('/profile/dir/Default/Preferences').resolves({ profile: { exit_type: 'Abnormal', exited_cleanly: false, }, }) sinon.stub(fs, 'outputJson').resolves() return chrome.open('chrome', 'http://', {}, this.automation) .then(() => { expect(fs.outputJson).to.be.calledWith('/profile/dir/Default/Preferences', { profile: { exit_type: 'Normal', exited_cleanly: true, }, }) }) }) it('calls cri client close on kill', function () { // need a reference here since the stub will be monkey-patched const { kill, } = this.launchedBrowser return chrome.open('chrome', 'http://', {}, this.automation) .then(() => { expect(this.launchedBrowser.kill).to.be.a('function') return this.launchedBrowser.kill() }).then(() => { expect(this.criClient.close).to.be.calledOnce expect(kill).to.be.calledOnce }) }) it('rejects if CDP version check fails', function () { this.criClient.ensureMinimumProtocolVersion.rejects() return expect(chrome.open('chrome', 'http://', {}, this.automation)).to.be.rejectedWith('Cypress requires at least Chrome 64.') }) // https://github.com/cypress-io/cypress/issues/9265 it('respond ACK after receiving new screenshot frame', function () { const frameMeta = { data: Buffer.from(''), sessionId: '1' } const write = sinon.stub() const options = { onScreencastFrame: write } return this.onCriEvent('Page.screencastFrame', frameMeta, options) .then(() => { expect(this.criClient.send).to.have.been.calledWith('Page.startScreencast') expect(write).to.have.been.calledWith(frameMeta) expect(this.criClient.send).to.have.been.calledWith('Page.screencastFrameAck', { sessionId: frameMeta.sessionId }) }) }) describe('downloads', function () { it('pushes create:download after download begins', function () { const downloadData = { guid: '1', suggestedFilename: 'file.csv', url: 'http://localhost:1234/file.csv', } const options = { downloadsFolder: 'downloads' } return this.onCriEvent('Page.downloadWillBegin', downloadData, options) .then(() => { expect(this.automation.push).to.be.calledWith('create:download', { id: '1', filePath: 'downloads/file.csv', mime: 'text/csv', url: 'http://localhost:1234/file.csv', }) }) }) it('pushes complete:download after download completes', function () { const downloadData = { guid: '1', state: 'completed', } const options = { downloadsFolder: 'downloads' } return this.onCriEvent('Page.downloadProgress', downloadData, options) .then(() => { expect(this.automation.push).to.be.calledWith('complete:download', { id: '1', }) }) }) }) }) context('#_getArgs', () => { it('disables gpu when linux', () => { sinon.stub(os, 'platform').returns('linux') const args = chrome._getArgs({}, {}) expect(args).to.include('--disable-gpu') }) it('does not disable gpu when not linux', () => { sinon.stub(os, 'platform').returns('darwin') const args = chrome._getArgs({}, {}) expect(args).not.to.include('--disable-gpu') }) it('turns off sandbox when linux', () => { sinon.stub(os, 'platform').returns('linux') const args = chrome._getArgs({}, {}) expect(args).to.include('--no-sandbox') }) it('does not turn off sandbox when not linux', () => { sinon.stub(os, 'platform').returns('win32') const args = chrome._getArgs({}, {}) expect(args).not.to.include('--no-sandbox') }) it('adds user agent when options.userAgent', () => { const args = chrome._getArgs({}, { userAgent: 'foo', }) expect(args).to.include('--user-agent=foo') }) it('does not add user agent', () => { const args = chrome._getArgs({}, {}) expect(args).not.to.include('--user-agent=foo') }) it('disables RootLayerScrolling in versions 66 or 67', () => { const arg = '--disable-blink-features=RootLayerScrolling' const disabledRootLayerScrolling = function (version, bool) { const args = chrome._getArgs({ majorVersion: version, }, {}) if (bool) { return expect(args).to.include(arg) } expect(args).not.to.include(arg) } disabledRootLayerScrolling('65', false) disabledRootLayerScrolling('66', true) disabledRootLayerScrolling('67', true) disabledRootLayerScrolling('68', false) }) // https://github.com/cypress-io/cypress/issues/1872 it('adds <-loopback> proxy bypass rule in version 72+', () => { const arg = '--proxy-bypass-list=<-loopback>' const chromeVersionHasLoopback = function (version, bool) { const args = chrome._getArgs({ majorVersion: version, }, {}) if (bool) { return expect(args).to.include(arg) } expect(args).not.to.include(arg) } chromeVersionHasLoopback('71', false) chromeVersionHasLoopback('72', true) return chromeVersionHasLoopback('73', true) }) }) context('#_getChromePreferences', () => { it('returns map of empty if the files do not exist', () => { sinon.stub(fs, 'readJson') .withArgs('/foo/Default/Preferences').rejects({ code: 'ENOENT' }) .withArgs('/foo/Default/Secure Preferences').rejects({ code: 'ENOENT' }) .withArgs('/foo/Local State').rejects({ code: 'ENOENT' }) expect(chrome._getChromePreferences('/foo')).to.eventually.deep.eq({ default: {}, defaultSecure: {}, localState: {}, }) }) it('returns map of json objects if the files do exist', () => { sinon.stub(fs, 'readJson') .withArgs('/foo/Default/Preferences').resolves({ foo: 'bar' }) .withArgs('/foo/Default/Secure Preferences').resolves({ bar: 'baz' }) .withArgs('/foo/Local State').resolves({ baz: 'quux' }) expect(chrome._getChromePreferences('/foo')).to.eventually.deep.eq({ default: { foo: 'bar' }, defaultSecure: { bar: 'baz' }, localState: { baz: 'quux' }, }) }) }) context('#_mergeChromePreferences', () => { it('merges as expected', () => { const originalPrefs = { default: {}, defaultSecure: { foo: 'bar', deleteThis: 'nephew', }, localState: {}, } const newPrefs = { default: { something: { nested: 'here', }, }, defaultSecure: { deleteThis: null, }, someGarbage: true, } const expected = { default: { something: { nested: 'here', }, }, defaultSecure: { foo: 'bar', }, localState: {}, } expect(chrome._mergeChromePreferences(originalPrefs, newPrefs)).to.deep.eq(expected) }) }) context('#_writeChromePreferences', () => { it('writes json as expected', () => { const outputJson = sinon.stub(fs, 'outputJson') const defaultPrefs = outputJson.withArgs('/foo/Default/Preferences').resolves() const securePrefs = outputJson.withArgs('/foo/Default/Secure Preferences').resolves() const statePrefs = outputJson.withArgs('/foo/Local State').resolves() const originalPrefs = { default: {}, defaultSecure: { foo: 'bar', deleteThis: 'nephew', }, localState: {}, } const newPrefs = chrome._mergeChromePreferences(originalPrefs, { default: { something: { nested: 'here', }, }, defaultSecure: { deleteThis: null, }, someGarbage: true, }) expect(chrome._writeChromePreferences('/foo', originalPrefs, newPrefs)).to.eventually.equal() .then(() => { expect(defaultPrefs).to.be.calledWith('/foo/Default/Preferences', { something: { nested: 'here', }, }) expect(securePrefs).to.be.calledWith('/foo/Default/Secure Preferences', { foo: 'bar', }) // no changes were made expect(statePrefs).to.not.be.called }) }) }) })