diff --git a/packages/driver/src/cy/commands/querying.coffee b/packages/driver/src/cy/commands/querying.coffee index 9ccd5049f90a..d999f385244f 100644 --- a/packages/driver/src/cy/commands/querying.coffee +++ b/packages/driver/src/cy/commands/querying.coffee @@ -126,9 +126,16 @@ module.exports = (Commands, Cypress, cy, state, config) -> options._log.set(obj) - ## we always want to strip everything after the first '.' - ## since we support alias propertys like '1' or 'all' - if aliasObj = cy.getAlias(selector.split(".")[0]) + ## We want to strip everything after the last '.' + ## only when it is potentially a number or 'all' + if _.indexOf(selector, ".") == -1 || + selector.slice(1) in _.keys(cy.state("aliases")) + toSelect = selector + else + allParts = _.split(selector, '.') + toSelect = _.join(_.dropRight(allParts, 1), '.') + + if aliasObj = cy.getAlias(toSelect) {subject, alias, command} = aliasObj return do resolveAlias = -> @@ -176,7 +183,11 @@ module.exports = (Commands, Cypress, cy, state, config) -> ## if this is a route command when command.get("name") is "route" - alias = _.compact([alias, selector.split(".")[1]]).join(".") + if !(_.indexOf(selector, ".") == -1 || + selector.slice(1) in _.keys(cy.state("aliases"))) + allParts = _.split(selector, ".") + index = _.last(allParts) + alias = _.join([alias, index], ".") requests = cy.getRequestsByAlias(alias) ? null log(requests, "route") return requests diff --git a/packages/driver/src/cy/commands/waiting.coffee b/packages/driver/src/cy/commands/waiting.coffee index c2eb47718b5e..bc15c6a4e176 100644 --- a/packages/driver/src/cy/commands/waiting.coffee +++ b/packages/driver/src/cy/commands/waiting.coffee @@ -66,9 +66,15 @@ module.exports = (Commands, Cypress, cy, state, config) -> , options waitForXhr = (str, options) -> - ## we always want to strip everything after the first '.' + ## we always want to strip everything after the last '.' ## since we support alias property 'request' - [str, str2] = str.split(".") + if _.indexOf(str, ".") == -1 || + str.slice(1) in _.keys(cy.state("aliases")) + [str, str2] = [str, null] + else + # potentially request, response or index + allParts = _.split(str, '.') + [str, str2] = [_.join(_.dropRight(allParts, 1), '.'), _.last(allParts)] if not aliasObj = cy.getAlias(str, "wait", log) cy.aliasNotFoundFor(str, "wait", log) @@ -79,7 +85,7 @@ module.exports = (Commands, Cypress, cy, state, config) -> {alias, command} = aliasObj str = _.compact([alias, str2]).join(".") - + type = cy.getXhrTypeByAlias(str) [ index, num ] = getNumRequests(state, alias) diff --git a/packages/driver/src/cy/xhrs.coffee b/packages/driver/src/cy/xhrs.coffee index 6b3c375bcde3..48011b87bb59 100644 --- a/packages/driver/src/cy/xhrs.coffee +++ b/packages/driver/src/cy/xhrs.coffee @@ -24,7 +24,11 @@ xhrNotWaitedOnByIndex = (state, alias, index, prop) -> create = (state) -> return { getIndexedXhrByAlias: (alias, index) -> - [str, prop] = alias.split(".") + if _.indexOf(alias, ".") == -1 + [str, prop] = [alias, null] + else + allParts = _.split(alias, '.') + [str, prop] = [_.join(_.dropRight(allParts, 1), '.'), _.last(allParts)] if prop if prop is "request" @@ -38,7 +42,12 @@ create = (state) -> xhrNotWaitedOnByIndex(state, str, index, "responses") getRequestsByAlias: (alias) -> - [alias, prop] = alias.split(".") + if _.indexOf(alias, ".") == -1 || alias in _.keys(cy.state("aliases")) + [alias, prop] = [alias, null] + else + # potentially valid prop + allParts = _.split(alias, '.') + [alias, prop] = [_.join(_.dropRight(allParts, 1), '.'), _.last(allParts)] if prop and not validAliasApiRe.test(prop) $utils.throwErrByPath "get.alias_invalid", { diff --git a/packages/driver/test/cypress/integration/commands/agents_spec.coffee b/packages/driver/test/cypress/integration/commands/agents_spec.coffee index 2f2f87b1e2bd..2bd78d910c5b 100644 --- a/packages/driver/test/cypress/integration/commands/agents_spec.coffee +++ b/packages/driver/test/cypress/integration/commands/agents_spec.coffee @@ -201,59 +201,115 @@ describe "src/cy/commands/agents", -> expect(@consoleProps[" 1.2 matching arguments"]).to.eql(["foo", "baz"]) describe ".as", -> - beforeEach -> - @logs = [] - cy.on "log:added", (attrs, log) => - @logs.push(log) + context "without dots", -> + beforeEach -> + @logs = [] + cy.on "log:added", (attrs, log) => + @logs.push(log) - @stub = cy.stub().as("myStub") + @stub = cy.stub().as("myStub") - it "returns stub", -> - expect(@stub).to.have.property("callCount") + it "returns stub", -> + expect(@stub).to.have.property("callCount") - it "updates instrument log with alias", -> - expect(@logs[0].get("alias")).to.eq("myStub") - expect(@logs[0].get("aliasType")).to.eq("agent") + it "updates instrument log with alias", -> + expect(@logs[0].get("alias")).to.eq("myStub") + expect(@logs[0].get("aliasType")).to.eq("agent") - it "includes alias in invocation log", -> - @stub() - expect(@logs[1].get("alias")).to.eql(["myStub"]) - expect(@logs[1].get("aliasType")).to.eq("agent") + it "includes alias in invocation log", -> + @stub() + expect(@logs[1].get("alias")).to.eql(["myStub"]) + expect(@logs[1].get("aliasType")).to.eq("agent") - it "includes alias in console props", -> - @stub() - consoleProps = @logs[1].get("consoleProps")() - expect(consoleProps["Alias"]).to.eql("myStub") + it "includes alias in console props", -> + @stub() + consoleProps = @logs[1].get("consoleProps")() + expect(consoleProps["Alias"]).to.eql("myStub") + + it "updates the displayName of the agent", -> + expect(@myStub.displayName).to.eq("myStub") + + it "stores the lookup as an alias", -> + expect(cy.state("aliases").myStub).to.be.defined + + it "stores the agent as the subject", -> + expect(cy.state("aliases").myStub.subject).to.eq(@stub) + + it "assigns subject to runnable ctx", -> + expect(@myStub).to.eq(@stub) + + it "retries until assertions pass", -> + cy.on "command:retry", _.after 2, => + @myStub("foo") + + cy.get("@myStub").should("be.calledWith", "foo") + + describe "errors", -> + _.each [null, undefined, {}, [], 123], (value) => + it "throws when passed: #{value}", -> + expect(=> cy.stub().as(value)).to.throw("cy.as() can only accept a string.") + + it "throws on blank string", -> + expect(=> cy.stub().as("")).to.throw("cy.as() cannot be passed an empty string.") + + _.each ["test", "runnable", "timeout", "slow", "skip", "inspect"], (blacklist) -> + it "throws on a blacklisted word: #{blacklist}", -> + expect(=> cy.stub().as(blacklist)).to.throw("cy.as() cannot be aliased as: '#{blacklist}'. This word is reserved.") + + context "with dots", -> + beforeEach -> + @logs = [] + cy.on "log:added", (attrs, log) => + @logs.push(log) + + @stub = cy.stub().as("my.stub") + + it "returns stub", -> + expect(@stub).to.have.property("callCount") + + it "updates instrument log with alias", -> + expect(@logs[0].get("alias")).to.eq("my.stub") + expect(@logs[0].get("aliasType")).to.eq("agent") + + it "includes alias in invocation log", -> + @stub() + expect(@logs[1].get("alias")).to.eql(["my.stub"]) + expect(@logs[1].get("aliasType")).to.eq("agent") + + it "includes alias in console props", -> + @stub() + consoleProps = @logs[1].get("consoleProps")() + expect(consoleProps["Alias"]).to.eql("my.stub") - it "updates the displayName of the agent", -> - expect(@myStub.displayName).to.eq("myStub") + it "updates the displayName of the agent", -> + expect(@["my.stub"].displayName).to.eq("my.stub") - it "stores the lookup as an alias", -> - expect(cy.state("aliases").myStub).to.be.defined + it "stores the lookup as an alias", -> + expect(cy.state("aliases")["my.stub"]).to.be.defined - it "stores the agent as the subject", -> - expect(cy.state("aliases").myStub.subject).to.eq(@stub) + it "stores the agent as the subject", -> + expect(cy.state("aliases")["my.stub"].subject).to.eq(@stub) - it "assigns subject to runnable ctx", -> - expect(@myStub).to.eq(@stub) + it "assigns subject to runnable ctx", -> + expect(@["my.stub"]).to.eq(@stub) - it "retries until assertions pass", -> - cy.on "command:retry", _.after 2, => - @myStub("foo") - - cy.get("@myStub").should("be.calledWith", "foo") + it "retries until assertions pass", -> + cy.on "command:retry", _.after 2, => + @["my.stub"]("foo") + + cy.get("@my.stub").should("be.calledWith", "foo") - describe "errors", -> - _.each [null, undefined, {}, [], 123], (value) => - it "throws when passed: #{value}", -> - expect(=> cy.stub().as(value)).to.throw("cy.as() can only accept a string.") + describe "errors", -> + _.each [null, undefined, {}, [], 123], (value) => + it "throws when passed: #{value}", -> + expect(=> cy.stub().as(value)).to.throw("cy.as() can only accept a string.") - it "throws on blank string", -> - expect(=> cy.stub().as("")).to.throw("cy.as() cannot be passed an empty string.") + it "throws on blank string", -> + expect(=> cy.stub().as("")).to.throw("cy.as() cannot be passed an empty string.") - _.each ["test", "runnable", "timeout", "slow", "skip", "inspect"], (blacklist) -> - it "throws on a blacklisted word: #{blacklist}", -> - expect(=> cy.stub().as(blacklist)).to.throw("cy.as() cannot be aliased as: '#{blacklist}'. This word is reserved.") + _.each ["test", "runnable", "timeout", "slow", "skip", "inspect"], (blacklist) -> + it "throws on a blacklisted word: #{blacklist}", -> + expect(=> cy.stub().as(blacklist)).to.throw("cy.as() cannot be aliased as: '#{blacklist}'. This word is reserved.") describe "logging", -> beforeEach -> @@ -468,4 +524,4 @@ describe "src/cy/commands/agents", -> expect(@agents.spy).to.be.a("function") expect(@agents.spy().callCount).to.be.a("number") expect(@agents.stub).to.be.a("function") - expect(@agents.stub().returns).to.be.a("function") + expect(@agents.stub().returns).to.be.a("function") \ No newline at end of file diff --git a/packages/driver/test/cypress/integration/commands/aliasing_spec.coffee b/packages/driver/test/cypress/integration/commands/aliasing_spec.coffee index 59a28cfe3806..8e4b2fb84c29 100644 --- a/packages/driver/test/cypress/integration/commands/aliasing_spec.coffee +++ b/packages/driver/test/cypress/integration/commands/aliasing_spec.coffee @@ -51,6 +51,20 @@ describe "src/cy/commands/aliasing", -> cy.get("@obj").should("deep.eq", { foo: "bar" }) + it "allows dot in alias names", -> + cy.get("body").as("body.foo").then -> + expect(cy.get('@body.foo')).to.be.defined + expect(cy.state("aliases")['body.foo']).to.be.defined + + it "recognizes dot and non dot with same alias names", -> + cy.get("body").as("body").then -> + expect(cy.get('@body')).to.be.defined + expect(cy.state("aliases")['body']).to.be.defined + cy.contains("foo").as("body.foo").then -> + expect(cy.get('@body.foo')).to.be.defined + expect(cy.get('@body.foo')).to.not.eq(cy.get('@body')) + expect(cy.state("aliases")['body.foo']).to.be.defined + context "DOM subjects", -> it "assigns the remote jquery instance", -> obj = {} @@ -75,6 +89,10 @@ describe "src/cy/commands/aliasing", -> .noop({}).as("baz").then (obj) -> expect(@baz).to.eq obj + it "assigns subject with dot to runnable ctx", -> + cy.noop({}).as("bar.baz").then (obj) -> + expect(@["bar.baz"]).to.eq obj + describe "nested hooks", -> afterEach -> if not @bar @@ -130,6 +148,13 @@ describe "src/cy/commands/aliasing", -> cy.get("div:first").as("@myAlias") + it "throws on alias starting with @ char and dots", (done) -> + cy.on "fail", (err) -> + expect(err.message).to.eq "'@my.alias' cannot be named starting with the '@' symbol. Try renaming the alias to 'my.alias', or something else that does not start with the '@' symbol." + done() + + cy.get("div:first").as("@my.alias") + it "does not throw on alias with @ char in non-starting position", () -> cy.get("div:first").as("my@Alias") cy.get("@my@Alias") @@ -157,7 +182,6 @@ describe "src/cy/commands/aliasing", -> lastLog = @lastLog expect(lastLog.get("aliasType")).to.eq "primitive" - it "sets aliasType to 'dom'", -> cy.get("body").find("button:first").click().as("button").then -> lastLog = @lastLog @@ -198,11 +222,6 @@ describe "src/cy/commands/aliasing", -> expect(@logs[1].get("name")).to.eq("route") expect(@logs[1].get("alias")).to.eq("getFoo") - # it "does not alias previous logs when no matching chainerId", -> - # cy - # .get("div:first") - # .noop({}).as("foo").then -> - context "#replayCommandsFrom", -> describe "subject in document", -> it "returns if subject is still in the document", -> @@ -349,7 +368,7 @@ describe "src/cy/commands/aliasing", -> .get("body").as("b") .get("input:first").as("firstInput") .get("@lastDiv") - + it "throws when alias is missing '@' but matches an available alias", (done) -> cy.on "fail", (err) -> expect(err.message).to.eq "Invalid alias: 'getAny'.\nYou forgot the '@'. It should be written as: '@getAny'." @@ -358,4 +377,4 @@ describe "src/cy/commands/aliasing", -> cy .server() .route("*", {}).as("getAny") - .wait("getAny").then -> + .wait("getAny").then -> \ No newline at end of file diff --git a/packages/driver/test/cypress/integration/commands/querying_spec.coffee b/packages/driver/test/cypress/integration/commands/querying_spec.coffee index 84f08e912745..0d782519cfdc 100644 --- a/packages/driver/test/cypress/integration/commands/querying_spec.coffee +++ b/packages/driver/test/cypress/integration/commands/querying_spec.coffee @@ -732,18 +732,17 @@ describe "src/cy/commands/querying", -> expect(@lastLog.get("$el").get(0)).not.to.be.ok it "logs route aliases", -> - cy - .visit("http://localhost:3500/fixtures/jquery.html") - .server() - .route(/users/, {}).as("getUsers") - .window().then { timeout: 2000 }, (win) -> - win.$.get("/users") - .get("@getUsers").then -> - expect(@lastLog.pick("message", "referencesAlias", "aliasType")).to.deep.eq { - message: "@getUsers" - referencesAlias: {name: "getUsers"} - aliasType: "route" - } + cy.visit("http://localhost:3500/fixtures/jquery.html") + cy.server() + cy.route(/users/, {}).as("get.users") + cy.window().then { timeout: 2000 }, (win) -> + win.$.get("/users") + cy.get("@get.users").then -> + expect(@lastLog.pick("message", "referencesAlias", "aliasType")).to.deep.eq { + message: "@get.users" + referencesAlias: {name: "get.users"} + aliasType: "route" + } it "logs primitive aliases", (done) -> cy.on "log:added", (attrs, log) -> @@ -885,6 +884,15 @@ describe "src/cy/commands/querying", -> .get("@getUsers").then (xhr) -> expect(xhr.url).to.include "/users" + it "handles dots in alias name", -> + cy.server() + cy.route(/users/, {}).as("get.users") + cy.visit("http://localhost:3500/fixtures/jquery.html") + cy.window().then { timeout: 2000 }, (win) -> + win.$.get("/users") + cy.get("@get.users").then (xhr) -> + expect(xhr.url).to.include "/users" + it "returns null if no xhr is found", -> cy .server() @@ -908,6 +916,20 @@ describe "src/cy/commands/querying", -> expect(xhrs[0].url).to.include "/users?num=1" expect(xhrs[1].url).to.include "/users?num=2" + it "returns an array of xhrs when dots in alias name", -> + cy.visit("http://localhost:3500/fixtures/jquery.html") + cy.server() + cy.route(/users/, {}).as("get.users") + cy.window().then { timeout: 2000 }, (win) -> + Promise.all([ + win.$.get("/users", {num: 1}) + win.$.get("/users", {num: 2}) + ]) + cy.get("@get.users.all").then (xhrs) -> + expect(xhrs).to.be.an("array") + expect(xhrs[0].url).to.include "/users?num=1" + expect(xhrs[1].url).to.include "/users?num=2" + it "returns the 1st xhr", -> cy .visit("http://localhost:3500/fixtures/jquery.html") @@ -934,6 +956,18 @@ describe "src/cy/commands/querying", -> .get("@getUsers.2").then (xhr2) -> expect(xhr2.url).to.include "/users?num=2" + it "returns the 2nd xhr when dots in alias", -> + cy.visit("http://localhost:3500/fixtures/jquery.html") + cy.server() + cy.route(/users/, {}).as("get.users") + cy.window().then { timeout: 2000 }, (win) -> + Promise.all([ + win.$.get("/users", {num: 1}) + win.$.get("/users", {num: 2}) + ]) + cy.get("@get.users.2").then (xhr2) -> + expect(xhr2.url).to.include "/users?num=2" + it "returns the 3rd xhr as null", -> cy .server() @@ -1627,4 +1661,4 @@ describe "src/cy/commands/querying", -> cy.on "command:retry", _.after 2, -> Cypress.stop() - cy.contains(/^does not contain asdfasdf at all$/) + cy.contains(/^does not contain asdfasdf at all$/) \ No newline at end of file diff --git a/packages/driver/test/cypress/integration/commands/waiting_spec.coffee b/packages/driver/test/cypress/integration/commands/waiting_spec.coffee index 7f14aaad37e1..5fadf4edfed6 100644 --- a/packages/driver/test/cypress/integration/commands/waiting_spec.coffee +++ b/packages/driver/test/cypress/integration/commands/waiting_spec.coffee @@ -379,7 +379,7 @@ describe "src/cy/commands/waiting", -> Cypress.config("requestTimeout", 200) cy.on "fail", (err) -> - expect(err.message).to.include "cy.wait() timed out waiting 200ms for the 3rd request to the route: 'getUsers'. No request ever occurred." + expect(err.message).to.include "cy.wait() timed out waiting 200ms for the 3rd request to the route: 'get.users'. No request ever occurred." done() cy.on "command:retry", => @@ -390,10 +390,9 @@ describe "src/cy/commands/waiting", -> win = cy.state("window") win.$.get("/users", {num: response}) - cy - .server() - .route(/users/, resp).as("getUsers") - .wait(["@getUsers", "@getUsers", "@getUsers"]) + cy.server() + cy.route(/users/, resp).as("get.users") + cy.wait(["@get.users", "@get.users", "@get.users"]) it "throws waiting for the 2nd response", (done) -> resp = {foo: "foo"} @@ -517,15 +516,14 @@ describe "src/cy/commands/waiting", -> _.defer => win.$.get("/timeout?ms=2002") cy.on "fail", (err) -> - expect(err.message).to.include "cy.wait() timed out waiting 500ms for the 1st request to the route: 'getThree'. No request ever occurred." + expect(err.message).to.include "cy.wait() timed out waiting 500ms for the 1st request to the route: 'get.three'. No request ever occurred." done() - cy - .server() - .route("/timeout?ms=2001").as("getOne") - .route("/timeout?ms=2002").as("getTwo") - .route(/three/, {}).as("getThree") - .wait(["@getOne", "@getTwo", "@getThree"]) + cy.server() + cy.route("/timeout?ms=2001").as("getOne") + cy.route("/timeout?ms=2002").as("getTwo") + cy.route(/three/, {}).as("get.three") + cy.wait(["@getOne", "@getTwo", "@get.three"]) it "throws when waiting on the 3rd response on array of aliases", (done) -> Cypress.config("requestTimeout", 200) @@ -570,17 +568,16 @@ describe "src/cy/commands/waiting", -> resp1 = {foo: "foo"} resp2 = {bar: "bar"} - cy - .server() - .route(/users/, resp1).as("getUsers") - .route(/posts/, resp2).as("getPosts") - .window().then (win) -> - win.$.get("/users") - win.$.get("/posts") - null - .wait(["@getUsers", "@getPosts"]).spread (xhr1, xhr2) -> - expect(xhr1.responseBody).to.deep.eq resp1 - expect(xhr2.responseBody).to.deep.eq resp2 + cy.server() + cy.route(/users/, resp1).as("getUsers") + cy.route(/posts/, resp2).as("get.posts") + cy.window().then (win) -> + win.$.get("/users") + win.$.get("/posts") + null + cy.wait(["@getUsers", "@get.posts"]).spread (xhr1, xhr2) -> + expect(xhr1.responseBody).to.deep.eq resp1 + expect(xhr2.responseBody).to.deep.eq resp2 describe "multiple separate alias waits", -> before -> @@ -918,4 +915,4 @@ describe "src/cy/commands/waiting", -> # Command: "wait" # "Waited For": _.str.clean(fn.toString()) # Retried: "3 times" - # } + # } \ No newline at end of file diff --git a/packages/driver/test/cypress/integration/commands/xhr_spec.coffee b/packages/driver/test/cypress/integration/commands/xhr_spec.coffee index 87501f75bdc1..497fec854fe9 100644 --- a/packages/driver/test/cypress/integration/commands/xhr_spec.coffee +++ b/packages/driver/test/cypress/integration/commands/xhr_spec.coffee @@ -188,7 +188,7 @@ describe "src/cy/commands/xhr", -> cy .server() - .route({url: /timeout/}).as("getTimeout") + .route({url: /timeout/}).as("get.timeout") .window().then (win) -> xhr = new win.XMLHttpRequest xhr.open("GET", "/timeout?ms=100") @@ -198,7 +198,7 @@ describe "src/cy/commands/xhr", -> xhr.onload = -> onloaded = true null - .wait("@getTimeout").then (xhr) -> + .wait("@get.timeout").then (xhr) -> expect(onloaded).to.be.true expect(onreadystatechanged).to.be.true expect(xhr.status).to.eq(200) @@ -2076,4 +2076,4 @@ describe "src/cy/commands/xhr", -> # .window().then (win) -> # win.$.get("/foo") # null - # .respond() + # .respond() \ No newline at end of file