Skip to content

Commit

Permalink
Merge branch 'develop' into win-pipe-2181
Browse files Browse the repository at this point in the history
  • Loading branch information
bahmutov authored Sep 19, 2019
2 parents 7dd8ba3 + 9f717fe commit b44c644
Show file tree
Hide file tree
Showing 48 changed files with 2,243 additions and 1,836 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@
</a>
<a href="https://gitter.im/cypress-io/cypress">
<img src="https://img.shields.io/gitter/room/cypress-io/cypress.svg" alt="Gitter chat"/>
</a><br />
<a href="https://opencollective.com/cypressio">
<object type="image/svg+xml" data="https://opencollective.com/cypressio/tiers/corporate-backer.svg?avatarHeight=64&width=600" />
</a>
<a href="https://stackshare.io/cypress">
<img src="https://img.stackshare.io/misc/follow-on-stackshare-badge.svg" alt="StackShare"/>
</a><br />
</p>

## What is Cypress?
Expand Down
2 changes: 1 addition & 1 deletion cli/lib/exec/xvfb.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const { throwFormErrorText, errors } = require('../errors')
const util = require('../util')

const xvfb = Promise.promisifyAll(new Xvfb({
timeout: 5000, // milliseconds
timeout: 30000, // milliseconds
onStderrData (data) {
if (debugXvfb.enabled) {
debugXvfb(data.toString())
Expand Down
5 changes: 5 additions & 0 deletions cli/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2349,6 +2349,11 @@ declare namespace Cypress {
* })
*/
auth: Auth

/**
* Query parameters to append to the `url` of the request.
*/
qs: object
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/desktop-gui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"devDependencies": {
"@cypress/icons": "0.7.0",
"@cypress/json-schemas": "5.32.2",
"@cypress/react-tooltip": "0.5.2",
"@cypress/react-tooltip": "0.5.3",
"bin-up": "1.2.0",
"bluebird": "3.5.3",
"bootstrap-sass": "3.4.1",
Expand Down
8 changes: 7 additions & 1 deletion packages/driver/src/cy/commands/navigation.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,9 @@ module.exports = (Commands, Cypress, cy, state, config) ->
onLoad: ->
})

if !_.isUndefined(options.qs) and not _.isObject(options.qs)
$utils.throwErrByPath("visit.invalid_qs", { args: { qs: String(options.qs) }})

if options.retryOnStatusCodeFailure and not options.failOnStatusCode
$utils.throwErrByPath("visit.status_code_flags_invalid")

Expand Down Expand Up @@ -550,6 +553,9 @@ module.exports = (Commands, Cypress, cy, state, config) ->
if baseUrl = config("baseUrl")
url = $Location.qualifyWithBaseUrl(baseUrl, url)

if qs = options.qs
url = $Location.mergeUrlWithParams(url, qs)

cleanup = null

## clear the current timeout
Expand Down Expand Up @@ -610,7 +616,7 @@ module.exports = (Commands, Cypress, cy, state, config) ->
existing = $utils.locExisting()

## TODO: $Location.resolve(existing.origin, url)

if $Location.isLocalFileUrl(url)
return specifyFileByRelativePath(url, options._log)

Expand Down
3 changes: 2 additions & 1 deletion packages/driver/src/cypress/error_messages.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -998,6 +998,7 @@ module.exports = {
invalid_1st_arg: "#{cmd('visit')} must be called with a URL or an options object containing a URL as its 1st argument"
invalid_method: "#{cmd('visit')} was called with an invalid method: '{{method}}'. Method can only be GET or POST."
invalid_headers: "#{cmd('visit')} requires the 'headers' option to be an object."
invalid_qs: "#{cmd('visit')} requires the 'qs' option to be an object, but received: '{{qs}}'"
no_duplicate_url: """
#{cmd('visit')} must be called with only one URL. You specified two URLs:
Expand Down Expand Up @@ -1090,7 +1091,7 @@ module.exports = {
#{cmd('request')} will automatically get and set cookies and enable you to parse responses.
"""

specify_file_by_relative_path: """
#{cmd('visit')} failed because the 'file://...' protocol is not supported by Cypress.
Expand Down
5 changes: 5 additions & 0 deletions packages/driver/src/cypress/location.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,11 @@ class $Location
url = new UrlParse(url, existing.origin)
url.toString()

@mergeUrlWithParams = (url, params) ->
url = new UrlParse(url, null, true)
url.set("query", _.merge(url.query || {}, params))
url.toString()

@normalize = (url) ->
## A properly formed URL will always have a trailing
## slash at the end of it
Expand Down
2 changes: 1 addition & 1 deletion packages/driver/src/dom/elements.js
Original file line number Diff line number Diff line change
Expand Up @@ -707,7 +707,7 @@ const getContainsSelector = (text, filter = '') => {
const filters = filter.trim().split(',')

const selectors = _.map(filters, (filter) => {
return `${filter}:not(script):contains('${escapedText}'), ${filter}[type='submit'][value~='${escapedText}']`
return `${filter}:not(script,style):contains('${escapedText}'), ${filter}[type='submit'][value~='${escapedText}']`
})

return selectors.join()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -543,11 +543,11 @@ describe "src/cy/commands/navigation", ->

it "does not support file:// protocol", (done) ->
Cypress.config("baseUrl", "")

cy.on "fail", (err) ->
expect(err.message).to.contain("cy.visit() failed because the 'file://...' protocol is not supported by Cypress.")
done()

cy.visit("file:///cypress/fixtures/generic.html")

## https://github.com/cypress-io/cypress/issues/1727
Expand Down Expand Up @@ -619,6 +619,15 @@ describe "src/cy/commands/navigation", ->
})
cy.contains('"user-agent":"something special"')

it "can send querystring params", ->
qs = { "foo bar": "baz quux" }

cy
.visit("http://localhost:3500/dump-qs", { qs })
.then ->
cy.contains(JSON.stringify(qs))
cy.url().should('eq', 'http://localhost:3500/dump-qs?foo%20bar=baz%20quux')

describe "can send a POST request", ->
it "automatically urlencoded using an object body", ->
cy.visit("http://localhost:3500/post-only", {
Expand Down Expand Up @@ -1060,6 +1069,23 @@ describe "src/cy/commands/navigation", ->
headers: "quux"
})

[
"foo",
null,
false,
].forEach (qs) =>
str = String(qs)

it "throws when qs is #{str}", (done) ->
cy.on "fail", (err) ->
expect(err.message).to.contain "cy.visit() requires the 'qs' option to be an object, but received: '#{str}'"
done()

cy.visit({
url: "http://foobarbaz",
qs
})

it "throws when failOnStatusCode is false and retryOnStatusCodeFailure is true", (done) ->
cy.on "fail", (err) ->
expect(err.message).to.contain "cy.visit() was invoked with { failOnStatusCode: false, retryOnStatusCodeFailure: true }."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1195,6 +1195,14 @@ describe "src/cy/commands/querying", ->
cy.contains("DOM Fixture").then ($el) ->
expect($el).not.to.match("title")

it 'will not find script elements', ->
cy.$$('<script>// some-script-content </script>').appendTo(cy.$$('body'))
cy.contains('some-script-content').should('not.match', 'script')

it 'will not find style elements', ->
cy.$$('<style> some-style-content {} </style>').appendTo(cy.$$('body'))
cy.contains('some-style-content').should('not.match', 'style')

it "finds the nearest element by :contains selector", ->
cy.contains("li 0").then ($el) ->
expect($el.length).to.eq(1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,21 @@ describe "src/cypress/location", ->
obj = Location.create(urls.signin)
expect(obj.toString()).to.eq("http://localhost:2020/signin")

context ".mergeUrlWithParams", ->
beforeEach ->
@url = (str, expected, params) ->
url = Location.mergeUrlWithParams(str, params)
expect(url).to.eq(expected)

it "merges params into a URL", ->
@url "http://example.com/a", "http://example.com/a?foo=bar", { foo: 'bar' }

it "overrides existing queryparams", ->
@url "http://example.com/a?foo=quux", "http://example.com/a?foo=bar", { foo: 'bar' }

it "appends and overrides existing queryparams", ->
@url "http://example.com/a?foo=quux", "http://example.com/a?foo=bar&baz=quuz", { foo: 'bar', baz: 'quuz' }

context ".normalize", ->
beforeEach ->
@url = (source, expected) ->
Expand Down

This file was deleted.

35 changes: 35 additions & 0 deletions packages/driver/test/cypress/integration/cypress/runner_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const pending = []

Cypress.on('test:after:run', (test) => {
if (test.state === 'pending') {
return pending.push(test)
}
})

describe('src/cypress/runner', () => {
it('handles "double quotes" in test name', (done) => {
cy.once('log:added', (log) => {
expect(log.hookName).to.equal('test')

return done()
})

return cy.wrap({})
})

context('pending tests', () => {
it('is not pending', () => {})

it('is pending 1')

it('is pending 2')

it('has 2 pending tests', () => {
expect(pending).to.have.length(2)

expect(pending[0].title).to.eq('is pending 1')

expect(pending[1].title).to.eq('is pending 2')
})
})
})
3 changes: 3 additions & 0 deletions packages/driver/test/support/server.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ niv.install("[email protected]")
app.all '/dump-method', (req, res) ->
res.send("<html><body>request method: #{req.method}</body></html>")

app.all '/dump-qs', (req, res) ->
res.send("<html><body>it worked!<br>request querystring:<br>#{JSON.stringify(req.query)}</body></html>")

app.post '/post-only', (req, res) ->
res.send("<html><body>it worked!<br>request body:<br>#{JSON.stringify(req.body)}</body></html>")

Expand Down
60 changes: 30 additions & 30 deletions packages/https-proxy/lib/ca.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -112,22 +112,35 @@ ServerExtensions = [{
}]

class CA
constructor: (caFolder) ->
if not caFolder
caFolder = path.join(os.tmpdir(), 'cy-ca')

@baseCAFolder = caFolder
@certsFolder = path.join(@baseCAFolder, "certs")
@keysFolder = path.join(@baseCAFolder, "keys")

randomSerialNumber: ->
## generate random 16 bytes hex string
sn = ""
removeAll: ->
fs
.removeAsync(@baseCAFolder)
.catchReturn({ code: "ENOENT" })

for i in [1..4]
sn += ("00000000" + Math.floor(Math.random()*Math.pow(256, 4)).toString(16)).slice(-8)
randomSerialNumber: ->
## generate random 16 bytes hex string
sn = ""

sn
for i in [1..4]
sn += ("00000000" + Math.floor(Math.random()*Math.pow(256, 4)).toString(16)).slice(-8)

sn

generateCA: ->
generateKeyPairAsync({bits: 512})
.then (keys) =>
cert = pki.createCertificate()
cert.publicKey = keys.publicKey
cert.serialNumber = @randomSerialNumber()

cert.validity.notBefore = new Date()
cert.validity.notAfter = new Date()
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 10)
Expand All @@ -140,9 +153,9 @@ class CA
@CAkeys = keys

Promise.all([
fs.writeFileAsync(path.join(@certsFolder, "ca.pem"), pki.certificateToPem(cert))
fs.writeFileAsync(path.join(@keysFolder, "ca.private.key"), pki.privateKeyToPem(keys.privateKey))
fs.writeFileAsync(path.join(@keysFolder, "ca.public.key"), pki.publicKeyToPem(keys.publicKey))
fs.outputFileAsync(path.join(@certsFolder, "ca.pem"), pki.certificateToPem(cert))
fs.outputFileAsync(path.join(@keysFolder, "ca.private.key"), pki.privateKeyToPem(keys.privateKey))
fs.outputFileAsync(path.join(@keysFolder, "ca.public.key"), pki.publicKeyToPem(keys.publicKey))
])

loadCA: ->
Expand Down Expand Up @@ -198,9 +211,9 @@ class CA
dest = mainHost.replace(asterisksRe, "_")

Promise.all([
fs.writeFileAsync(path.join(@certsFolder, dest + ".pem"), certPem)
fs.writeFileAsync(path.join(@keysFolder, dest + ".key"), keyPrivatePem)
fs.writeFileAsync(path.join(@keysFolder, dest + ".public.key"), keyPublicPem)
fs.outputFileAsync(path.join(@certsFolder, dest + ".pem"), certPem)
fs.outputFileAsync(path.join(@keysFolder, dest + ".key"), keyPrivatePem)
fs.outputFileAsync(path.join(@keysFolder, dest + ".public.key"), keyPublicPem)
])
.return([certPem, keyPrivatePem])

Expand All @@ -216,25 +229,12 @@ class CA
path.join(@certsFolder, "ca.pem")

@create = (caFolder) ->
ca = new CA
ca = new CA(caFolder)

if not caFolder
caFolder = path.join(os.tmpdir(), 'cy-ca')

ca.baseCAFolder = caFolder
ca.certsFolder = path.join(ca.baseCAFolder, "certs")
ca.keysFolder = path.join(ca.baseCAFolder, "keys")

Promise.all([
fs.ensureDirAsync(ca.baseCAFolder)
fs.ensureDirAsync(ca.certsFolder)
fs.ensureDirAsync(ca.keysFolder)
])
.then ->
fs.statAsync(path.join(ca.certsFolder, "ca.pem"))
.bind(ca)
.then(ca.loadCA)
.catch(ca.generateCA)
fs.statAsync(path.join(ca.certsFolder, "ca.pem"))
.bind(ca)
.then(ca.loadCA)
.catch(ca.generateCA)
.return(ca)

module.exports = CA
Loading

0 comments on commit b44c644

Please sign in to comment.