Skip to content

Commit 148a3e1

Browse files
authored
fix: use stdio for CDP instead of TCP (#14348)
1 parent da6ee21 commit 148a3e1

File tree

10 files changed

+365
-100
lines changed

10 files changed

+365
-100
lines changed

packages/launcher/lib/browsers.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ export function launch (
9797
browser: FoundBrowser,
9898
url: string,
9999
args: string[] = [],
100+
opts: { pipeStdio?: boolean } = {},
100101
) {
101102
log('launching browser %o', { browser, url })
102103

@@ -110,7 +111,15 @@ export function launch (
110111

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

113-
const proc = cp.spawn(browser.path, args, { stdio: ['ignore', 'pipe', 'pipe'] })
114+
const stdio = ['ignore', 'pipe', 'pipe']
115+
116+
if (opts.pipeStdio) {
117+
// also pipe stdio 3 and 4 for access to debugger protocol
118+
stdio.push('pipe', 'pipe')
119+
}
120+
121+
// @ts-ignore
122+
const proc = cp.spawn(browser.path, args, { stdio })
114123

115124
proc.stdout.on('data', (buf) => {
116125
log('%s stdout: %s', browser.name, String(buf).trim())

packages/server/__snapshots__/5_cdp_spec.ts.js

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
exports['e2e cdp / handles disconnections as expected'] = `
1+
exports['e2e cdp / with TCP transport / handles disconnections as expected'] = `
22
33
====================================================================================================
44
@@ -59,4 +59,67 @@ Error: connect ECONNREFUSED 127.0.0.1:7777
5959
✖ 1 of 1 failed (100%) XX:XX - - 1 - -
6060
6161
62-
`
62+
`
63+
64+
exports['e2e cdp / with stdio transport / falls back to connecting via tcp when stdio cannot be connected'] = `
65+
66+
====================================================================================================
67+
68+
(Run Starting)
69+
70+
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
71+
│ Cypress: 1.2.3 │
72+
│ Browser: FooBrowser 88 │
73+
│ Specs: 1 found (spec.ts) │
74+
│ Searched: cypress/integration/spec.ts │
75+
└────────────────────────────────────────────────────────────────────────────────────────────────┘
76+
77+
78+
────────────────────────────────────────────────────────────────────────────────────────────────────
79+
80+
Running: spec.ts (1 of 1)
81+
Warning: Cypress failed to connect to Chrome via stdio after 1 second. Falling back to TCP...
82+
Connecting to Chrome via TCP was successful, continuing with tests.
83+
84+
85+
passes
86+
✓ passes
87+
88+
89+
1 passing
90+
91+
92+
(Results)
93+
94+
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
95+
│ Tests: 1 │
96+
│ Passing: 1 │
97+
│ Failing: 0 │
98+
│ Pending: 0 │
99+
│ Skipped: 0 │
100+
│ Screenshots: 0 │
101+
│ Video: true │
102+
│ Duration: X seconds │
103+
│ Spec Ran: spec.ts │
104+
└────────────────────────────────────────────────────────────────────────────────────────────────┘
105+
106+
107+
(Video)
108+
109+
- Started processing: Compressing to 32 CRF
110+
- Finished processing: /XXX/XXX/XXX/cypress/videos/spec.ts.mp4 (X second)
111+
112+
113+
====================================================================================================
114+
115+
(Run Finished)
116+
117+
118+
Spec Tests Passing Failing Pending Skipped
119+
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
120+
│ ✔ spec.ts XX:XX 1 1 - - - │
121+
└────────────────────────────────────────────────────────────────────────────────────────────────┘
122+
✔ All specs passed! XX:XX 1 1 - - -
123+
124+
125+
`

packages/server/lib/browsers/chrome.ts

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import * as CriClient from './cri-client'
1313
import * as protocol from './protocol'
1414
import utils from './utils'
1515
import { Browser } from './types'
16+
import errors from '../errors'
1617

1718
// TODO: this is defined in `cypress-npm-api` but there is currently no way to get there
1819
type CypressConfiguration = any
@@ -35,6 +36,9 @@ type ChromePreferences = {
3536
localState: object
3637
}
3738

39+
const staticCdpPort = Number(process.env.CYPRESS_REMOTE_DEBUGGING_PORT)
40+
const stdioTimeoutMs = Number(process.env.CYPRESS_CDP_TARGET_TIMEOUT) || 60000
41+
3842
const pathToExtension = extension.getPathToExtension()
3943
const pathToTheme = extension.getPathToTheme()
4044

@@ -171,9 +175,7 @@ const _writeChromePreferences = (userDir: string, originalPrefs: ChromePreferenc
171175
}
172176

173177
const getRemoteDebuggingPort = async () => {
174-
const port = Number(process.env.CYPRESS_REMOTE_DEBUGGING_PORT)
175-
176-
return port || utils.getPort()
178+
return staticCdpPort || utils.getPort()
177179
}
178180

179181
/**
@@ -241,19 +243,47 @@ const _disableRestorePagesPrompt = function (userDir) {
241243
.catch(() => { })
242244
}
243245

246+
const useStdioCdp = (browser) => {
247+
return (
248+
// CDP via stdio doesn't seem to work in browsers older than 72
249+
// @see https://github.com/cyrus-and/chrome-remote-interface/issues/381#issuecomment-445277683
250+
browser.majorVersion >= 72
251+
// allow users to force TCP by specifying a port in environment
252+
&& !staticCdpPort
253+
)
254+
}
255+
244256
// After the browser has been opened, we can connect to
245257
// its remote interface via a websocket.
246-
const _connectToChromeRemoteInterface = function (port, onError) {
247-
// @ts-ignore
248-
la(check.userPort(port), 'expected port number to connect CRI to', port)
258+
const _connectToChromeRemoteInterface = function (browser, process, port, onError) {
259+
const connectTcp = () => {
260+
// @ts-ignore
261+
la(check.userPort(port), 'expected port number to connect CRI to', port)
262+
263+
debug('connecting to Chrome remote interface at random port %d', port)
264+
265+
return protocol.getWsTargetFor(port)
266+
.then((wsUrl) => {
267+
debug('received wsUrl %s for port %d', wsUrl, port)
268+
269+
return CriClient.create({ target: wsUrl }, onError)
270+
})
271+
}
272+
273+
if (!useStdioCdp(browser)) {
274+
return connectTcp()
275+
}
276+
277+
return CriClient.create({ process }, onError)
278+
.timeout(stdioTimeoutMs)
279+
.catch(Bluebird.TimeoutError, async () => {
280+
errors.warning('CDP_STDIO_TIMEOUT', browser.displayName, stdioTimeoutMs)
249281

250-
debug('connecting to Chrome remote interface at random port %d', port)
282+
const client = await connectTcp()
251283

252-
return protocol.getWsTargetFor(port)
253-
.then((wsUrl) => {
254-
debug('received wsUrl %s for port %d', wsUrl, port)
284+
errors.warning('CDP_FALLBACK_SUCCEEDED', browser.displayName)
255285

256-
return CriClient.create(wsUrl, onError)
286+
return client
257287
})
258288
}
259289

@@ -405,6 +435,10 @@ export = {
405435
args.push(`--remote-debugging-port=${port}`)
406436
args.push('--remote-debugging-address=127.0.0.1')
407437

438+
if (useStdioCdp(browser)) {
439+
args.push('--remote-debugging-pipe')
440+
}
441+
408442
return args
409443
},
410444

@@ -460,14 +494,16 @@ export = {
460494
// first allows us to connect the remote interface,
461495
// start video recording and then
462496
// we will load the actual page
463-
const launchedBrowser = await utils.launch(browser, 'about:blank', args)
497+
const launchedBrowser = await utils.launch(browser, 'about:blank', args, {
498+
pipeStdio: useStdioCdp(browser),
499+
})
464500

465501
la(launchedBrowser, 'did not get launched browser instance')
466502

467503
// SECOND connect to the Chrome remote interface
468504
// and when the connection is ready
469505
// navigate to the actual url
470-
const criClient = await this._connectToChromeRemoteInterface(port, options.onError)
506+
const criClient = await this._connectToChromeRemoteInterface(browser, launchedBrowser, port, options.onError)
471507

472508
la(criClient, 'expected Chrome remote interface reference', criClient)
473509

0 commit comments

Comments
 (0)