Skip to content

Commit

Permalink
fix: use stdio for CDP instead of TCP (#14348)
Browse files Browse the repository at this point in the history
  • Loading branch information
flotwig committed Mar 16, 2021
1 parent de8e6c3 commit d0d1736
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 58 deletions.
11 changes: 10 additions & 1 deletion packages/launcher/lib/browsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export function launch (
browser: FoundBrowser,
url: string,
args: string[] = [],
opts: { pipeStdio?: boolean } = {},
) {
log('launching browser %o', { browser, url })

Expand All @@ -120,7 +121,15 @@ export function launch (

log('spawning browser with args %o', { args })

const proc = cp.spawn(browser.path, args, { stdio: ['ignore', 'pipe', 'pipe'] })
const stdio = ['ignore', 'pipe', 'pipe']

if (opts.pipeStdio) {
// also pipe stdio 3 and 4 for access to debugger protocol
stdio.push('pipe', 'pipe')
}

// @ts-ignore
const proc = cp.spawn(browser.path, args, { stdio })

proc.stdout.on('data', (buf) => {
log('%s stdout: %s', browser.name, String(buf).trim())
Expand Down
132 changes: 100 additions & 32 deletions packages/server/lib/browsers/cri-client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Bluebird from 'bluebird'
import debugModule from 'debug'
import _ from 'lodash'
import { ChildProcess } from 'child_process'

const chromeRemoteInterface = require('chrome-remote-interface')
const errors = require('../errors')
Expand Down Expand Up @@ -85,41 +86,40 @@ const getMajorMinorVersion = (version: string): Version => {

const maybeDebugCdpMessages = (cri) => {
if (debugVerboseReceive.enabled) {
cri._ws.on('message', (data) => {
data = _
.chain(JSON.parse(data))
.tap((data) => {
([
'params.data', // screencast frame data
'result.data', // screenshot data
]).forEach((truncatablePath) => {
const str = _.get(data, truncatablePath)

if (!_.isString(str)) {
return
}
const handleMessage = cri._handleMessage

_.set(data, truncatablePath, _.truncate(str, {
length: 100,
omission: `... [truncated string of total bytes: ${str.length}]`,
}))
})
cri._handleMessage = (message) => {
const formatted = _.cloneDeep(message)

;([
'params.data', // screencast frame data
'result.data', // screenshot data
]).forEach((truncatablePath) => {
const str = _.get(formatted, truncatablePath)

if (!_.isString(str)) {
return
}

return data
_.set(formatted, truncatablePath, _.truncate(str, {
length: 100,
omission: `... [truncated string of total bytes: ${str.length}]`,
}))
})
.value()

debugVerboseReceive('received CDP message %o', data)
})
debugVerboseReceive('received CDP message %o', formatted)

return handleMessage.call(cri, message)
}
}

if (debugVerboseSend.enabled) {
const send = cri._ws.send
const send = cri._send

cri._ws.send = (data, callback) => {
cri._send = (data, callback) => {
debugVerboseSend('sending CDP command %o', JSON.parse(data))

return send.call(cri._ws, data, callback)
return send.call(cri, data, callback)
}
}
}
Expand All @@ -135,17 +135,36 @@ export { chromeRemoteInterface }

type DeferredPromise = { resolve: Function, reject: Function }

export const create = Bluebird.method((target: websocketUrl, onAsynchronousError: Function): Bluebird<CRIWrapper> => {
type CreateOpts = {
target?: websocketUrl
process?: ChildProcess
}

type Message = {
method: CRI.Command
params?: any
sessionId?: string
}

export const create = Bluebird.method((opts: CreateOpts, onAsynchronousError: Function): Bluebird<CRIWrapper> => {
const subscriptions: {eventName: CRI.EventName, cb: Function}[] = []
let enqueuedCommands: {command: CRI.Command, params: any, p: DeferredPromise }[] = []
let enqueuedCommands: {message: Message, params: any, p: DeferredPromise }[] = []

let closed = false // has the user called .close on this?
let connected = false // is this currently connected to CDP?

let cri
let client: CRIWrapper
let sessionId: string | undefined

const reconnect = () => {
if (opts.process) {
// reconnecting doesn't make sense for stdio
onAsynchronousError(errors.get('CDP_STDIO_ERROR'))

return
}

debug('disconnected, attempting to reconnect... %o', { closed })

connected = false
Expand All @@ -162,7 +181,7 @@ export const create = Bluebird.method((target: websocketUrl, onAsynchronousError
})

enqueuedCommands.forEach((cmd) => {
cri.send(cmd.command, cmd.params)
cri.sendRaw(cmd.message)
.then(cmd.p.resolve, cmd.p.reject)
})

Expand All @@ -176,10 +195,10 @@ export const create = Bluebird.method((target: websocketUrl, onAsynchronousError
const connect = () => {
cri?.close()

debug('connecting %o', { target })
debug('connecting %o', opts)

return chromeRemoteInterface({
target,
...opts,
local: true,
})
.then((newCri) => {
Expand All @@ -193,6 +212,46 @@ export const create = Bluebird.method((target: websocketUrl, onAsynchronousError

// @see https://github.com/cyrus-and/chrome-remote-interface/issues/72
cri._notifier.on('disconnect', reconnect)

if (opts.process) {
// if using stdio, we need to find the target before declaring the connection complete
return findTarget()
}

return
})
}

const findTarget = () => {
debug('finding CDP target...')

return new Bluebird<void>((resolve, reject) => {
const isAboutBlank = (target) => target.type === 'page' && target.url === 'about:blank'

const attachToTarget = _.once(({ targetId }) => {
debug('attaching to target %o', { targetId })
cri.send('Target.attachToTarget', {
targetId,
flatten: true, // enable selecting via sessionId
}).then((result) => {
debug('attached to target %o', result)
sessionId = result.sessionId
resolve()
}).catch(reject)
})

cri.send('Target.setDiscoverTargets', { discover: true })
.then(() => {
cri.on('Target.targetCreated', (target) => {
if (isAboutBlank(target)) {
attachToTarget(target)
}
})

return cri.send('Target.getTargets')
.then(({ targetInfos }) => targetInfos.filter(isAboutBlank).map(attachToTarget))
})
.catch(reject)
})
}

Expand Down Expand Up @@ -222,14 +281,23 @@ export const create = Bluebird.method((target: websocketUrl, onAsynchronousError
ensureMinimumProtocolVersion,
getProtocolVersion,
send: Bluebird.method((command: CRI.Command, params?: object) => {
const message: Message = {
method: command,
params,
}

if (sessionId) {
message.sessionId = sessionId
}

const enqueue = () => {
return new Bluebird((resolve, reject) => {
enqueuedCommands.push({ command, params, p: { resolve, reject } })
enqueuedCommands.push({ message, params, p: { resolve, reject } })
})
}

if (connected) {
return cri.send(command, params)
return cri.sendRaw(message)
.catch((err) => {
if (!WEBSOCKET_NOT_OPEN_RE.test(err.message)) {
throw err
Expand Down
7 changes: 7 additions & 0 deletions packages/server/lib/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const chalk = require('chalk')
const AU = require('ansi_up')
const Promise = require('bluebird')
const { stripIndent } = require('./util/strip_indent')
const humanTime = require('./util/human_time')

const ansi_up = new AU.default

Expand Down Expand Up @@ -871,6 +872,12 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) {
There was an error reconnecting to the Chrome DevTools protocol. Please restart the browser.
${arg1.stack}`
case 'CDP_STDIO_ERROR':
return 'The connection between Cypress and Chrome has unexpectedly ended. Please restart the browser.'
case 'CDP_STDIO_TIMEOUT':
return `Warning: Cypress failed to connect to ${arg1} via stdio after ${humanTime.long(arg2)}. Falling back to TCP...`
case 'CDP_FALLBACK_SUCCEEDED':
return `Connecting to ${arg1} via TCP was successful, continuing with tests.`
case 'CDP_RETRYING_CONNECTION':
return `Failed to connect to Chrome, retrying in 1 second (attempt ${chalk.yellow(arg1)}/62)`
case 'DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS':
Expand Down
2 changes: 1 addition & 1 deletion packages/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"chalk": "2.4.2",
"check-more-types": "2.24.0",
"chokidar": "3.2.2",
"chrome-remote-interface": "0.28.2",
"chrome-remote-interface": "cypress-io/chrome-remote-interface#147192810f29951cd96c5e406495e9b4d740ba95",
"cli-table3": "0.5.1",
"coffeescript": "1.12.7",
"color-string": "1.5.4",
Expand Down
Loading

0 comments on commit d0d1736

Please sign in to comment.