Fix cypress run --headless (#5953)

* fix chrome headless

* update tests, load absolutely no extensions

* lol

* add e2e test for '--headless' flag

* add get:screenshots:taken

* add test for window bounds

* fix --headless test

* update snapshot

* ts'ify and document functions in chrome.ts

* properly export @packages/launcher types

* fix types

* assert on window bounds in headless spec

* add navigator.userAgent test

* only run in ci

* remove test of questionable value
This commit is contained in:
Zach Bloomquist
2019-12-16 13:10:12 -05:00
committed by GitHub
parent bb7252b291
commit c8b184be44
8 changed files with 123 additions and 45 deletions

View File

@@ -38,5 +38,5 @@
"files": [
"lib"
],
"types": "../ts/index.d.ts"
"types": "./lib/types.ts"
}

View File

@@ -19,16 +19,18 @@ exports['e2e headless / tests in headless mode pass'] = `
e2e headless spec
✓ has the expected values for Cypress.browser
✓ has expected HeadlessChrome useragent
✓ has expected launch args
1 passing
3 passing
(Results)
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Tests: 1
│ Passing: 1
│ Tests: 3
│ Passing: 3
│ Failing: 0 │
│ Pending: 0 │
│ Skipped: 0 │
@@ -52,9 +54,9 @@ exports['e2e headless / tests in headless mode pass'] = `
Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ ✔ headless_spec.js XX:XX 1 1 - - - │
│ ✔ headless_spec.js XX:XX 3 3 - - - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
✔ All specs passed! XX:XX 1 1 - - -
✔ All specs passed! XX:XX 3 3 - - -
`
@@ -80,16 +82,18 @@ exports['e2e headless / tests in headed mode pass [chrome]'] = `
e2e headless spec
✓ has the expected values for Cypress.browser
✓ has expected HeadlessChrome useragent
✓ has expected launch args
1 passing
3 passing
(Results)
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Tests: 1
│ Passing: 1
│ Tests: 3
│ Passing: 3
│ Failing: 0 │
│ Pending: 0 │
│ Skipped: 0 │
@@ -113,9 +117,9 @@ exports['e2e headless / tests in headed mode pass [chrome]'] = `
Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ ✔ headless_spec.js XX:XX 1 1 - - - │
│ ✔ headless_spec.js XX:XX 3 3 - - - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
✔ All specs passed! XX:XX 1 1 - - -
✔ All specs passed! XX:XX 3 3 - - -
`
@@ -147,16 +151,18 @@ A video will not be recorded when using this mode.
e2e headless spec
✓ has the expected values for Cypress.browser
✓ has expected HeadlessChrome useragent
✓ has expected launch args
1 passing
3 passing
(Results)
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Tests: 1
│ Passing: 1
│ Tests: 3
│ Passing: 3
│ Failing: 0 │
│ Pending: 0 │
│ Skipped: 0 │
@@ -174,9 +180,9 @@ A video will not be recorded when using this mode.
Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ ✔ headless_spec.js XX:XX 1 1 - - - │
│ ✔ headless_spec.js XX:XX 3 3 - - - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
✔ All specs passed! XX:XX 1 1 - - -
✔ All specs passed! XX:XX 3 3 - - -
`

View File

@@ -1,18 +1,30 @@
const _ = require('lodash')
const os = require('os')
const path = require('path')
const Promise = require('bluebird')
const la = require('lazy-ass')
const check = require('check-more-types')
const extension = require('@packages/extension')
const debug = require('debug')('cypress:server:browsers:chrome')
import _ from 'lodash'
import os from 'os'
import path from 'path'
import Promise from 'bluebird'
import la from 'lazy-ass'
import check from 'check-more-types'
import extension from '@packages/extension'
import { FoundBrowser } from '@packages/launcher'
import debugModule from 'debug'
import fs from '../util/fs'
import appData from '../util/app_data'
import utils from './utils'
import protocol from './protocol'
import { CdpAutomation } from './cdp_automation'
import * as CriClient from './cri-client'
// TODO: this is defined in `cypress-npm-api` but there is currently no way to get there
type CypressConfiguration = any
type Browser = FoundBrowser & {
isHeadless: boolean
isHeaded: boolean
}
const plugins = require('../plugins')
const fs = require('../util/fs')
const appData = require('../util/app_data')
const utils = require('./utils')
const protocol = require('./protocol')
const { CdpAutomation } = require('./cdp_automation')
const CriClient = require('./cri-client')
const debug = debugModule('cypress:server:browsers:chrome')
const LOAD_EXTENSION = '--load-extension='
const CHROME_VERSIONS_WITH_BUGGY_ROOT_LAYER_SCROLLING = '66 67'.split(' ')
@@ -118,7 +130,19 @@ const pluginsBeforeBrowserLaunch = function (browser, args) {
})
}
const _normalizeArgExtensions = function (dest, args) {
/**
* Merge the different `--load-extension` arguments into one.
*
* @param extPath path to Cypress extension
* @param args all browser args
* @param browser the current browser being launched
* @returns the modified list of arguments
*/
const _normalizeArgExtensions = function (extPath, args, browser: Browser): string[] {
if (browser.isHeadless) {
return args
}
let userExtensions
const loadExtension = _.find(args, (arg) => {
return arg.includes(LOAD_EXTENSION)
@@ -131,7 +155,7 @@ const _normalizeArgExtensions = function (dest, args) {
userExtensions = loadExtension.replace(LOAD_EXTENSION, '').split(',')
}
const extensions = [].concat(userExtensions, dest, pathToTheme)
const extensions = [].concat(userExtensions, extPath, pathToTheme)
args.push(LOAD_EXTENSION + _.compact(extensions).join(','))
@@ -171,6 +195,7 @@ const _disableRestorePagesPrompt = function (userDir) {
// After the browser has been opened, we can connect to
// its remote interface via a websocket.
const _connectToChromeRemoteInterface = function (port) {
// @ts-ignore
la(check.userPort(port), 'expected port number to connect CRI to', port)
debug('connecting to Chrome remote interface at random port %d', port)
@@ -206,6 +231,7 @@ const _maybeRecordVideo = (options) => {
// a utility function that navigates to the given URL
// once Chrome remote interface client is passed to it.
const _navigateUsingCRI = function (url) {
// @ts-ignore
la(check.url(url), 'missing url to navigate to', url)
return function (client) {
@@ -247,7 +273,7 @@ module.exports = {
_setAutomation,
_writeExtension (browser, options) {
_writeExtension (browser: Browser, options) {
if (browser.isHeadless) {
debug('chrome is running headlessly, not installing extension')
@@ -269,14 +295,14 @@ module.exports = {
})
},
_getArgs (options = {}) {
_getArgs (options: CypressConfiguration = {}) {
let ps; let ua
_.defaults(options, {
browser: {},
})
const args = [].concat(defaultArgs)
const args = ([] as string[]).concat(defaultArgs)
if (os.platform() === 'linux') {
args.push('--disable-gpu')
@@ -316,14 +342,10 @@ module.exports = {
args.push('--proxy-bypass-list=<-loopback>')
}
if (options.isHeadless) {
args.push('--headless')
}
return args
},
open (browser, url, options = {}, automation) {
open (browser: Browser, url, options: CypressConfiguration = {}, automation) {
const { isTextTerminal } = options
const userDir = utils.getProfileDir(browser, isTextTerminal)
@@ -332,6 +354,10 @@ module.exports = {
.try(() => {
const args = this._getArgs(options)
if (browser.isHeadless) {
args.push('--headless')
}
return getRemoteDebuggingPort()
.then((port) => {
args.push(`--remote-debugging-port=${port}`)
@@ -344,7 +370,7 @@ module.exports = {
port,
])
})
}).spread((cacheDir, args, port) => {
}).spread((cacheDir, args: string[], port) => {
return Promise.all([
this._writeExtension(
browser,
@@ -356,7 +382,7 @@ module.exports = {
.spread((extDest) => {
// normalize the --load-extensions argument by
// massaging what the user passed into our own
args = _normalizeArgExtensions(extDest, args)
args = _normalizeArgExtensions(extDest, args, browser)
// this overrides any previous user-data-dir args
// by being the last one

View File

@@ -157,7 +157,7 @@ module.exports = {
debug('debugger: received response to %s: %o', message, res)
res
.catch (err) ->
debug('debugger: received error on %s: %o', messsage, err)
debug('debugger: received error on %s: %o', message, err)
throw err
webContents.debugger.sendCommand('Browser.getVersion')

View File

@@ -8,6 +8,7 @@ describe('e2e headless', function () {
spec: 'headless_spec.js',
config: {
env: {
'CI': process.env.CI,
'EXPECT_HEADLESS': '1',
},
},
@@ -19,6 +20,11 @@ describe('e2e headless', function () {
// cypress run --headed
e2e.it('tests in headed mode pass', {
spec: 'headless_spec.js',
config: {
env: {
'CI': process.env.CI,
},
},
expectedExitCode: 0,
headed: true,
snapshot: true,

View File

@@ -5,4 +5,22 @@ describe('e2e headless spec', function () {
expect(Cypress.browser.isHeadless).to.eq(expectedHeadless)
expect(Cypress.browser.isHeaded).to.eq(!expectedHeadless)
})
it('has expected HeadlessChrome useragent', function () {
if (Cypress.browser.family !== 'chrome') {
return
}
cy.wrap(navigator.userAgent)
.should(expectedHeadless ? 'contain' : 'not.contain', 'HeadlessChrome')
})
it('has expected launch args', function () {
if (Cypress.browser.family !== 'chrome') {
return
}
cy.task('get:browser:args')
.should(expectedHeadless ? 'contain' : 'not.contain', '--headless')
})
})

View File

@@ -10,6 +10,9 @@ module.exports = (on) => {
// save some time by only reading the originals once
let cache = {}
const screenshotsTaken = []
let browserArgs = null
function getCachedImage (name) {
const cachedImage = cache[name]
@@ -24,6 +27,16 @@ module.exports = (on) => {
})
}
on('after:screenshot', (details) => {
screenshotsTaken.push(details)
})
on('before:browser:launch', (browser, args) => {
browserArgs = args
return args
})
on('task', {
'returns:undefined' () {},
@@ -122,5 +135,13 @@ module.exports = (on) => {
return performance.track('fast_visit_spec percentiles', data)
.return(null)
},
'get:screenshots:taken' () {
return screenshotsTaken
},
'get:browser:args' () {
return browserArgs
},
})
}

View File

@@ -70,16 +70,17 @@ describe "lib/browsers/chrome", ->
it "does not load extension in headless mode", ->
plugins.has.returns(false)
chrome._writeExtension.restore()
pathToTheme = extension.getPathToTheme()
chrome.open("chrome", "http://", { isHeadless: true, isHeaded: false }, @automation)
chrome.open({ isHeadless: true, isHeaded: false }, "http://", {}, @automation)
.then =>
args = utils.launch.firstCall.args[2]
expect(args).to.deep.eq([
"--headless"
"--remote-debugging-port=50505"
"--load-extension=/path/to/ext,#{pathToTheme}"
"--user-data-dir=/profile/dir"
"--disk-cache-dir=/profile/dir/CypressCache"
])