Skip to content

Commit 76a1830

Browse files
authored
throw specific error when cannot parse Cypress arguments (#6280)
* WIP: throw specific error when cannot parse Cypress arguments * test invalid config string * add unit test * rework error message, handle env, reporter options and config * add args error unit tests * rework snapshots * use reporterOptions name * update snapshot
1 parent 81555fc commit 76a1830

7 files changed

Lines changed: 189 additions & 40 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
exports['invalid env error'] = `
2+
Cypress encountered an error while parsing the argument env
3+
4+
You passed: nonono
5+
6+
The error was: Cannot read property 'split' of undefined
7+
`
8+
9+
exports['invalid reporter options error'] = `
10+
Cypress encountered an error while parsing the argument reporterOptions
11+
12+
You passed: abc
13+
14+
The error was: Cannot read property 'split' of undefined
15+
`
16+
17+
exports['invalid config error'] = `
18+
Cypress encountered an error while parsing the argument config
19+
20+
You passed: xyz
21+
22+
The error was: Cannot read property 'split' of undefined
23+
`

packages/server/__snapshots__/cypress_spec.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,3 +264,27 @@ These flags can only be used when recording to the Cypress Dashboard service.
264264
265265
https://on.cypress.io/record-params-without-recording
266266
`
267+
268+
exports['could not parse config error'] = `
269+
Cypress encountered an error while parsing the argument config
270+
271+
You passed: xyz
272+
273+
The error was: Cannot read property 'split' of undefined
274+
`
275+
276+
exports['could not parse env error'] = `
277+
Cypress encountered an error while parsing the argument env
278+
279+
You passed: a123
280+
281+
The error was: Cannot read property 'split' of undefined
282+
`
283+
284+
exports['could not parse reporter options error'] = `
285+
Cypress encountered an error while parsing the argument reporterOptions
286+
287+
You passed: nonono
288+
289+
The error was: Cannot read property 'split' of undefined
290+
`

packages/server/lib/cypress.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ const exitErr = (err) => {
3939

4040
return require('./errors').logException(err)
4141
.then(() => {
42+
debug('calling exit 1')
43+
4244
return exit(1)
4345
})
4446
}
@@ -151,7 +153,16 @@ module.exports = {
151153
// for https://github.com/cypress-io/cypress/issues/5466
152154
argv = R.without('--', argv)
153155

154-
const options = argsUtils.toObject(argv)
156+
let options
157+
158+
try {
159+
options = argsUtils.toObject(argv)
160+
} catch (argumentsError) {
161+
debug('could not parse CLI arguments: %o', argv)
162+
163+
// note - this is promise-returned call
164+
return exitErr(argumentsError)
165+
}
155166

156167
debug('from argv %o got options %o', argv, options)
157168

packages/server/lib/errors.coffee

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ trimMultipleNewLines = (str) ->
6262
isCypressErr = (err) ->
6363
Boolean(err.isCypressErr)
6464

65-
getMsgByType = (type, arg1 = {}, arg2) ->
65+
getMsgByType = (type, arg1 = {}, arg2, arg3) ->
6666
switch type
6767
when "CANNOT_TRASH_ASSETS"
6868
"""
@@ -525,7 +525,6 @@ getMsgByType = (type, arg1 = {}, arg2) ->
525525
526526
#{chalk.blue(arg2)}
527527
"""
528-
529528
when "RENDERER_CRASHED"
530529
"""
531530
We detected that the Chromium Renderer process just crashed.
@@ -881,9 +880,17 @@ getMsgByType = (type, arg1 = {}, arg2) ->
881880
"""
882881
Failed to connect to Chrome, retrying in 1 second (attempt #{chalk.yellow(arg1)}/32)
883882
"""
883+
when "COULD_NOT_PARSE_ARGUMENTS"
884+
"""
885+
Cypress encountered an error while parsing the argument #{chalk.gray(arg1)}
886+
887+
You passed: #{arg2}
888+
889+
The error was: #{arg3}
890+
"""
884891

885-
get = (type, arg1, arg2) ->
886-
msg = getMsgByType(type, arg1, arg2)
892+
get = (type, arg1, arg2, arg3) ->
893+
msg = getMsgByType(type, arg1, arg2, arg3)
887894

888895
if _.isObject(msg)
889896
details = msg.details
@@ -904,8 +911,8 @@ warning = (type, arg1, arg2) ->
904911

905912
return null
906913

907-
throwErr = (type, arg1, arg2) ->
908-
throw get(type, arg1, arg2)
914+
throwErr = (type, arg1, arg2, arg3) ->
915+
throw get(type, arg1, arg2, arg3)
909916

910917
clone = (err, options = {}) ->
911918
_.defaults options, {

packages/server/lib/util/args.js

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
const _ = require('lodash')
2+
const la = require('lazy-ass')
3+
const is = require('check-more-types')
24
const path = require('path')
35
const debug = require('debug')('cypress:server:args')
46
const minimist = require('minimist')
57
const coerceUtil = require('./coerce')
68
const configUtil = require('../config')
79
const proxyUtil = require('./proxy')
10+
const errors = require('../errors')
811

912
const nestedObjectsInCurlyBracesRe = /\{(.+?)\}/g
1013
const nestedArraysInSquareBracketsRe = /\[(.+?)\]/g
@@ -107,32 +110,41 @@ const JSONOrCoerce = (str) => {
107110
return coerceUtil(str)
108111
}
109112

110-
const sanitizeAndConvertNestedArgs = (str) => {
113+
const sanitizeAndConvertNestedArgs = (str, argname) => {
114+
la(is.unemptyString(argname), 'missing config argname to be parsed')
115+
116+
try {
111117
// if this is valid JSON then just
112118
// parse it and call it a day
113-
const parsed = tryJSONParse(str)
119+
const parsed = tryJSONParse(str)
114120

115-
if (parsed) {
116-
return parsed
117-
}
121+
if (parsed) {
122+
return parsed
123+
}
118124

119-
// invalid JSON, so assume mixed usage
120-
// first find foo={a:b,b:c} and bar=[1,2,3]
121-
// syntax and turn those into
122-
// foo: a:b|b:c
123-
// bar: 1|2|3
124-
125-
return _
126-
.chain(str)
127-
.replace(nestedObjectsInCurlyBracesRe, commasToPipes)
128-
.replace(nestedArraysInSquareBracketsRe, commasToPipes)
129-
.split(',')
130-
.map((pair) => {
131-
return pair.split(everythingAfterFirstEqualRe)
132-
})
133-
.fromPairs()
134-
.mapValues(JSONOrCoerce)
135-
.value()
125+
// invalid JSON, so assume mixed usage
126+
// first find foo={a:b,b:c} and bar=[1,2,3]
127+
// syntax and turn those into
128+
// foo: a:b|b:c
129+
// bar: 1|2|3
130+
131+
return _
132+
.chain(str)
133+
.replace(nestedObjectsInCurlyBracesRe, commasToPipes)
134+
.replace(nestedArraysInSquareBracketsRe, commasToPipes)
135+
.split(',')
136+
.map((pair) => {
137+
return pair.split(everythingAfterFirstEqualRe)
138+
})
139+
.fromPairs()
140+
.mapValues(JSONOrCoerce)
141+
.value()
142+
} catch (err) {
143+
debug('could not pass config %s value %s', argname, str)
144+
debug('error %o', err)
145+
146+
return errors.throw('COULD_NOT_PARSE_ARGUMENTS', argname, str, err.message)
147+
}
136148
}
137149

138150
module.exports = {
@@ -219,7 +231,7 @@ module.exports = {
219231
}
220232

221233
if (env) {
222-
options.env = sanitizeAndConvertNestedArgs(env)
234+
options.env = sanitizeAndConvertNestedArgs(env, 'env')
223235
}
224236

225237
const proxySource = proxyUtil.loadSystemProxySettings()
@@ -234,13 +246,13 @@ module.exports = {
234246
}
235247

236248
if (reporterOptions) {
237-
options.reporterOptions = sanitizeAndConvertNestedArgs(reporterOptions)
249+
options.reporterOptions = sanitizeAndConvertNestedArgs(reporterOptions, 'reporterOptions')
238250
}
239251

240252
if (config) {
241253
// convert config to an object
242-
// annd store the config
243-
options.config = sanitizeAndConvertNestedArgs(config)
254+
// and store the config
255+
options.config = sanitizeAndConvertNestedArgs(config, 'config')
244256
}
245257

246258
// get a list of the available config keys

packages/server/test/integration/cypress_spec.js

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -140,14 +140,22 @@ describe('lib/cypress', () => {
140140
expect(process.exit).to.be.calledWith(code)
141141
}
142142

143-
this.expectExitWithErr = (type, msg) => {
144-
expect(errors.log).to.be.calledWithMatch({ type })
145-
expect(process.exit).to.be.calledWith(1)
146-
if (msg) {
147-
const err = errors.log.getCall(0).args[0]
143+
// returns error object
144+
this.expectExitWithErr = (type, msg1, msg2) => {
145+
expect(errors.log, 'error was logged').to.be.calledWithMatch({ type })
146+
expect(process.exit, 'process.exit was called').to.be.calledWith(1)
148147

149-
expect(err.message).to.include(msg)
148+
const err = errors.log.getCall(0).args[0]
149+
150+
if (msg1) {
151+
expect(err.message, 'error text').to.include(msg1)
152+
}
153+
154+
if (msg2) {
155+
expect(err.message, 'second error text').to.include(msg2)
150156
}
157+
158+
return err
151159
}
152160
})
153161

@@ -190,6 +198,35 @@ describe('lib/cypress', () => {
190198
})
191199
})
192200

201+
context('error handling', function () {
202+
it('exits if config cannot be parsed', function () {
203+
return cypress.start(['--config', 'xyz'])
204+
.then(() => {
205+
const err = this.expectExitWithErr('COULD_NOT_PARSE_ARGUMENTS')
206+
207+
snapshot('could not parse config error', stripAnsi(err.message))
208+
})
209+
})
210+
211+
it('exits if env cannot be parsed', function () {
212+
return cypress.start(['--env', 'a123'])
213+
.then(() => {
214+
const err = this.expectExitWithErr('COULD_NOT_PARSE_ARGUMENTS')
215+
216+
snapshot('could not parse env error', stripAnsi(err.message))
217+
})
218+
})
219+
220+
it('exits if reporter options cannot be parsed', function () {
221+
return cypress.start(['--reporterOptions', 'nonono'])
222+
.then(() => {
223+
const err = this.expectExitWithErr('COULD_NOT_PARSE_ARGUMENTS')
224+
225+
snapshot('could not parse reporter options error', stripAnsi(err.message))
226+
})
227+
})
228+
})
229+
193230
context('--get-key', () => {
194231
it('writes out key and exits on success', function () {
195232
return Promise.all([

packages/server/test/unit/args_spec.coffee

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ require("../spec_helper")
33
_ = require("lodash")
44
path = require("path")
55
os = require("os")
6-
argsUtil = require("#{root}lib/util/args")
6+
snapshot = require("snap-shot-it")
7+
stripAnsi = require("strip-ansi")
8+
argsUtil = require("#{root}lib/util/args")
79
proxyUtil = require("#{root}lib/util/proxy")
810
getWindowsProxyUtil = require("#{root}lib/util/get-windows-proxy")
911

@@ -74,6 +76,17 @@ describe "lib/util/args", ->
7476
bar: "qux="
7577
})
7678

79+
it "throws if env string cannot be parsed", ->
80+
expect () =>
81+
@setup("--env", "nonono")
82+
.to.throw
83+
84+
# now look at the error
85+
try
86+
@setup("--env", "nonono")
87+
catch err
88+
snapshot("invalid env error", stripAnsi(err.message))
89+
7790
context "--reporterOptions", ->
7891
it "converts to object literal", ->
7992
reporterOpts = {
@@ -112,6 +125,17 @@ describe "lib/util/args", ->
112125
}
113126
})
114127

128+
it "throws if reporter string cannot be parsed", ->
129+
expect () =>
130+
@setup("--reporterOptions", "abc")
131+
.to.throw
132+
133+
# now look at the error
134+
try
135+
@setup("--reporterOptions", "abc")
136+
catch err
137+
snapshot("invalid reporter options error", stripAnsi(err.message))
138+
115139
context "--config", ->
116140
it "converts to object literal", ->
117141
options = @setup("--config", "pageLoadTimeout=10000,waitForAnimations=false")
@@ -172,6 +196,17 @@ describe "lib/util/args", ->
172196
options = @setup("--port", 2222)
173197
expect(options.config.port).to.eq(2222)
174198

199+
it "throws if config string cannot be parsed", ->
200+
expect () =>
201+
@setup("--config", "xyz")
202+
.to.throw
203+
204+
# now look at the error
205+
try
206+
@setup("--config", "xyz")
207+
catch err
208+
snapshot("invalid config error", stripAnsi(err.message))
209+
175210
context ".toArray", ->
176211
beforeEach ->
177212
@obj = {config: {foo: "bar"}, project: "foo/bar"}

0 commit comments

Comments
 (0)