cypress: cy.task() calls do not fire in 'test:after:run' event block

Current behavior:

Attempting to fire cy.task() commands after a test completes appears to outright fail.

Desired behavior:

At the end of each test, I would expect the cy.task command to fire, running a block of code within the task registered in cypress/plugins/index.js

Steps to reproduce: (app code and test code)

Simple Test

it('Can navigate to Account page', () => {
    cy.get('nav').within(() => {
      cy.getByText('Account').should('have.attr', 'href', '/parent/settings');
    });
  });

In cypress/support/index.js I have the following event listener enabled:

Cypress.on('test:after:run', (testAttr, runnable) => {
    cy.task('logTestResults', {
      title: `${runnable.parent.title}${runnable.title}`,
      state: runnable.state,
      err: runnable.err,
    });
  });

And that should fire this task in cypress/plugins/index.js:

on('task', {
      logTestResults({ title, state, err }) {
        console.log('made it to the task');
        const tags = [];
        tags.push(`outcome:${state}`);
        tags.push(`description:${changeCase.dot(title)}`)
        if (err) {
          tags.push(`error:${changeCase.dot(err.name)}`);
          tags.push(`error.message:${changeCase.dot(err.message)}`);
        }
        dogapi.metric.send(`e2e`, 1, {tags:tags}, function(err, results){
          console.dir(results);
        });

        return null
      }
    });

Through console logging I can tell that the test:after:run event listener is working, as console logs appear in the cypress runner’s JS console. However console logs within the plugin task do not get written to my terminal.

However, if I add a cy.task command DIRECTLY to my test like so:

it('Can navigate to Account page', () => {
    cy.task('logTestResults', {title: 'test', state: 'failed', err:{name:'testerr', message:'test err message'}});
    cy.get('nav').within(() => {
      cy.getByText('Account').should('have.attr', 'href', '/parent/settings');
    });
  });

The task registered in the plugins file DOES catch the command and logs the faked details I pass it. Why is that same cy.task command not working in my test:after:run event listener?

Versions

Cypress: 3.4.0

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 9
  • Comments: 19 (5 by maintainers)

Most upvoted comments

@drewbrend maybe a bit late, but as I can see you can reference the current test in afterEach hook with:

afterEach(() => {
  // @ts-ignore
  const test = cy.state('runnable')?.ctx?.currentTest;
  if (test) {
    // stuffs here
  }
});

We got to add cy.state method to our typescript definitions even if just a few properties, then our docs would be enough for people to build whatever they want I think

Sent from my iPhone

On Dec 12, 2019, at 19:11, Brian Mann notifications@github.com wrote:

The test:after:run event is synchronous and will not respect async cypress commands - and likely never will. Hooks before, beforeEach, afterEach, and after are the only places you’ll be able to put cypress commands because of how Cypress must tie hooks + tests together.

I understand what you all are trying to do - getting access to the currentTest properties from inside of a hook. This data should already be available to you as cy.state(‘runnable’).currentTest.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub, or unsubscribe.

So, I figured out how to get this to work in an after hook by learning more about the context available in an after hook. Here’s how I got it working:

cypress/support/index.js

after(() => {
  const findTests = suite => {
    suite.tests.forEach(test => {
      cy.task('logTestResults', {
        title: `${test.parent.title}${test.title}`,
        state: test.state,
      });
    });

    if (suite.suites.length < 1) {
      return;
    }
    suite.suites.forEach(nestedSuite => {
      findTests(nestedSuite);
    });
  };

  cy.state('runnable').parent.suites.forEach(suite => {
    findTests(suite);
  });
});

However I still feel that the test:after:run event should be able to fire cy.task() commands so I will leave this issue open.

@jennifer-shehane I want to implement the full capture of page when some failure happens. (The documentation explains in this case, the capture is always coerced to runner: https://docs.cypress.io/api/cypress-api/screenshot-api#Arguments)

So I thought something like that:

Cypress.Screenshot.defaults({
    capture: 'fullPage'
})

Cypress.on('test:after:run', (test, runnable) => {
    if (test.state === 'failed') {
        cy.screenshot()
    }
})

But I cannot call any cypress command from events, as you said.

How could I resolve it?

We got to add cy.state method to our typescript definitions even if just a few properties, then our docs would be enough for people to build whatever they want I think

This would be very helpful 😃

@drewbrend Brian means use afterEach or after hook and execute cy.task or any other Cypress command there 😃 Not Cypress.on hook