mirror of
https://github.com/cypress-io/cypress.git
synced 2026-03-13 12:59:07 -05:00
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:
@@ -38,5 +38,5 @@
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"types": "../ts/index.d.ts"
|
||||
"types": "./lib/types.ts"
|
||||
}
|
||||
|
||||
@@ -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 - - -
|
||||
|
||||
|
||||
`
|
||||
|
||||
@@ -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
|
||||
@@ -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')
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
])
|
||||
|
||||
Reference in New Issue
Block a user