From 16f52cc0f9cb71b187747e7f7a91935633400f40 Mon Sep 17 00:00:00 2001 From: Lila Conlee Date: Fri, 18 Jan 2019 05:36:43 -1000 Subject: [PATCH] Update alias UI (#2960) - Fixes #477 --- circle.yml | 3 + .../driver/src/cy/commands/querying.coffee | 2 +- .../driver/src/cy/commands/waiting.coffee | 15 +- .../integration/commands/querying_spec.coffee | 4 +- .../integration/commands/waiting_spec.coffee | 23 +- .../integration/commands/window_spec.coffee | 4 +- packages/reporter/cypress.json | 1 + .../cypress/fixtures/aliases_runnables.json | 13 + ...runnables.json => controls_runnables.json} | 0 .../cypress/integration/aliases_spec.coffee | 222 ++++++++++++++++++ .../cypress/integration/controls_spec.coffee | 2 +- packages/reporter/package.json | 4 +- packages/reporter/src/commands/command.jsx | 51 +++- .../reporter/src/commands/command.spec.jsx | 29 ++- packages/reporter/src/commands/commands.scss | 45 ++++ packages/reporter/src/hooks/hook-model.js | 24 +- .../reporter/src/hooks/hook-model.spec.js | 21 ++ packages/reporter/src/hooks/hooks.jsx | 2 +- 18 files changed, 431 insertions(+), 34 deletions(-) create mode 100644 packages/reporter/cypress/fixtures/aliases_runnables.json rename packages/reporter/cypress/fixtures/{runnables.json => controls_runnables.json} (100%) create mode 100644 packages/reporter/cypress/integration/aliases_spec.coffee diff --git a/circle.yml b/circle.yml index a1047dc2f05d..3aa06ae90c4d 100644 --- a/circle.yml +++ b/circle.yml @@ -717,6 +717,9 @@ linux-workflow: &linux-workflow - desktop-gui-integration-tests-2x: requires: - build + - reporter-integration-tests: + requires: + - build - run-launcher: requires: - build diff --git a/packages/driver/src/cy/commands/querying.coffee b/packages/driver/src/cy/commands/querying.coffee index 943af26ca7e1..42f97ba700ce 100644 --- a/packages/driver/src/cy/commands/querying.coffee +++ b/packages/driver/src/cy/commands/querying.coffee @@ -84,7 +84,7 @@ module.exports = (Commands, Cypress, cy, state, config) -> options._log ?= Cypress.log message: selector - referencesAlias: aliasObj?.alias + referencesAlias: if aliasObj?.alias then {name: aliasObj.alias} aliasType: aliasType consoleProps: -> consoleProps diff --git a/packages/driver/src/cy/commands/waiting.coffee b/packages/driver/src/cy/commands/waiting.coffee index 0fcdbf789a73..c2eb47718b5e 100644 --- a/packages/driver/src/cy/commands/waiting.coffee +++ b/packages/driver/src/cy/commands/waiting.coffee @@ -82,13 +82,22 @@ module.exports = (Commands, Cypress, cy, state, config) -> type = cy.getXhrTypeByAlias(str) + [ index, num ] = getNumRequests(state, alias) + ## if we have a command then continue to ## build up an array of referencesAlias ## because wait can reference an array of aliases if log referencesAlias = log.get("referencesAlias") ? [] aliases = [].concat(referencesAlias) - aliases.push(str) + + if str + aliases.push({ + name: str + cardinal: index + 1, + ordinal: num + }) + log.set "referencesAlias", aliases if command.get("name") isnt "route" @@ -104,8 +113,6 @@ module.exports = (Commands, Cypress, cy, state, config) -> requestTimeout = options.requestTimeout ? timeout responseTimeout = options.responseTimeout ? timeout - [ index, num ] = getNumRequests(state, alias) - waitForRequest = -> options = _.omit(options, "_runnableTimeout") options.timeout = requestTimeout ? Cypress.config("requestTimeout") @@ -136,7 +143,7 @@ module.exports = (Commands, Cypress, cy, state, config) -> if log log.set "consoleProps", -> { - "Waited For": (log.get("referencesAlias") || []).join(", ") + "Waited For": (_.map(log.get("referencesAlias"), 'name') || []).join(", ") "Yielded": ret } diff --git a/packages/driver/test/cypress/integration/commands/querying_spec.coffee b/packages/driver/test/cypress/integration/commands/querying_spec.coffee index 56d2b5fd205a..84684b6c9aea 100644 --- a/packages/driver/test/cypress/integration/commands/querying_spec.coffee +++ b/packages/driver/test/cypress/integration/commands/querying_spec.coffee @@ -739,7 +739,7 @@ describe "src/cy/commands/querying", -> .get("@getUsers").then -> expect(@lastLog.pick("message", "referencesAlias", "aliasType")).to.deep.eq { message: "@getUsers" - referencesAlias: "getUsers" + referencesAlias: {name: "getUsers"} aliasType: "route" } @@ -747,7 +747,7 @@ describe "src/cy/commands/querying", -> cy.on "log:added", (attrs, log) -> if attrs.name is "get" expect(log.pick("$el", "numRetries", "referencesAlias", "aliasType")).to.deep.eq { - referencesAlias: "f" + referencesAlias: {name: "f"} aliasType: "primitive" } done() diff --git a/packages/driver/test/cypress/integration/commands/waiting_spec.coffee b/packages/driver/test/cypress/integration/commands/waiting_spec.coffee index 17101108edb6..292618683488 100644 --- a/packages/driver/test/cypress/integration/commands/waiting_spec.coffee +++ b/packages/driver/test/cypress/integration/commands/waiting_spec.coffee @@ -770,7 +770,7 @@ describe "src/cy/commands/waiting", -> cy.on "test:fail", (err) => obj = { name: "wait" - referencesAlias: ["getFoo"] + referencesAlias: [{name: 'getFoo', cardinal: 1, ordinal: "1st"}] aliasType: "route" type: "parent" error: err @@ -847,11 +847,28 @@ describe "src/cy/commands/waiting", -> .window().then (win) -> xhrGet(win, "/foo") xhrGet(win, "/bar") + xhrGet(win, "/foo") null - .wait(["@getFoo", "@getBar"]).then (xhrs) -> + .wait(["@getFoo", "@getBar", "@getFoo"]).then (xhrs) -> lastLog = @lastLog - expect(lastLog.get("referencesAlias")).to.deep.eq ["getFoo", "getBar"] + expect(lastLog.get("referencesAlias")).to.deep.eq [ + { + name: "getFoo", + cardinal: 1, + ordinal: '1st' + }, + { + name: "getBar", + cardinal: 1, + ordinal: '1st' + }, + { + name: "getFoo", + cardinal: 2, + ordinal: '2nd' + } + ] it "#consoleProps waiting on 1 alias", -> cy diff --git a/packages/driver/test/cypress/integration/commands/window_spec.coffee b/packages/driver/test/cypress/integration/commands/window_spec.coffee index 4aa8936f904e..658d7d39e3c0 100644 --- a/packages/driver/test/cypress/integration/commands/window_spec.coffee +++ b/packages/driver/test/cypress/integration/commands/window_spec.coffee @@ -123,7 +123,7 @@ describe "src/cy/commands/window", -> expect(@logs[0].get("aliasType")).to.eq("primitive") expect(@logs[2].get("aliasType")).to.eq("primitive") - expect(@logs[2].get("referencesAlias")).to.eq("win") + expect(@logs[2].get("referencesAlias").name).to.eq("win") it "logs obj", -> cy.window().then -> @@ -270,7 +270,7 @@ describe "src/cy/commands/window", -> expect(logs[0].get("aliasType")).to.eq("primitive") expect(logs[2].get("aliasType")).to.eq("primitive") - expect(logs[2].get("referencesAlias")).to.eq("doc") + expect(logs[2].get("referencesAlias").name).to.eq("doc") it "logs obj", -> cy.document().then -> diff --git a/packages/reporter/cypress.json b/packages/reporter/cypress.json index b16929a9f710..40950ebabc22 100644 --- a/packages/reporter/cypress.json +++ b/packages/reporter/cypress.json @@ -1,4 +1,5 @@ { + "projectId": "ypt4pf", "viewportWidth": 400, "viewportHeight": 450, "supportFile": false, diff --git a/packages/reporter/cypress/fixtures/aliases_runnables.json b/packages/reporter/cypress/fixtures/aliases_runnables.json new file mode 100644 index 000000000000..6626864d36a9 --- /dev/null +++ b/packages/reporter/cypress/fixtures/aliases_runnables.json @@ -0,0 +1,13 @@ +{ + "id": "r1", + "title": "", + "root": true, + "suites": [], + "tests": [ + { + "id": "r3", + "title": "test 1", + "state": "passed" + } + ] +} diff --git a/packages/reporter/cypress/fixtures/runnables.json b/packages/reporter/cypress/fixtures/controls_runnables.json similarity index 100% rename from packages/reporter/cypress/fixtures/runnables.json rename to packages/reporter/cypress/fixtures/controls_runnables.json diff --git a/packages/reporter/cypress/integration/aliases_spec.coffee b/packages/reporter/cypress/integration/aliases_spec.coffee new file mode 100644 index 000000000000..435d6d408388 --- /dev/null +++ b/packages/reporter/cypress/integration/aliases_spec.coffee @@ -0,0 +1,222 @@ +{ EventEmitter } = require("events") +_ = Cypress._ + +addLog = (runner, log) -> + defaultLog = { + event: false + hookName: "test" + id: _.uniqueId('l') + instrument: "command" + renderProps: {} + state: "passed" + testId: "r3" + type: "parent" + url: "http://example.com" + } + + runner.emit("reporter:log:add", _.extend(defaultLog, log)) + +describe "aliases", -> + beforeEach -> + cy.fixture("aliases_runnables").as("runnables") + + @runner = new EventEmitter() + + cy.visit("cypress/support/index.html").then (win) => + win.render({ + runner: @runner + specPath: "/foo/bar" + }) + + cy.get(".reporter").then => + @runner.emit("runnables:ready", @runnables) + @runner.emit("reporter:start", {}) + + + describe "without duplicates", -> + beforeEach -> + addLog(@runner, { + alias: "getUsers" + aliasType: "route" + displayName: "xhr stub" + event: true + name: "xhr" + renderProps: {message: "GET --- /users", indicator: "passed"} + }) + addLog(@runner, { + aliasType: "route" + message: "@getUsers, function(){}" + name: "wait" + referencesAlias: [{ + cardinal: 1 + name: "getUsers" + ordinal: "1st" + }], + }) + + it "render without a count", -> + cy.contains('.command-number', '1') + .parent() + .within -> + cy.get('.command-alias-count').should('not.exist') + cy.contains('.command-alias', '@getUsers') + .trigger("mouseover") + + cy.get(".tooltip span").should ($tooltip) -> + expect($tooltip).to.contain("Found an alias for: 'getUsers'") + + describe "with consecutive duplicates", -> + beforeEach -> + addLog(@runner, { + alias: "getPosts" + aliasType: "route" + displayName: "xhr stub" + event: true + name: "xhr" + renderProps: {message: "GET --- /posts", indicator: "passed"} + }) + addLog(@runner, { + alias: "getPosts" + aliasType: "route" + displayName: "xhr stub" + event: true + name: "xhr" + renderProps: {message: "GET --- /posts", indicator: "passed"} + }) + addLog(@runner, { + aliasType: "route" + message: "@getPosts, function(){}" + name: "wait" + referencesAlias: [{ + cardinal: 1 + name: "getPosts" + ordinal: "1st" + }], + }) + addLog(@runner, { + aliasType: "route" + message: "@getPosts, function(){}" + name: "wait" + referencesAlias: [{ + cardinal: 2 + name: "getPosts" + ordinal: "2nd" + }], + }) + + it "render with counts in non-event commands", -> + cy.contains('.command-number', '1') + .parent() + .within -> + cy.contains('.command-alias-count', '1') + cy.contains('.command-alias', '@getPosts') + .trigger("mouseover") + + cy.get(".tooltip span").should ($tooltip) -> + expect($tooltip).to.contain("Found 1st alias for: 'getPosts'") + + cy.contains('.command-number', '2') + .parent() + .within -> + cy.contains('.command-alias-count', '2') + cy.contains('.command-alias', '@getPosts') + .trigger("mouseover") + + cy.get(".tooltip span").should ($tooltip) -> + expect($tooltip).to.contain("Found 2nd alias for: 'getPosts'") + + it "render with counts in event commands when collapsed", -> + cy.get(".command-wrapper") + .first() + .within -> + cy.contains('.num-duplicates', '2') + cy.contains('.command-alias', 'getPosts') + + it "render without counts in event commands when expanded", -> + cy.get(".command-expander") + .first() + .click() + + cy.get(".command-wrapper") + .first() + .within ($commandWrapper) -> + cy.get('.num-duplicates').should('not.be.visible') + cy.contains('.command-alias', 'getPosts') + + describe "with non-consecutive duplicates", -> + beforeEach -> + addLog(@runner, { + alias: "getPosts" + aliasType: "route" + displayName: "xhr stub" + event: true + name: "xhr" + renderProps: {message: "GET --- /posts", indicator: "passed"} + }) + addLog(@runner, { + alias: "getUsers" + aliasType: "route" + displayName: "xhr stub" + event: true + name: "xhr" + renderProps: {message: "GET --- /users", indicator: "passed"} + }) + addLog(@runner, { + alias: "getPosts" + aliasType: "route" + displayName: "xhr stub" + event: true + name: "xhr" + renderProps: {message: "GET --- /posts", indicator: "passed"} + }) + addLog(@runner, { + aliasType: "route" + message: "@getPosts, function(){}" + name: "wait" + referencesAlias: [{ + cardinal: 1 + name: "getPosts" + ordinal: "1st" + }], + }) + addLog(@runner, { + aliasType: "route" + message: "@getUsers, function(){}" + name: "wait" + referencesAlias: [{ + cardinal: 1 + name: "getUsers" + ordinal: "1st" + }], + }) + addLog(@runner, { + aliasType: "route" + message: "@getPosts, function(){}" + name: "wait" + referencesAlias: [{ + cardinal: 2 + name: "getPosts" + ordinal: "2nd" + }], + }) + + it "render with counts", -> + cy.contains('.command-number', '1') + .parent() + .within -> + cy.contains('.command-alias-count', '1') + cy.contains('.command-alias', '@getPosts') + .trigger("mouseover") + + cy.get(".tooltip span").should ($tooltip) -> + expect($tooltip).to.contain("Found 1st alias for: 'getPosts'") + + cy.contains('.command-number', '3') + .parent() + .within -> + cy.contains('.command-alias-count', '2') + cy.contains('.command-alias', '@getPosts') + .trigger("mouseover") + + cy.get(".tooltip span").should ($tooltip) -> + expect($tooltip).to.contain("Found 2nd alias for: 'getPosts'") diff --git a/packages/reporter/cypress/integration/controls_spec.coffee b/packages/reporter/cypress/integration/controls_spec.coffee index a30e73226b48..9d3c24e799f2 100644 --- a/packages/reporter/cypress/integration/controls_spec.coffee +++ b/packages/reporter/cypress/integration/controls_spec.coffee @@ -2,7 +2,7 @@ describe "controls", -> beforeEach -> - cy.fixture("runnables").as("runnables") + cy.fixture("controls_runnables").as("runnables") @runner = new EventEmitter() diff --git a/packages/reporter/package.json b/packages/reporter/package.json index f17a0fcfd6ff..6ade931eb95e 100644 --- a/packages/reporter/package.json +++ b/packages/reporter/package.json @@ -19,7 +19,9 @@ "clean-deps": "rm -rf node_modules", "pretest": "npm run check-deps-pre", "test": "node ./scripts/test.js", - "lint": "bin-up eslint --fix lib/*.js scripts/*.js src/*.js* src/**/*.js*" + "lint": "bin-up eslint --fix lib/*.js scripts/*.js src/*.js* src/**/*.js*", + "cypress:open": "node ../../scripts/cypress open --project .", + "cypress:run": "node ../../scripts/cypress run --project ." }, "files": [ "dist" diff --git a/packages/reporter/src/commands/command.jsx b/packages/reporter/src/commands/command.jsx index 98c7f7f92943..10de4f1cf68f 100644 --- a/packages/reporter/src/commands/command.jsx +++ b/packages/reporter/src/commands/command.jsx @@ -24,24 +24,47 @@ const visibleMessage = (model) => { 'This element is not visible.' } -const AliasesReferences = observer(({ model }) => ( - - {_.map([].concat(model.referencesAlias), (alias) => ( - - @{alias} +const shouldShowCount = (aliasesWithDuplicates, aliasName) => { + return _.includes(aliasesWithDuplicates, aliasName) +} + +const AliasReference = observer(({ model, aliasObj, aliasesWithDuplicates }) => { + if (shouldShowCount(aliasesWithDuplicates, aliasObj.name)) { + return ( + + + @{aliasObj.name} + {aliasObj.cardinal} + + ) + } + + return ( + + @{aliasObj.name} + + ) +}) + +const AliasesReferences = observer(({ model, aliasesWithDuplicates }) => ( + + {_.map([].concat(model.referencesAlias), (aliasObj) => ( + + + ))} )) -const Aliases = observer(({ model }) => { +const Aliases = observer(({ model, aliasesWithDuplicates }) => { if (!model.alias) return null return ( {_.map([].concat(model.alias), (alias) => ( - {alias} + 1) ? shouldShowCount(aliasesWithDuplicates, alias) : false })}>{alias} ))} @@ -69,7 +92,7 @@ class Command extends Component { } render () { - const { model } = this.props + const { model, aliasesWithDuplicates } = this.props const message = model.displayMessage return ( @@ -117,19 +140,21 @@ class Command extends Component { {model.event ? `(${displayName(model)})` : displayName(model)} - {model.referencesAlias ? : } + {model.referencesAlias ? : } - {model.numElements} - - {model.numDuplicates} - + + + + {model.numDuplicates} + + diff --git a/packages/reporter/src/commands/command.spec.jsx b/packages/reporter/src/commands/command.spec.jsx index 15c502c533b4..705e8f42e8f0 100644 --- a/packages/reporter/src/commands/command.spec.jsx +++ b/packages/reporter/src/commands/command.spec.jsx @@ -1,4 +1,4 @@ -import { shallow } from 'enzyme' +import { shallow, mount } from 'enzyme' import _ from 'lodash' import React from 'react' import sinon from 'sinon' @@ -180,11 +180,11 @@ describe('', () => { let aliases beforeEach(() => { - aliases = shallow().find(AliasesReferences).shallow() + aliases = mount().find(AliasesReferences) }) it('renders the aliases for each one it references', () => { - expect(aliases.find('.command-alias').length).to.equal(2) + expect(aliases.find('.command-alias').length).to.equal(3) }) it('renders the aliases with the right class', () => { @@ -197,14 +197,21 @@ describe('', () => { }) it('renders tooltip for each alias it references', () => { - expect(aliases.find('Tooltip').length).to.equal(2) + expect(aliases.find('Tooltip').length).to.equal(3) }) it('renders the right tooltip title for each alias it references', () => { const tooltips = aliases.find('Tooltip') expect(tooltips.first()).to.have.prop('title', 'Found an alias for: \'barAlias\'') - expect(tooltips.last()).to.have.prop('title', 'Found an alias for: \'bazAlias\'') + expect(tooltips.last()).to.have.prop('title', 'Found 2nd alias for: \'bazAlias\'') + }) + + it('only renders the count for aliases with duplicates', () => { + const commandAliasContainers = aliases.find('.command-alias-container') + + expect(commandAliasContainers.first().find('.command-alias-count')).to.not.exist + expect(commandAliasContainers.last().find('.command-alias-count')).to.have.text('2') }) }) @@ -489,6 +496,18 @@ describe('', () => { expect(component).not.to.have.className('command-is-duplicate') }) + it('num duplicates renders with has-alias class if command is an alias', () => { + const component = shallow() + + expect(component.find('.num-duplicates')).to.have.className('has-alias') + }) + + it('num duplicates renders without has-alias class if command is not an alias', () => { + const component = shallow() + + expect(component.find('.num-duplicates')).not.to.have.className('has-alias') + }) + it('displays number of duplicates', () => { const component = shallow() diff --git a/packages/reporter/src/commands/commands.scss b/packages/reporter/src/commands/commands.scss index 87cb9aa8756d..a3d59c7fb992 100644 --- a/packages/reporter/src/commands/commands.scss +++ b/packages/reporter/src/commands/commands.scss @@ -122,6 +122,46 @@ &.primitive { background-color: darken(#FFE0DE, 3%); } + + &.show-count { + border-radius: 10px 0 0 10px; + } + } + + // ensures alias & number of duplicates don't break if reporter + // width is narrow + .alias-container { + white-space: nowrap; + + > { + display: inline-block; + } + } + + .num-duplicates, + .command-alias-count { + border-radius: 5px; + color: #777; + font-size: 90%; + font-style: normal; + line-height: 1; + margin-left: 0; + } + + .num-duplicates.has-alias, + .command-alias-count { + border-radius: 0 10px 10px 0; + padding: 0px 6px 1px 4px; + } + + .num-duplicates, + .command-alias-count { + background-color: lighten(#ffdf9c, 8%); + } + + .command-alias-count { + display: inline; + padding: 2px 6px 2px 4px; } } @@ -205,6 +245,7 @@ .command-message-text { display: block; + flex-grow: 2; overflow: hidden; text-overflow: ellipsis; } @@ -439,6 +480,10 @@ .num-duplicates { display: none; } + + .command-alias { + border-radius: 10px !important; + } } .command-is-pinned, diff --git a/packages/reporter/src/hooks/hook-model.js b/packages/reporter/src/hooks/hook-model.js index a7c97a28466a..c49f51257c5d 100644 --- a/packages/reporter/src/hooks/hook-model.js +++ b/packages/reporter/src/hooks/hook-model.js @@ -1,5 +1,5 @@ import _ from 'lodash' -import { observable } from 'mobx' +import { observable, computed } from 'mobx' export default class Hook { @observable id @@ -13,6 +13,28 @@ export default class Hook { this.name = props.name } + @computed get aliasesWithDuplicates () { + // Consecutive duplicates only appear once in command array, but hasDuplicates is true + // Non-consecutive duplicates appear multiple times in command array, but hasDuplicates is false + // This returns aliases that have consecutive or non-consecutive duplicates + let consecutiveDuplicateAliases = [] + const aliases = this.commands.map((command) => { + if (command.alias) { + if (command.hasDuplicates) { + consecutiveDuplicateAliases.push(command.alias) + } + + return command.alias + } + }) + + const nonConsecutiveDuplicateAliases = aliases.filter((alias, i) => { + return aliases.indexOf(alias) === i && aliases.lastIndexOf(alias) !== i + }) + + return consecutiveDuplicateAliases.concat(nonConsecutiveDuplicateAliases) + } + addCommand (command) { if (!command.event) { command.number = this._currentNumber diff --git a/packages/reporter/src/hooks/hook-model.spec.js b/packages/reporter/src/hooks/hook-model.spec.js index a74e8c81d68b..9925c4f2ca6b 100644 --- a/packages/reporter/src/hooks/hook-model.spec.js +++ b/packages/reporter/src/hooks/hook-model.spec.js @@ -107,4 +107,25 @@ describe('Hook model', () => { expect(hook.commandMatchingErr({ displayMessage: 'matching error message' })).to.be.undefined }) }) + + context('#aliasesWithDuplicates', () => { + it('returns duplicates marked with hasDuplicates and those that appear mulitple times in the commands array', () => { + hook.addCommand({ isMatchingEvent: () => { + return false + }, alias: 'foo' }) + hook.addCommand({ isMatchingEvent: () => { + return false + }, alias: 'bar' }) + hook.addCommand({ isMatchingEvent: () => { + return false + }, alias: 'foo' }) + hook.addCommand({ isMatchingEvent: () => { + return false + }, alias: 'baz', hasDuplicates: true }) + + expect(hook.aliasesWithDuplicates).to.include('foo') + expect(hook.aliasesWithDuplicates).to.include('baz') + expect(hook.aliasesWithDuplicates).to.not.include('bar') + }) + }) }) diff --git a/packages/reporter/src/hooks/hooks.jsx b/packages/reporter/src/hooks/hooks.jsx index 1ff0e879ecc5..2a9c96eb95bc 100644 --- a/packages/reporter/src/hooks/hooks.jsx +++ b/packages/reporter/src/hooks/hooks.jsx @@ -19,7 +19,7 @@ const Hook = observer(({ model }) => ( isOpen={true} >
    - {_.map(model.commands, (command) => )} + {_.map(model.commands, (command) => )}