Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix cypress run --headless #5953

Merged
merged 16 commits into from
Dec 16, 2019
Merged
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"
}
39 changes: 24 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,19 @@ 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
✓ has expected window bounds in CI


1 passing
4 passing


(Results)

┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Tests: 1
│ Passing: 1
│ Tests: 4
│ Passing: 4
│ Failing: 0 │
│ Pending: 0 │
│ Skipped: 0 │
Expand All @@ -52,9 +55,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 4 4 - - - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
✔ All specs passed! XX:XX 1 1 - - -
✔ All specs passed! XX:XX 4 4 - - -


`
Expand All @@ -80,16 +83,19 @@ 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
✓ has expected window bounds in CI


1 passing
4 passing


(Results)

┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Tests: 1
│ Passing: 1
│ Tests: 4
│ Passing: 4
│ Failing: 0 │
│ Pending: 0 │
│ Skipped: 0 │
Expand All @@ -113,9 +119,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 4 4 - - - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
✔ All specs passed! XX:XX 1 1 - - -
✔ All specs passed! XX:XX 4 4 - - -


`
Expand Down Expand Up @@ -147,16 +153,19 @@ 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
✓ has expected window bounds in CI


1 passing
4 passing


(Results)

┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Tests: 1
│ Passing: 1
│ Tests: 4
│ Passing: 4
│ Failing: 0 │
│ Pending: 0 │
│ Skipped: 0 │
Expand All @@ -174,9 +183,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 4 4 - - - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
✔ All specs passed! XX:XX 1 1 - - -
✔ All specs passed! XX:XX 4 4 - - -


`
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)
flotwig marked this conversation as resolved.
Show resolved Hide resolved

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
@@ -1,8 +1,62 @@
const { _ } = Cypress
const expectedHeadless = !!Cypress.env('EXPECT_HEADLESS')

describe('e2e headless spec', function () {
it('has the expected values for Cypress.browser', 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')
})

it('has expected window bounds in CI', function () {
if (!Cypress.env('CI')) {
// will only work in the CI docker container, so skip it if we're not in CI
return this.skip()
}

if (Cypress.browser.family !== 'chrome') {
// Browser.getWindowForTarget does not exist in Electron
return
}

// in CI with Xvfb, Cypress will fill the entire screen, this test is to explicitly
// assert on the expected dimensions

const cdp = _.bind(Cypress.automation, Cypress, 'remote:debugger:protocol')

return cdp({
command: 'Browser.getWindowForTarget',
})
.then(({ windowId }) => {
return cdp({
command: 'Browser.getWindowBounds',
params: {
windowId,
},
})
})
.then(({ bounds }) => {
expect(bounds).to.include({
width: expectedHeadless ? 800 : 1050,
height: expectedHeadless ? 600 : 1004,
flotwig marked this conversation as resolved.
Show resolved Hide resolved
})
})
})
})
Loading