mirror of
https://github.com/cypress-io/cypress.git
synced 2026-03-14 13:20:29 -05:00
add Loggable options to its() / invoke() command (#5519)
* add Loggable options to its() command * add test for new Loggable option for its() * add loggable options to invoke() * add type definition fix: only set logger config once. afterwards other commands can overwrite the logger config as done in line 322 remove unused error message (usage was removed in a former commit) remove test that is unnecessary now * add check if its() was passed additional arguments next to options * try to fix test * do not log 'this' context * from review: add additional tests and fix some edge cases * add tests for combination of loggable options and numeric index * fix wrong indentation * write as 'functionName' and 'propertyName' to match other error message * Update tests to reflect newly worded errors Co-authored-by: Jennifer Shehane <shehane.jennifer@gmail.com>
This commit is contained in:
committed by
Jennifer Shehane
parent
bbd519a54f
commit
259bfbecbe
7
cli/types/index.d.ts
vendored
7
cli/types/index.d.ts
vendored
@@ -871,6 +871,7 @@ declare namespace Cypress {
|
||||
* @see https://on.cypress.io/invoke
|
||||
*/
|
||||
invoke<T extends (...args: any[]) => any, Subject extends T[]>(index: number): Chainable<ReturnType<T>>
|
||||
invoke<T extends (...args: any[]) => any, Subject extends T[]>(options: Loggable, index: number): Chainable<ReturnType<T>>
|
||||
|
||||
/**
|
||||
* Invoke a function on the previously yielded subject.
|
||||
@@ -882,6 +883,7 @@ declare namespace Cypress {
|
||||
* @see https://on.cypress.io/invoke
|
||||
*/
|
||||
invoke(functionName: keyof Subject, ...args: any[]): Chainable<Subject> // don't have a way to express return types yet
|
||||
invoke(options: Loggable, functionName: keyof Subject, ...args: any[]): Chainable<Subject>
|
||||
|
||||
/**
|
||||
* Get a property’s value on the previously yielded subject.
|
||||
@@ -893,14 +895,15 @@ declare namespace Cypress {
|
||||
* // Drill into nested properties by using dot notation
|
||||
* cy.wrap({foo: {bar: {baz: 1}}}).its('foo.bar.baz')
|
||||
*/
|
||||
its<K extends keyof Subject>(propertyName: K): Chainable<Subject[K]>
|
||||
its<K extends keyof Subject>(propertyName: K, options?: Loggable): Chainable<Subject[K]>
|
||||
|
||||
/**
|
||||
* Get a value by index from an array yielded from the previous command.
|
||||
* @see https://on.cypress.io/its
|
||||
* @example
|
||||
* cy.wrap(['a', 'b']).its(1).should('equal', 'b')
|
||||
*/
|
||||
its<T, Subject extends T[]>(index: number): Chainable<T>
|
||||
its<T, Subject extends T[]>(index: number, options?: Loggable): Chainable<T>
|
||||
|
||||
/**
|
||||
* Get the last DOM element within a set of DOM elements.
|
||||
|
||||
@@ -135,8 +135,26 @@ module.exports = (Commands, Cypress, cy, state, config) ->
|
||||
}
|
||||
.finally(cleanup)
|
||||
|
||||
invokeFn = (subject, str, args...) ->
|
||||
options = {}
|
||||
invokeItsFn = (subject, str, options, args...) ->
|
||||
return invokeBaseFn(options or { log: true }, subject, str, args...)
|
||||
|
||||
invokeFn = (subject, optionsOrStr, args...) ->
|
||||
optionsPassed = _.isObject(optionsOrStr) and !_.isFunction(optionsOrStr)
|
||||
options = null
|
||||
str = null
|
||||
|
||||
if not optionsPassed
|
||||
str = optionsOrStr
|
||||
options = { log: true }
|
||||
else
|
||||
options = optionsOrStr
|
||||
if args.length > 0
|
||||
str = args[0]
|
||||
args = args.slice(1)
|
||||
|
||||
return invokeBaseFn(options, subject, str, args...)
|
||||
|
||||
invokeBaseFn = (options, subject, str, args...) ->
|
||||
|
||||
## name could be invoke or its!
|
||||
name = state("current").get("name")
|
||||
@@ -154,21 +172,34 @@ module.exports = (Commands, Cypress, cy, state, config) ->
|
||||
|
||||
traversalErr = null
|
||||
|
||||
options._log = Cypress.log
|
||||
message: message
|
||||
$el: if $dom.isElement(subject) then subject else null
|
||||
consoleProps: ->
|
||||
Subject: subject
|
||||
if options.log
|
||||
options._log = Cypress.log
|
||||
message: message
|
||||
$el: if $dom.isElement(subject) then subject else null
|
||||
consoleProps: ->
|
||||
Subject: subject
|
||||
|
||||
if not str
|
||||
$utils.throwErrByPath("invoke_its.null_or_undefined_property_name", {
|
||||
onFail: options._log
|
||||
args: { cmd: name, identifier: if isCmdIts then "property" else "function" }
|
||||
})
|
||||
|
||||
if not _.isString(str) and not _.isNumber(str)
|
||||
$utils.throwErrByPath("invoke_its.invalid_1st_arg", {
|
||||
$utils.throwErrByPath("invoke_its.invalid_prop_name_arg", {
|
||||
onFail: options._log
|
||||
args: { cmd: name, identifier: if isCmdIts then "property" else "function" }
|
||||
})
|
||||
|
||||
if not _.isObject(options) or _.isFunction(options)
|
||||
$utils.throwErrByPath("invoke_its.invalid_options_arg", {
|
||||
onFail: options._log
|
||||
args: { cmd: name }
|
||||
})
|
||||
|
||||
if isCmdIts and args.length > 0
|
||||
$utils.throwErrByPath("invoke_its.invalid_num_of_args", {
|
||||
onFail: options._log
|
||||
if isCmdIts and args and args.length > 0
|
||||
$utils.throwErrByPath("invoke_its.invalid_num_of_args", {
|
||||
onFail: options._log
|
||||
args: { cmd: name }
|
||||
})
|
||||
|
||||
@@ -449,9 +480,9 @@ module.exports = (Commands, Cypress, cy, state, config) ->
|
||||
## return values are undefined. prob should rethink
|
||||
## this and investigate why that is the default behavior
|
||||
## of child commands
|
||||
invoke: ->
|
||||
invokeFn.apply(@, arguments)
|
||||
invoke: (subject, optionsOrStr, args...) ->
|
||||
invokeFn.apply(@, [subject, optionsOrStr, args...])
|
||||
|
||||
its: ->
|
||||
invokeFn.apply(@, arguments)
|
||||
its: (subject, str, options, args...) ->
|
||||
invokeItsFn.apply(@, [subject, str, options, args...])
|
||||
})
|
||||
|
||||
@@ -409,12 +409,13 @@ module.exports = {
|
||||
|
||||
cy.wrap({ foo: {{value}} }).its('foo.baz').should('not.exist')
|
||||
"""
|
||||
invalid_1st_arg: "#{cmd('{{cmd}}')} only accepts a string or a number as the first argument."
|
||||
invalid_num_of_args:
|
||||
"""
|
||||
#{cmd('{{cmd}}')} only accepts a single argument.
|
||||
|
||||
If you want to invoke a function with arguments, use cy.invoke().
|
||||
invalid_prop_name_arg: "#{cmd('{{cmd}}')} only accepts a string or a number as the {{identifier}}Name argument."
|
||||
null_or_undefined_property_name: "#{cmd('{{cmd}}')} expects the {{identifier}}Name argument to have a value."
|
||||
invalid_options_arg: "#{cmd('{{cmd}}')} only accepts an object as the options argument."
|
||||
invalid_num_of_args:
|
||||
"""
|
||||
#{cmd('{{cmd}}')} does not accept additional arguments.
|
||||
If you want to invoke a function with arguments, use cy.invoke().
|
||||
"""
|
||||
timed_out:
|
||||
"""
|
||||
|
||||
@@ -473,6 +473,120 @@ describe "src/cy/commands/connectors", ->
|
||||
|
||||
cy.wrap(obj).invoke("foo.bar")
|
||||
|
||||
describe "accepts a options argument", ->
|
||||
|
||||
it "changes subject to function invocation", ->
|
||||
cy.noop({ foo: -> "foo" }).invoke({ log: false }, "foo").then (str) ->
|
||||
expect(str).to.eq "foo"
|
||||
|
||||
it "forwards any additional arguments", ->
|
||||
cy.noop({ bar: (num1, num2) -> num1 + num2 }).invoke({ log: false }, "bar", 1, 2).then (num) ->
|
||||
expect(num).to.eq 3
|
||||
|
||||
cy.noop({ bar: -> undefined }).invoke({ log: false }, "bar").then (val) ->
|
||||
expect(val).to.be.undefined
|
||||
|
||||
it "works with numerical indexes", ->
|
||||
i = 0
|
||||
fn = ->
|
||||
i++
|
||||
return i == 5
|
||||
|
||||
cy.noop([_.noop, fn]).invoke({}, 1).should('be.true')
|
||||
|
||||
describe "errors", ->
|
||||
beforeEach ->
|
||||
Cypress.config("defaultCommandTimeout", 50)
|
||||
|
||||
cy.on "log:added", (attrs, log) =>
|
||||
@lastLog = log
|
||||
|
||||
return null
|
||||
|
||||
it "throws when function name is missing", (done) ->
|
||||
cy.on "fail", (err) =>
|
||||
lastLog = @lastLog
|
||||
expect(err.message).to.include "cy.invoke() expects the functionName argument to have a value"
|
||||
expect(lastLog.get("error").message).to.include(err.message)
|
||||
done()
|
||||
|
||||
cy.wrap({ foo: -> "foo"}).invoke({})
|
||||
|
||||
it "throws when function name is not of type string but of type boolean", (done) ->
|
||||
cy.on "fail", (err) =>
|
||||
lastLog = @lastLog
|
||||
expect(err.message).to.include "cy.invoke() only accepts a string or a number as the functionName argument."
|
||||
expect(lastLog.get("error").message).to.include(err.message)
|
||||
done()
|
||||
|
||||
cy.wrap({ foo: -> "foo"}).invoke({}, true)
|
||||
|
||||
it "throws when function name is not of type string but of type function", (done) ->
|
||||
cy.on "fail", (err) =>
|
||||
lastLog = @lastLog
|
||||
expect(err.message).to.include "cy.invoke() only accepts a string or a number as the functionName argument."
|
||||
expect(lastLog.get("error").message).to.include(err.message)
|
||||
done()
|
||||
|
||||
cy.wrap({ foo: -> "foo"}).invoke(() -> {})
|
||||
|
||||
it "throws when first parameter is neither of type object nor of type string nor of type number", (done) ->
|
||||
cy.on "fail", (err) =>
|
||||
lastLog = @lastLog
|
||||
expect(err.message).to.include "cy.invoke() only accepts a string or a number as the functionName argument."
|
||||
expect(lastLog.get("error").message).to.include(err.message)
|
||||
done()
|
||||
|
||||
cy.wrap({ foo: -> "foo"}).invoke(true, "show")
|
||||
|
||||
describe ".log", ->
|
||||
beforeEach ->
|
||||
@obj = {
|
||||
foo: "foo bar baz"
|
||||
num: 123
|
||||
bar: -> "bar"
|
||||
attr: (key, value) ->
|
||||
obj = {}
|
||||
obj[key] = value
|
||||
obj
|
||||
sum: (a, b) -> a + b
|
||||
}
|
||||
|
||||
cy.on "log:added", (attrs, log) =>
|
||||
@lastLog = log
|
||||
|
||||
return null
|
||||
|
||||
it "logs obj as a function", ->
|
||||
cy.noop(@obj).invoke({ log: true }, "bar").then ->
|
||||
obj = {
|
||||
name: "invoke"
|
||||
message: ".bar()"
|
||||
}
|
||||
|
||||
lastLog = @lastLog
|
||||
|
||||
_.each obj, (value, key) =>
|
||||
expect(lastLog.get(key)).to.deep.eq value
|
||||
|
||||
it "logs obj with arguments", ->
|
||||
cy.noop(@obj).invoke({ log: true }, "attr", "numbers", [1,2,3]).then ->
|
||||
expect(@lastLog.invoke("consoleProps")).to.deep.eq {
|
||||
Command: "invoke"
|
||||
Function: ".attr(numbers, [1, 2, 3])"
|
||||
"With Arguments": ["numbers", [1,2,3]]
|
||||
Subject: @obj
|
||||
Yielded: {numbers: [1,2,3]}
|
||||
}
|
||||
|
||||
it "can be disabled", ->
|
||||
cy.noop(@obj).invoke({ log: true }, "sum", 1, 2).then ->
|
||||
expect(@lastLog.invoke("consoleProps")).to.have.property("Function", ".sum(1, 2)")
|
||||
@lastLog = undefined
|
||||
|
||||
cy.noop(@obj).invoke({ log: false }, "sum", 1, 2).then ->
|
||||
expect(@lastLog).to.be.undefined
|
||||
|
||||
describe ".log", ->
|
||||
beforeEach ->
|
||||
@obj = {
|
||||
@@ -612,16 +726,6 @@ describe "src/cy/commands/connectors", ->
|
||||
|
||||
cy.invoke("queue")
|
||||
|
||||
it "throws when first argument isnt a string or a number", (done) ->
|
||||
cy.on "fail", (err) =>
|
||||
lastLog = @lastLog
|
||||
|
||||
expect(err.message).to.eq "cy.invoke() only accepts a string or a number as the first argument."
|
||||
expect(lastLog.get("error")).to.eq err
|
||||
done()
|
||||
|
||||
cy.wrap({}).invoke({})
|
||||
|
||||
it "logs once when not dom subject", (done) ->
|
||||
cy.on "fail", (err) =>
|
||||
lastLog = @lastLog
|
||||
@@ -870,6 +974,54 @@ describe "src/cy/commands/connectors", ->
|
||||
cy.wrap(obj).its("foo").should("be.undefined")
|
||||
cy.wrap(obj).its("foo").should("eq", undefined)
|
||||
|
||||
describe "accepts a options argument and works as without options argument", ->
|
||||
|
||||
it "proxies to #invokeFn", ->
|
||||
fn = -> "bar"
|
||||
cy.wrap({foo: fn}).its("foo", { log: false }).should("eq", fn)
|
||||
|
||||
it "does not invoke a function and uses as a property", ->
|
||||
fn = -> "fn"
|
||||
fn.bar = "bar"
|
||||
|
||||
cy.wrap(fn).its("bar", { log: false }).should("eq", "bar")
|
||||
|
||||
it "works with numerical indexes", ->
|
||||
cy.wrap(['foo', 'bar']).its(1, {}).should('eq', 'bar')
|
||||
|
||||
describe ".log", ->
|
||||
beforeEach ->
|
||||
@obj = {
|
||||
foo: "foo bar baz"
|
||||
num: 123
|
||||
}
|
||||
|
||||
cy.on "log:added", (attrs, log) =>
|
||||
@lastLog = log
|
||||
|
||||
return null
|
||||
|
||||
it "logs obj as a property", ->
|
||||
cy.noop(@obj).its("foo", { log: true }).then ->
|
||||
obj = {
|
||||
name: "its"
|
||||
message: ".foo"
|
||||
}
|
||||
|
||||
lastLog = @lastLog
|
||||
|
||||
_.each obj, (value, key) =>
|
||||
expect(lastLog.get(key)).to.deep.eq value
|
||||
|
||||
it "#consoleProps as a regular property", ->
|
||||
cy.noop(@obj).its("num", { log: true }).then ->
|
||||
expect(@lastLog.invoke("consoleProps")).to.deep.eq {
|
||||
Command: "its"
|
||||
Property: ".num"
|
||||
Subject: @obj
|
||||
Yielded: 123
|
||||
}
|
||||
|
||||
describe ".log", ->
|
||||
beforeEach ->
|
||||
@obj = {
|
||||
@@ -942,6 +1094,14 @@ describe "src/cy/commands/connectors", ->
|
||||
Yielded: 123
|
||||
}
|
||||
|
||||
it "can be disabled", ->
|
||||
cy.noop(@obj).its("num", { log: true }).then ->
|
||||
expect(@lastLog.invoke("consoleProps")).to.have.property("Property", ".num")
|
||||
@lastLog = undefined
|
||||
|
||||
cy.noop(@obj).its("num", { log: false }).then ->
|
||||
expect(@lastLog).to.be.undefined
|
||||
|
||||
describe "errors", ->
|
||||
beforeEach ->
|
||||
Cypress.config("defaultCommandTimeout", 50)
|
||||
@@ -1132,11 +1292,11 @@ describe "src/cy/commands/connectors", ->
|
||||
|
||||
cy.wrap(val).its("foo")
|
||||
|
||||
it "throws two args were passed as subject", (done) ->
|
||||
it "throws does not accept additional arguments", (done) ->
|
||||
cy.on "fail", (err) =>
|
||||
lastLog = @lastLog
|
||||
|
||||
expect(err.message).to.include "cy.its() only accepts a single argument."
|
||||
expect(err.message).to.include "cy.its() does not accept additional arguments."
|
||||
expect(lastLog.get("error").message).to.include(err.message)
|
||||
done()
|
||||
|
||||
@@ -1144,7 +1304,34 @@ describe "src/cy/commands/connectors", ->
|
||||
fn.bar = -> "bar"
|
||||
fn.bar.baz = "baz"
|
||||
|
||||
cy.wrap(fn).its("bar", "baz").should("eq", "baz")
|
||||
cy.wrap(fn).its("bar", { log: false }, "baz").should("eq", "baz")
|
||||
|
||||
it "throws when options argument is not an object", (done) ->
|
||||
cy.on "fail", (err) =>
|
||||
lastLog = @lastLog
|
||||
expect(err.message).to.include "cy.its() only accepts an object as the options argument."
|
||||
expect(lastLog.get("error").message).to.include(err.message)
|
||||
done()
|
||||
|
||||
cy.wrap({ foo: "string" }).its("foo", "bar")
|
||||
|
||||
it "throws when property name is missing", (done) ->
|
||||
cy.on "fail", (err) =>
|
||||
lastLog = @lastLog
|
||||
expect(err.message).to.include "cy.its() expects the propertyName argument to have a value"
|
||||
expect(lastLog.get("error").message).to.include(err.message)
|
||||
done()
|
||||
|
||||
cy.wrap({ foo: "foo"}).its()
|
||||
|
||||
it "throws when property name is not of type string", (done) ->
|
||||
cy.on "fail", (err) =>
|
||||
lastLog = @lastLog
|
||||
expect(err.message).to.include "cy.its() only accepts a string or a number as the propertyName argument."
|
||||
expect(lastLog.get("error").message).to.include(err.message)
|
||||
done()
|
||||
|
||||
cy.wrap({ foo: "foo"}).its(true)
|
||||
|
||||
it "resets traversalErr and throws the right assertion", (done) ->
|
||||
cy.timeout(200)
|
||||
|
||||
Reference in New Issue
Block a user