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 cy.visit slowness by removing Electron timers workaround #4385

Merged
merged 28 commits into from
Jun 17, 2019
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3d82220
add tests for getAddress on localhost
flotwig Jun 3, 2019
925efeb
add test for visit resolving quickly
flotwig Jun 3, 2019
5f678a2
add debug logs to network
flotwig Jun 4, 2019
768381b
network: override addRequest in HttpAgent, not createSocket
flotwig Jun 4, 2019
79edfa5
don't need to add connection: keep-alive to all responses
flotwig Jun 5, 2019
6083e1e
test that visit finishes quickly
flotwig Jun 5, 2019
600c9c2
do not forward connection header
flotwig Jun 5, 2019
108d6b6
add tests to help reproduce
flotwig Jun 10, 2019
625d4ec
cleanup
flotwig Jun 10, 2019
5d67a97
still send keep-alive
flotwig Jun 10, 2019
07a8258
update tests
flotwig Jun 10, 2019
b9d65a4
remove timers
flotwig Jun 10, 2019
208775a
add snapshots to e2e test
flotwig Jun 10, 2019
ebe4310
remove tests for debugging
flotwig Jun 10, 2019
9c8337e
try making console writes async
flotwig Jun 10, 2019
93f15fc
Revert "remove timers"
flotwig Jun 11, 2019
aee8a72
allow DEBUG_COLORS to be passed in an e2e test
flotwig Jun 11, 2019
c51b395
try: using system node for the timers, setting up our own IPC
flotwig Jun 11, 2019
d507f52
Revert "try: using system node for the timers, setting up our own IPC"
flotwig Jun 11, 2019
1b14a1b
put the interesting test first
flotwig Jun 11, 2019
8b82f0e
use electron-mocha to run tests in electron
flotwig Jun 11, 2019
ae439aa
Revert "use electron-mocha to run tests in electron"
flotwig Jun 11, 2019
890e38a
support legacy addRequest invocation
flotwig Jun 11, 2019
3f077f8
Revert "Revert "remove timers""
flotwig Jun 11, 2019
2858a5b
update snapshot
flotwig Jun 11, 2019
47860ca
Revert "try making console writes async"
flotwig Jun 12, 2019
8e63476
Merge remote-tracking branch 'origin/develop' into issue-4313
flotwig Jun 12, 2019
bbff24d
complete in 150ms, not 1000ms
flotwig Jun 17, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 8 additions & 5 deletions packages/driver/src/cy/commands/navigation.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -577,15 +577,19 @@ module.exports = (Commands, Cypress, cy, state, config) ->

$utils.iframeSrc($autIframe, url)

onLoad = ({runOnLoadCallback}) ->
onLoad = ({runOnLoadCallback, totalTime}) ->
## reset window on load
win = state("window")

## the onLoad callback should only be skipped if specified
if runOnLoadCallback isnt false
options.onLoad?.call(runnable.ctx, win)

options._log.set({url: url}) if options._log
if options._log
options._log.set({
url
totalTime
})

return Promise.resolve(win)

Expand Down Expand Up @@ -680,7 +684,8 @@ module.exports = (Commands, Cypress, cy, state, config) ->
url = $Location.fullyQualifyUrl(url)

changeIframeSrc(url, "window:load")
.then(onLoad)
.then ->
onLoad(resp)
else
## if we've already visited a new superDomain
## then die else we'd be in a terrible endless loop
Expand Down Expand Up @@ -784,8 +789,6 @@ module.exports = (Commands, Cypress, cy, state, config) ->
go()

visit()
.then ->
state("window")
.timeout(options.timeout, "visit")
.catch Promise.TimeoutError, (err) =>
timedOutWaitingForPageLoad(options.timeout, options._log)
Expand Down
29 changes: 21 additions & 8 deletions packages/network/lib/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,18 @@ export class CombinedAgent {
}

