diff --git a/packages/driver/src/cy/commands/xhr.coffee b/packages/driver/src/cy/commands/xhr.coffee index 1a9ff4b74062..1b2b7fe228fb 100644 --- a/packages/driver/src/cy/commands/xhr.coffee +++ b/packages/driver/src/cy/commands/xhr.coffee @@ -358,11 +358,14 @@ module.exports = (Commands, Cypress, cy, state, config) -> _.defaults(options, defaults) - if not options.url - $utils.throwErrByPath "route.url_missing" - - if not (_.isString(options.url) or _.isRegExp(options.url)) - $utils.throwErrByPath "route.url_invalid" + if options.matcher + if not _.isFunction(options.matcher) + $utils.throwErrByPath "route.matcher_invalid" + else + if not options.url + $utils.throwErrByPath "route.url_missing" + if not (_.isString(options.url) or _.isRegExp(options.url)) + $utils.throwErrByPath "route.url_invalid" if not $utils.isValidHttpMethod(options.method) $utils.throwErrByPath "route.method_invalid", { diff --git a/packages/driver/src/cypress/error_messages.coffee b/packages/driver/src/cypress/error_messages.coffee index d7594354817c..eaa15a9b2013 100644 --- a/packages/driver/src/cypress/error_messages.coffee +++ b/packages/driver/src/cypress/error_messages.coffee @@ -689,6 +689,7 @@ module.exports = { route: failed_prerequisites: "#{cmd('route')} cannot be invoked before starting the #{cmd('server')}" invalid_arguments: "#{cmd('route')} was not provided any arguments. You must provide valid arguments." + matcher_invalid: "#{cmd('route')} was called with an invalid matcher option: matcher must be a Function that returns a Boolean." method_invalid: "#{cmd('route')} was called with an invalid method: '{{method}}'. Method can be: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, or any other method supported by Node's HTTP parser." response_invalid: "#{cmd('route')} cannot accept an undefined or null response. It must be set to something, even an empty string will work." url_invalid: "#{cmd('route')} was called with an invalid url. Url must be either a string or regular expression." diff --git a/packages/driver/src/cypress/server.coffee b/packages/driver/src/cypress/server.coffee index 22dcf0761b56..96af763f96d8 100644 --- a/packages/driver/src/cypress/server.coffee +++ b/packages/driver/src/cypress/server.coffee @@ -272,6 +272,7 @@ create = (options = {}) -> testStr(fullyQualifiedUrl, options.stripOrigin(fullyQualifiedUrl)) xhrMatchesRoute: (xhr, route) -> + return route.matcher(xhr, route) if _.isFunction(route.matcher) server.methodsMatch(route.method, xhr.method) and server.urlsMatch(route.url, xhr.url) add: (xhr, attrs = {}) -> diff --git a/packages/driver/test/cypress/integration/commands/xhr_spec.coffee b/packages/driver/test/cypress/integration/commands/xhr_spec.coffee index 87501f75bdc1..4ea7c1461a1d 100644 --- a/packages/driver/test/cypress/integration/commands/xhr_spec.coffee +++ b/packages/driver/test/cypress/integration/commands/xhr_spec.coffee @@ -1332,6 +1332,76 @@ describe "src/cy/commands/xhr", -> url: /foo/ respond: false + describe "matcher option", -> + it "accepts matcher option", -> + matcher = () => true + response = { foo: true } + + cy.route({ + matcher, + response, + }).then -> + @expectOptionsToBe({ + matcher, + status: 200, + response + }) + + it "passes both xhr and options arguments to matcher option function", -> + cy + .window() + .then (win) -> + options = { + matcher: (xhr, _options) => + expect(xhr instanceof win.XMLHttpRequest).to.be.true + expect(_options.matcher).to.eq(options.matcher) + expect(_options.response).to.eq(options.response) + response: 'foo', + } + cy + .route(options) + .window().then (win) -> + win.$.get("/foo") + + it "receives request body in matcher", -> + options = { + matcher: (xhr) => + expect(xhr.body).to.eq('baz') + } + + cy + .route(options) + .window().then (win) -> + win.$.post("/foo", "baz") + + it "ignores url when matcher option is present", -> + cy + .route({ + url: "/bar" + matcher: (xhr, options) => /foo/.test(xhr.url) + response: 'foo', + }).as("getFoo") + .window().then (win) -> + win.$.get("/bar").catch((response) -> expect(response.status == 404)) + .window().then (win) -> + win.$.get("/foo") + .wait("@getFoo").then (xhr) -> + expect(xhr.responseBody).to.eq 'foo' + + it "ignores url when matcher option is present", -> + cy + .route({ + url: "/bar" + matcher: (xhr, options) => /foo/.test(xhr.url) + response: 'foo', + }).as("getFoo") + .window().then (win) -> + win.$.get("/bar").catch((response) -> expect(response.status == 404)) + .window().then (win) -> + win.$.get("/foo") + .wait("@getFoo").then (xhr) -> + expect(xhr.responseBody).to.eq 'foo' + describe "deprecations", -> beforeEach -> @warn = cy.spy(window.top.console, "warn") @@ -1442,6 +1512,14 @@ describe "src/cy/commands/xhr", -> cy.route(getUrl) + it "fails when matcher option is not a function", -> + cy.route({ + matcher: 'foo', + }) + + cy.on "fail", (err) -> + expect(err.message).to.eq "cy.route() was called with an invalid matcher option: matcher must be a Function that returns a Boolean." + it "fails when functions reject", (done) -> error = new Error