diff --git a/packages/driver/src/cypress/runner.js b/packages/driver/src/cypress/runner.js index 98e6b9da690b..459a7f7dda02 100644 --- a/packages/driver/src/cypress/runner.js +++ b/packages/driver/src/cypress/runner.js @@ -889,6 +889,16 @@ const create = function (specWindow, mocha, Cypress, cy) { return _testsById[id] } + function hasTestAlreadyRun (test) { + if (Cypress._RESUMED_AT_TEST) { + if (+test.id.slice(1) < +Cypress._RESUMED_AT_TEST.slice(1)) { + return true + } + } + + return false + } + overrideRunnerHook(Cypress, _runner, getTestById, getTest, setTest, getTests) return { @@ -962,6 +972,27 @@ const create = function (specWindow, mocha, Cypress, cy) { break } + // if this isnt a hook, then the name is 'test' + const hookName = runnable.type === 'hook' ? getHookName(runnable) : 'test' + + // extract out the next(fn) which mocha uses to + // move to the next runnable - this will be our async seam + const _next = args[0] + + if (hasTestAlreadyRun(test)) { + // NOTE: this is a hack to work around another cypress bug + // where the currentTest of a global after hook + // can be the wrong test after top navigation occurs + // (no open issue since it isn't user-facing for the most part) + + // A failing after hook will also not show up as a failing test in open mode + // (only a visual bug - does not affect run mode) + // https://github.com/cypress-io/cypress/issues/2296 + if (!(hookName === 'after all' && runnable.parent.root)) { + return _next() + } + } + // closure for calculating the actual // runtime of a runnables fn exection duration // and also the run of the runnable:after:run:async event @@ -986,9 +1017,6 @@ const create = function (specWindow, mocha, Cypress, cy) { test.wallClockStartedAt = wallClockStartedAt } - // if this isnt a hook, then the name is 'test' - const hookName = runnable.type === 'hook' ? getHookName(runnable) : 'test' - // if we haven't yet fired this event for this test // that means that we need to reset the previous state // of cy - since we now have a new 'test' and all of the @@ -997,10 +1025,6 @@ const create = function (specWindow, mocha, Cypress, cy) { fire(TEST_BEFORE_RUN_EVENT, test, Cypress) } - // extract out the next(fn) which mocha uses to - // move to the next runnable - this will be our async seam - const _next = args[0] - const next = function (err) { // now set the duration of the after runnable run async event afterFnDurationEnd = (wallClockEnd = new Date()) diff --git a/packages/server/__snapshots__/3_issue_1987.ts.js b/packages/server/__snapshots__/3_issue_1987.ts.js new file mode 100644 index 000000000000..88179f6af777 --- /dev/null +++ b/packages/server/__snapshots__/3_issue_1987.ts.js @@ -0,0 +1,127 @@ +exports['e2e issue 1987 / can reload during spec run'] = ` + +==================================================================================================== + + (Run Starting) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Cypress: 1.2.3 │ + │ Browser: FooBrowser 88 │ + │ Specs: 1 found (beforehook-and-test-navigation.js) │ + │ Searched: cypress/integration/beforehook-and-test-navigation.js │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + +──────────────────────────────────────────────────────────────────────────────────────────────────── + + Running: beforehook-and-test-navigation.js (1 of 1) + + + suite + ✓ test + ✓ causes domain navigation + + + 2 passing + + + (Results) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Tests: 2 │ + │ Passing: 2 │ + │ Failing: 0 │ + │ Pending: 0 │ + │ Skipped: 0 │ + │ Screenshots: 0 │ + │ Video: true │ + │ Duration: X seconds │ + │ Spec Ran: beforehook-and-test-navigation.js │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /XXX/XXX/XXX/cypress/videos/beforehook-and-test-navigation. (X second) + js.mp4 + + +==================================================================================================== + + (Run Finished) + + + Spec Tests Passing Failing Pending Skipped + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ ✔ beforehook-and-test-navigation.js XX:XX 2 2 - - - │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + ✔ All specs passed! XX:XX 2 2 - - - + + +` + +exports['e2e issue 1987 / can run proper amount of hooks'] = ` + +==================================================================================================== + + (Run Starting) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Cypress: 1.2.3 │ + │ Browser: FooBrowser 88 │ + │ Specs: 1 found (afterhooks.spec.js) │ + │ Searched: cypress/integration/afterhooks.spec.js │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + +──────────────────────────────────────────────────────────────────────────────────────────────────── + + Running: afterhooks.spec.js (1 of 1) + + + suite 1 + ✓ test 1 + ✓ test 2 + + suite 2 + ✓ s2t1 + + + 3 passing + + + (Results) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Tests: 3 │ + │ Passing: 3 │ + │ Failing: 0 │ + │ Pending: 0 │ + │ Skipped: 0 │ + │ Screenshots: 0 │ + │ Video: true │ + │ Duration: X seconds │ + │ Spec Ran: afterhooks.spec.js │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /XXX/XXX/XXX/cypress/videos/afterhooks.spec.js.mp4 (X second) + + +==================================================================================================== + + (Run Finished) + + + Spec Tests Passing Failing Pending Skipped + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ ✔ afterhooks.spec.js XX:XX 3 3 - - - │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + ✔ All specs passed! XX:XX 3 3 - - - + + +` diff --git a/packages/server/test/e2e/3_issue_1987.ts b/packages/server/test/e2e/3_issue_1987.ts new file mode 100644 index 000000000000..8917bbd0b8d6 --- /dev/null +++ b/packages/server/test/e2e/3_issue_1987.ts @@ -0,0 +1,29 @@ +const e2e = require('../support/helpers/e2e') +const Fixtures = require('../support/helpers/fixtures') + +describe('e2e issue 1987', () => { + e2e.setup({ + servers: [{ + port: 3434, + static: true, + }, + { + port: 4545, + static: true, + }], + }) + + // https://github.com/cypress-io/cypress/issues/1987 + // before/after hooks should not be rerun on top navigation + e2e.it('can reload during spec run', { + project: Fixtures.projectPath('hooks-after-rerun'), + spec: 'beforehook-and-test-navigation.js', + snapshot: true, + }) + + e2e.it('can run proper amount of hooks', { + project: Fixtures.projectPath('hooks-after-rerun'), + spec: 'afterhooks.spec.js', + snapshot: true, + }) +}) diff --git a/packages/server/test/integration/cypress_spec.js b/packages/server/test/integration/cypress_spec.js index f49a17687c40..b302d0fa984a 100644 --- a/packages/server/test/integration/cypress_spec.js +++ b/packages/server/test/integration/cypress_spec.js @@ -105,7 +105,7 @@ describe('lib/cypress', () => { require('mocha-banner').register() beforeEach(function () { - this.timeout(5000) + this.timeout(8000) cache.__removeSync() diff --git a/packages/server/test/support/fixtures/projects/hooks-after-rerun/cypress.json b/packages/server/test/support/fixtures/projects/hooks-after-rerun/cypress.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/packages/server/test/support/fixtures/projects/hooks-after-rerun/cypress.json @@ -0,0 +1 @@ +{} diff --git a/packages/server/test/support/fixtures/projects/hooks-after-rerun/cypress/integration/afterhooks.spec.js b/packages/server/test/support/fixtures/projects/hooks-after-rerun/cypress/integration/afterhooks.spec.js new file mode 100644 index 000000000000..0111858e1d13 --- /dev/null +++ b/packages/server/test/support/fixtures/projects/hooks-after-rerun/cypress/integration/afterhooks.spec.js @@ -0,0 +1,41 @@ +describe('suite 1', () => { + before(() => { + cy.task('incrState', 'b1') + }) + + it('test 1', () => { + cy.task('incrState', 't1') + cy.visit('http://localhost:3434') + }) + + it('test 2', () => { + cy.task('incrState', 't2') + }) + + after(() => { + cy.task('incrState', 'a1') + }) +}) + +describe('suite 2', () => { + it('s2t1', () => { + cy.task('incrState', 's2t1') + cy.visit('http://localhost:4545') + }) +}) + +after(() => { + cy.task('incrState', 'a2') + cy.task('getState').then((state) => { + expect(state).deep.eq({ + // initial domain change causes 2 runs + 'b1': 2, + 't1': 2, + 't2': 1, + 'a1': 1, + // domain change causes 2 runs + 's2t1': 2, + 'a2': 1, + }) + }) +}) diff --git a/packages/server/test/support/fixtures/projects/hooks-after-rerun/cypress/integration/beforehook-and-test-navigation.js b/packages/server/test/support/fixtures/projects/hooks-after-rerun/cypress/integration/beforehook-and-test-navigation.js new file mode 100644 index 000000000000..66f1728ba86d --- /dev/null +++ b/packages/server/test/support/fixtures/projects/hooks-after-rerun/cypress/integration/beforehook-and-test-navigation.js @@ -0,0 +1,13 @@ +describe('suite', () => { + before(() => { + // will cause infinite top navigation + cy.visit('http://localhost:3434') + }) + + it('test', () => { + }) + + it('causes domain navigation', () => { + cy.visit('http://localhost:4545') + }) +}) diff --git a/packages/server/test/support/fixtures/projects/hooks-after-rerun/cypress/plugins/index.js b/packages/server/test/support/fixtures/projects/hooks-after-rerun/cypress/plugins/index.js new file mode 100644 index 000000000000..3980b57a95f3 --- /dev/null +++ b/packages/server/test/support/fixtures/projects/hooks-after-rerun/cypress/plugins/index.js @@ -0,0 +1,33 @@ +/// +// *********************************************************** +// This example plugins/index.js can be used to load plugins +// +// You can change the location of this file or turn off loading +// the plugins file with the 'pluginsFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/plugins-guide +// *********************************************************** + +// This function is called when a project is opened or re-opened (e.g. due to +// the project's config changing) + +const state = {} + +/** + * @type {Cypress.PluginConfig} + */ +module.exports = (on, config) => { + on('task', { + incrState (arg) { + state[arg] = state[arg] + 1 || 1 + + return null + }, + getState () { + return state + } }) + + // `on` is used to hook into various events Cypress emits + // `config` is the resolved Cypress config +} diff --git a/packages/server/test/support/fixtures/projects/hooks-after-rerun/cypress/support/commands.js b/packages/server/test/support/fixtures/projects/hooks-after-rerun/cypress/support/commands.js new file mode 100644 index 000000000000..ca4d256f3eb1 --- /dev/null +++ b/packages/server/test/support/fixtures/projects/hooks-after-rerun/cypress/support/commands.js @@ -0,0 +1,25 @@ +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add("login", (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) diff --git a/packages/server/test/support/fixtures/projects/hooks-after-rerun/cypress/support/index.js b/packages/server/test/support/fixtures/projects/hooks-after-rerun/cypress/support/index.js new file mode 100644 index 000000000000..d68db96df269 --- /dev/null +++ b/packages/server/test/support/fixtures/projects/hooks-after-rerun/cypress/support/index.js @@ -0,0 +1,20 @@ +// *********************************************************** +// This example support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands' + +// Alternatively you can use CommonJS syntax: +// require('./commands')