Skip to content

Commit

Permalink
Fix cypress run --headless (#5953)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
flotwig authored Dec 16, 2019
1 parent bb7252b commit c8b184b
Show file tree
Hide file tree
Showing 8 changed files with 123 additions and 45 deletions.
2 changes: 1 addition & 1 deletion packages/launcher/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,5 @@
"files": [
"lib"
],
"types": "../ts/index.d.ts"
"types": "./lib/types.ts"
}
36 changes: 21 additions & 15 deletions packages/server/__snapshots__/2_headless_spec.ts.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 │
Expand All @@ -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 - - -
`
Expand All @@ -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 │
Expand All @@ -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 - - -
`
Expand Down Expand Up @@ -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 │
Expand All @@ -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 - - -
`
Original file line number Diff line number Diff line change
@@ -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(' ')
Expand Down Expand Up @@ -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)
Expand All @@ -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(','))

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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')

Expand All @@ -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')
Expand Down Expand Up @@ -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)
Expand All @@ -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}`)
Expand All @@ -344,7 +370,7 @@ module.exports = {
port,
])
})
}).spread((cacheDir, args, port) => {
}).spread((cacheDir, args: string[], port) => {
return Promise.all([
this._writeExtension(
browser,
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/server/lib/browsers/electron.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
6 changes: 6 additions & 0 deletions packages/server/test/e2e/2_headless_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ describe('e2e headless', function () {
spec: 'headless_spec.js',
config: {
env: {
'CI': process.env.CI,
'EXPECT_HEADLESS': '1',
},
},
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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')
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand All @@ -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' () {},

Expand Down Expand Up @@ -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
},
})
}
5 changes: 3 additions & 2 deletions packages/server/test/unit/browsers/chrome_spec.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -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"
])
Expand Down

4 comments on commit c8b184b

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on c8b184b Dec 16, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the linux x64 version of the Test Runner.

You can install this pre-release platform-specific build using instructions at https://on.cypress.io/installing-cypress#Install-pre-release-version.

You will need to use custom CYPRESS_INSTALL_BINARY url and install Cypress using an url instead of the version.

export CYPRESS_INSTALL_BINARY=https://cdn.cypress.io/beta/binary/3.8.1/linux-x64/circle-develop-c8b184be44dd5e1fb1c2b8eb4570a8903ac3f8f5-216314/cypress.zip
npm install https://cdn.cypress.io/beta/npm/3.8.1/circle-develop-c8b184be44dd5e1fb1c2b8eb4570a8903ac3f8f5-216302/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on c8b184b Dec 16, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AppVeyor has built the win32 ia32 version of the Test Runner.

You can install this pre-release platform-specific build using instructions at https://on.cypress.io/installing-cypress#Install-pre-release-version.

You will need to use custom CYPRESS_INSTALL_BINARY url and install Cypress using an url instead of the version.

set CYPRESS_INSTALL_BINARY=https://cdn.cypress.io/beta/binary/3.8.1/win32-ia32/appveyor-develop-c8b184be44dd5e1fb1c2b8eb4570a8903ac3f8f5-29576886/cypress.zip
npm install https://cdn.cypress.io/beta/binary/3.8.1/win32-ia32/appveyor-develop-c8b184be44dd5e1fb1c2b8eb4570a8903ac3f8f5-29576886/cypress.zip

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on c8b184b Dec 16, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AppVeyor has built the win32 x64 version of the Test Runner.

You can install this pre-release platform-specific build using instructions at https://on.cypress.io/installing-cypress#Install-pre-release-version.

You will need to use custom CYPRESS_INSTALL_BINARY url and install Cypress using an url instead of the version.

set CYPRESS_INSTALL_BINARY=https://cdn.cypress.io/beta/binary/3.8.1/win32-x64/appveyor-develop-c8b184be44dd5e1fb1c2b8eb4570a8903ac3f8f5-29576886/cypress.zip
npm install https://cdn.cypress.io/beta/binary/3.8.1/win32-x64/appveyor-develop-c8b184be44dd5e1fb1c2b8eb4570a8903ac3f8f5-29576886/cypress.zip

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on c8b184b Dec 16, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the darwin x64 version of the Test Runner.

You can install this pre-release platform-specific build using instructions at https://on.cypress.io/installing-cypress#Install-pre-release-version.

You will need to use custom CYPRESS_INSTALL_BINARY url and install Cypress using an url instead of the version.

export CYPRESS_INSTALL_BINARY=https://cdn.cypress.io/beta/binary/3.8.1/darwin-x64/circle-develop-c8b184be44dd5e1fb1c2b8eb4570a8903ac3f8f5-216381/cypress.zip
npm install https://cdn.cypress.io/beta/npm/3.8.1/circle-develop-c8b184be44dd5e1fb1c2b8eb4570a8903ac3f8f5-216315/cypress.tgz

Please sign in to comment.