// called by Node.js whenever a new request is made internally
addRequest(req: http.ClientRequest, options: http.RequestOptions) {
addRequest(req: http.ClientRequest, options: http.RequestOptions, port?: number, localAddress?: string) {
// Legacy API: addRequest(req, host, port, localAddress)
// https://github.com/nodejs/node/blob/cb68c04ce1bc4534b2d92bc7319c6ff6dda0180d/lib/_http_agent.js#L148-L155
if (typeof options === 'string') {
// @ts-ignore
options = {
host: options,
port: port!,
localAddress
}
}

const isHttps = isRequestHttps(options)

if (!options.href) {
Expand All @@ -156,11 +167,13 @@ export class CombinedAgent {
}
}

debug(`addRequest called for ${options.href}`)
debug('addRequest called %o', { isHttps, ..._.pick(options, 'href') })

return getFirstWorkingFamily(options, this.familyCache, (family: net.family) => {
options.family = family

debug('got family %o', _.pick(options, 'family', 'href'))

if (isHttps) {
return this.httpsAgent.addRequest(req, options)
}
Expand All @@ -180,22 +193,22 @@ class HttpAgent extends http.Agent {
this.httpsAgent = new https.Agent({ keepAlive: true })
}

createSocket (req: http.ClientRequest, options: http.RequestOptions, cb: http.SocketCallback) {
addRequest (req: http.ClientRequest, options: http.RequestOptions) {
if (process.env.HTTP_PROXY) {
const proxy = getProxyForUrl(options.href)

if (proxy) {
options.proxy = proxy

return this._createProxiedSocket(req, <RequestOptionsWithProxy>options, cb)
return this._addProxiedRequest(req, <RequestOptionsWithProxy>options)
}
}

super.createSocket(req, options, cb)
super.addRequest(req, options)
}

_createProxiedSocket (req: http.ClientRequest, options: RequestOptionsWithProxy, cb: http.SocketCallback) {
debug(`Creating proxied socket for ${options.href} through ${options.proxy}`)
_addProxiedRequest (req: http.ClientRequest, options: RequestOptionsWithProxy) {
debug(`Creating proxied request for ${options.href} through ${options.proxy}`)

const proxy = url.parse(options.proxy)

Expand Down Expand Up @@ -226,7 +239,7 @@ class HttpAgent extends http.Agent {
return this.httpsAgent.addRequest(req, options)
}

super.createSocket(req, options, cb)
super.addRequest(req, options)
}
}

Expand Down
6 changes: 6 additions & 0 deletions packages/network/lib/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export function byPortAndAddress (port: number, address: net.Address) {
}

export function getAddress (port: number, hostname: string) {
debug('beginning getAddress %o', { hostname, port })

const fn = byPortAndAddress.bind({}, port)

// promisify at the very last second which enables us to
Expand All @@ -35,9 +37,13 @@ export function getAddress (port: number, hostname: string) {
// @ts-ignore
return lookupAsync(hostname, { all: true })
.then((addresses: net.Address[]) => {
debug('got addresses %o', { hostname, port, addresses })
// convert to an array if string
return Array.prototype.concat.call(addresses).map(fn)
})
.tapCatch((err) => {
debug('error getting address', { hostname, port, err })
})
.any()
}

Expand Down
53 changes: 53 additions & 0 deletions packages/network/test/integration/connect_spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import _ from 'lodash'
import Bluebird from 'bluebird'
import chai from 'chai'
import net from 'net'
import sinonChai from 'sinon-chai'
import * as connect from '../../lib/connect'

const expect = chai.expect
chai.use(sinonChai)

describe('lib/connect', function() {
context('.getAddress', function() {
it('resolves localhost on 127.0.0.1 immediately', function() {
this.timeout(50)

const server = net.createServer(_.partialRight(_.invoke, 'close'))

return Bluebird.fromCallback(cb => {
server.listen(0, '127.0.0.1', cb)
})
.then(() => {
return connect.getAddress(server.address().port, 'localhost')
})
.then((address) => {
expect(address).to.deep.eq({
family: 4,
address: '127.0.0.1'
})
})
})

// Error: listen EADDRNOTAVAIL ::1
// TODO: add an ipv6 lo if to the docker container
it.skip('resolves localhost on ::1 immediately', function() {
this.timeout(50)

const server = net.createServer(_.partialRight(_.invoke, 'close'))

return Bluebird.fromCallback(cb => {
server.listen(0, '::1', cb)
})
.then(() => {
return connect.getAddress(server.address().port, 'localhost')
})
.then((address) => {
expect(address).to.deep.eq({
family: 6,
address: '::1'
})
})
})
})
})
1 change: 1 addition & 0 deletions packages/network/test/mocha.opts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
test/unit
test/integration
--compilers ts:@packages/ts/register
--timeout 10000
--recursive
1 change: 0 additions & 1 deletion packages/network/test/unit/agent_spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import Bluebird from 'bluebird'
import chai from 'chai'
import { EventEmitter } from 'events'
import http from 'http'
import https from 'https'
import net from 'net'
Expand Down
120 changes: 120 additions & 0 deletions packages/server/__snapshots__/6_visit_spec.coffee.js
Original file line number Diff line number Diff line change
Expand Up @@ -819,3 +819,123 @@ Error: ESOCKETTIMEDOUT


`

exports['e2e visit resolves visits quickly in chrome (headed) 1'] = `

====================================================================================================

(Run Starting)

┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Cypress: 1.2.3 │
│ Browser: FooBrowser 88 │
│ Specs: 1 found (fast_visit_spec.coffee) │
│ Searched: cypress/integration/fast_visit_spec.coffee │
└────────────────────────────────────────────────────────────────────────────────────────────────┘


────────────────────────────────────────────────────────────────────────────────────────────────────

Running: fast_visit_spec.coffee... (1 of 1)

Warning: Cypress can only record videos when using the built in 'electron' browser.

You have set the browser to: 'chrome'

A video will not be recorded when using this browser.


✓ normally finishes in less than XX:XX on localhost with connection: close
✓ normally finishes in less than XX:XX on localhost with connection: keep-alive

2 passing


(Results)

┌──────────────────────────────────────┐
│ Tests: 2 │
│ Passing: 2 │
│ Failing: 0 │
│ Pending: 0 │
│ Skipped: 0 │
│ Screenshots: 0 │
│ Video: false │
│ Duration: X seconds │
│ Spec Ran: fast_visit_spec.coffee │
└──────────────────────────────────────┘


====================================================================================================

(Run Finished)


Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ ✔ fast_visit_spec.coffee XX:XX 2 2 - - - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
All specs passed! XX:XX 2 2 - - -


`

exports['e2e visit resolves visits quickly in electron (headless) 1'] = `

====================================================================================================

(Run Starting)

┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Cypress: 1.2.3 │
│ Browser: FooBrowser 88 │
│ Specs: 1 found (fast_visit_spec.coffee) │
│ Searched: cypress/integration/fast_visit_spec.coffee │
└────────────────────────────────────────────────────────────────────────────────────────────────┘


────────────────────────────────────────────────────────────────────────────────────────────────────

Running: fast_visit_spec.coffee... (1 of 1)


✓ normally finishes in less than XX:XX on localhost with connection: close
✓ normally finishes in less than XX:XX on localhost with connection: keep-alive

2 passing


(Results)

┌──────────────────────────────────────┐
│ Tests: 2 │
│ Passing: 2 │
│ Failing: 0 │
│ Pending: 0 │
│ Skipped: 0 │
│ Screenshots: 0 │
│ Video: true │
│ Duration: X seconds │
│ Spec Ran: fast_visit_spec.coffee │
└──────────────────────────────────────┘


(Video)

- Started processing: Compressing to 32 CRF
- Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds)


====================================================================================================

(Run Finished)


Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ ✔ fast_visit_spec.coffee XX:XX 2 2 - - - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
All specs passed! XX:XX 2 2 - - -


`
6 changes: 0 additions & 6 deletions packages/server/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
// override tty if we're being forced to
require('./lib/util/tty').override()

// if we are running in electron
// we must hack around busted timers
if (process.versions.electron) {
require('./timers/parent').fix()
}

if (process.env.CY_NET_PROFILE && process.env.CYPRESS_ENV) {
const netProfiler = require('./lib/util/net_profiler')()

Expand Down
2 changes: 1 addition & 1 deletion packages/server/lib/controllers/proxy.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ module.exports = {
return if res.headersSent

## omit problematic headers
headers = _.omit incomingRes.headers, "set-cookie", "x-frame-options", "content-length", "content-security-policy"
headers = _.omit incomingRes.headers, "set-cookie", "x-frame-options", "content-length", "content-security-policy", "connection"

## do not cache when we inject content into responses
## later on we should switch to an etag system so we dont
Expand Down
5 changes: 0 additions & 5 deletions packages/server/lib/plugins/child/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
// if we are running in electron
// we must hack around busted timers
if (process.versions.electron) {
require('../../timers/parent').fix()
}
require('graceful-fs').gracefulify(require('fs'))
require('@packages/coffee/register')
require && require.extensions && delete require.extensions['.litcoffee']
Expand Down
4 changes: 4 additions & 0 deletions packages/server/lib/server.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,8 @@ class Server
options
})

startTime = new Date()

## if we have an existing url resolver
## in flight then cancel it
if @_urlResolver
Expand Down Expand Up @@ -452,6 +454,8 @@ class Server
## connection hangs before receiving a body.
debug("resolve:url response ended, setting buffer %o", { newUrl, details })

details.totalTime = new Date() - startTime

## TODO: think about moving this logic back into the
## frontend so that the driver can be in control of
## when the server should cache the request buffer
Expand Down